mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
refactor(web): rename Execution mode and tighten settings dialog UI (#1568)
* refactor(web): rename Execution mode and tighten settings dialog UI - Rename "Settings → Execution & model" to "Settings → Execution mode" across the web UI, i18n keys, docs, and e2e selectors. - Redesign SettingsDialog: kicker + title row in the modal head, a flatMap-driven agent grid that renders the inline test-result row beside the selected card, compact unavailable cards with right-aligned install/docs links, and an install guide that only shows when the user has no working agent picked. - Trim verbose subtitle / hint copy across chat model, CLI proxy, media providers, custom instructions, and memory sections. - Add an `info` Icon variant for the redesigned settings hints. - Update e2e selectors and docs that referenced the old menu label. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(web): polish Settings dialog — media providers, skills, MCP Media providers - Hide internal Stub fixture provider (settingsVisible: false) - Split provider list into Available (integrated, editable) and Coming Soon (collapsed <details> drawer with name/hint/Docs link only) - Drop right-side Integrated/Configured badges from every row; all rows in the main list are integrated by definition; inline grey "Saved" chip next to the provider name is the only status indicator now - "Saved" badge moves inline to the right of the provider name and uses a neutral grey treatment (was a standalone green pill below the name) - "Reload from daemon" button shows a 2s green "✓ Reloaded" flash on success instead of leaving a permanent paragraph under the header; errors remain sticky Skills - Replace three pill-row filter banks (Source, Type, Category) with a compact single-row toolbar: search + three inline <select> dropdowns side by side; active filter highlighted with a stronger border MCP server - Shorten section hint to one line - Move WHAT YOUR AGENT CAN DO capabilities above the client dropdown (motivate before asking to act) - Move "Build the daemon first" warning below the code block where it contextually explains why the command might fail, not as a top-level error before the user has done anything - Downgrade "Restart your client" left-border from accent orange to border-strong grey — it is a next step, not a warning External MCP - Shorten section hint to one line Misc CSS - Add .sr-only utility for accessible off-screen live regions - Add button.ghost.is-success-flash for transient success feedback - Add .library-filter-selects / .library-filter-select for dropdown filter rows - Add .media-provider-coming-soon-* for the roadmap drawer Co-authored-by: Cursor <cursoragent@cursor.com> * [codex] Add Cursor Agent auth diagnostics (#1538) * Add Cursor Agent auth diagnostics * Handle Cursor not logged in auth status * Address Cursor auth review feedback * Classify Cursor stdout auth failures * test: expand Memory and Routines coverage (#1521) * test: expand settings and packaged coverage * test: extend memory settings coverage * test: cover routine settings failure states * test: cover routine operation failures * test: fix daemon test typing on CI * test: decouple packaged smoke from orbit bug * test: avoid live memory LLM calls in route tests * test: fix daemon fetch typing in CI * fix: restore preview comment and inspect toggles * test: align manual edit flow with current inspector UX * test: align comment attachment flow with current preview comments UI * fix: probe resolved Codex launch path during detection * fix: remove duplicate board activation helper after rebase * test: update ghost cli detection mock * test: align FileViewer toolbar expectation * ci: move full app tests to extended lane * ci: run app tests by changed scope * ci: cover shared app inputs in test scopes * ci: avoid setup-node cache in windows packaged smoke * test: align extended settings and manual edit flows * refactor(web): rename Execution mode and tighten settings dialog UI - Rename "Settings → Execution & model" to "Settings → Execution mode" across the web UI, i18n keys, docs, and e2e selectors. - Redesign SettingsDialog: kicker + title row in the modal head, a flatMap-driven agent grid that renders the inline test-result row beside the selected card, compact unavailable cards with right-aligned install/docs links, and an install guide that only shows when the user has no working agent picked. - Trim verbose subtitle / hint copy across chat model, CLI proxy, media providers, custom instructions, and memory sections. - Add an `info` Icon variant for the redesigned settings hints. - Update e2e selectors and docs that referenced the old menu label. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(web): settings dialog UX polish — layout, dedup, and interactions - Remove duplicate section headers from all settings sections (Notifications, Appearance, Privacy, About, Design Systems, Skills, MCP server, Connectors, Media providers, Routines) - Restructure Notifications cards: title + toggle on same row, hint below - Restructure Skills toolbar: search + New skill button in row 1, filter dropdowns in row 2 with left-aligned labels - Restructure Pet section: tabs and Wake button on same row - MCP server: group capabilities and setup into separate cards, remove nested double border on client picker - Connectors: show connect errors as toast instead of inline card text, position toast inside panel, hide single-provider tab - Media providers: move Reload button to left-aligned small ghost button - Memory: info icon shows path on hover, Path copied badge inline; Extraction history and MEMORY.md as standalone collapsible cards; group header hidden when only one type visible - Pet grid cards: Adopt button hidden until hover, icon-only when adopted, description truncated to 2 lines, text fills full width via abs positioning - Agent cards: selected state uses accent border only, no background change - Add sun/moon icons to Appearance theme buttons (Light/Dark) - Shorten several hint strings for clarity Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): resolve i18n review comments from PR #1568 - Update settings.title and settings.envConfigure to localized "Execution mode" in all 17 non-English locale files - Add settings.memoryFlashPathCopied to all locales and use t() in MemorySection instead of hardcoded English "Path copied" - Add settings.agentModelHead to all locales and use t() in SettingsDialog for "Model for:" agent model row header Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): update tests to match settings dialog redesign - Add role prop to Toast (alert/status) so error toasts from ConnectorsBrowser are announced immediately by screen readers - Clear connectErrorToast on successful connector retry - Update SettingsDialog.execution tests: - Remove heading assertions for About and MCP server (headers were intentionally removed as duplicate nav labels) - Rewrite CLI env test to use codex-only fields (per-agent filtering means only selected agent's fields are shown) - Update Composio key hint text assertion to match shortened copy - Replace filter button click with select change for Type filter - Replace Configured/Unsupported/Integrated badge checks with updated assertions matching the new media provider UI - Replace disabled BFL row test with coming-soon section check - Update SettingsDialog.media test: remove Fal.ai input assertions (non-integrated providers no longer have editable fields) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): unblock CI for #1568 Three small fixes to get Playwright back to green on the settings dialog redesign: 1. `en.ts`: revert `settings.envConfigure` to "Configure execution mode". This PR collapsed both `settings.title` (header gear) and `settings.envConfigure` (entry-side foot pill) to the same string "Execution mode", so `getByRole('button', { name: 'Execution mode' })` resolved to two elements and tripped Playwright strict mode in the three Composio-flow tests (entry-configuration-flows.test.ts:174, 228, 285). Restoring the distinct label also gives screen readers a clearer hint for the pill, which doubles as a status display. Non-English locales still alias the two keys; happy to follow up on those, but they don't gate the (English-only) Playwright suite. 2. entry-configuration-flows.test.ts:167 — `Connectors` heading is now rendered at `<h2>` in the modal-head (SettingsDialog.tsx:1545), with the inner `<h3>` removed by design (see comment around line 1448). Updated the assertion from `level: 3` to `level: 2`. 3. project-management-flows.test.ts:360 — same change for the `Pets` heading. Verified locally with `pnpm --filter @open-design/web typecheck` and `pnpm --filter @open-design/e2e typecheck`. The actual Playwright specs need the dev server up; I didn't rerun them here, but the locator changes are mechanical and match the new DOM. * fix(web): use exact match for Execution mode button locator Playwright's `getByRole({ name })` defaults to substring matching, so `{ name: 'Execution mode' }` still resolved to both the header gear (aria-label "Execution mode") and the entry-side foot pill (aria-label "Configure execution mode" — substring contains "Execution mode"). Strict mode tripped in the three composio-flow tests at lines 202, 257, and 319. Adding `exact: true` makes each call resolve to just the header gear, which opens the same dialog the foot pill does — the test outcomes are unchanged. --------- Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Caprika <56862773+alchemistklk@users.noreply.github.com> Co-authored-by: shangxinyu1 <shangxinyu@refly.ai> Co-authored-by: lefarcen <935902669@qq.com>
This commit is contained in:
parent
a41d4f6126
commit
bcc58af931
46 changed files with 1708 additions and 744 deletions
|
|
@ -13,7 +13,7 @@ Run the full product locally.
|
|||
|
||||
### Local agent CLI and PATH
|
||||
|
||||
The daemon scans your **`PATH`** (plus common user toolchain directories). If you install a CLI with **`npm install -g`** or **Homebrew** and Open Design still shows it as *not installed*, the GUI may be starting with a minimal `PATH` that does not include your global npm or Homebrew `bin` directory (common on macOS when the app is not launched from a full login shell). Ensure the executable’s directory is on `PATH` for the process that runs the daemon, then use **Rescan** in **Settings → Execution & model**.
|
||||
The daemon scans your **`PATH`** (plus common user toolchain directories). If you install a CLI with **`npm install -g`** or **Homebrew** and Open Design still shows it as *not installed*, the GUI may be starting with a minimal `PATH` that does not include your global npm or Homebrew `bin` directory (common on macOS when the app is not launched from a full login shell). Ensure the executable’s directory is on `PATH` for the process that runs the daemon, then use **Rescan** in **Settings → Execution mode**.
|
||||
|
||||
`nvm` / `fnm` are optional convenience tools, not required project setup. If you use one, install/select Node 24 before running pnpm:
|
||||
|
||||
|
|
@ -349,7 +349,7 @@ open-design/
|
|||
claude auth status --text
|
||||
printf 'hello' | claude -p --output-format stream-json --verbose --permission-mode bypassPermissions
|
||||
```
|
||||
If the smoke test reports `401`, `apiKeySource: "none"`, or another auth error without a custom endpoint, run `claude`, use `/login`, exit Claude, and retry Open Design. If you use multiple Claude profiles, set **Settings -> Execution & model -> Claude Code config directory** to the profile path such as `~/.claude-2`. If `ANTHROPIC_BASE_URL` or a proxy is set, check the endpoint URL, proxy credentials, endpoint auth environment, and model access; remove the custom endpoint only if you want to retry with standard Claude Code auth. On Windows, native PowerShell and WSL use separate Claude installs and credential stores; re-authenticate in the same environment Open Design uses, and check Windows Credential Manager if `/login` does not repair native Windows credentials.
|
||||
If the smoke test reports `401`, `apiKeySource: "none"`, or another auth error without a custom endpoint, run `claude`, use `/login`, exit Claude, and retry Open Design. If you use multiple Claude profiles, set **Settings -> Execution mode -> Claude Code config directory** to the profile path such as `~/.claude-2`. If `ANTHROPIC_BASE_URL` or a proxy is set, check the endpoint URL, proxy credentials, endpoint auth environment, and model access; remove the custom endpoint only if you want to retry with standard Claude Code auth. On Windows, native PowerShell and WSL use separate Claude installs and credential stores; re-authenticate in the same environment Open Design uses, and check Windows Credential Manager if `/login` does not repair native Windows credentials.
|
||||
- **daemon 500 on /api/chat** — check the daemon terminal for the stderr tail; usually the CLI rejected its args. Different CLIs take different argv shapes; see `apps/daemon/src/agents.ts` `buildArgs` if you need to tweak.
|
||||
- **media generation says `OD_BIN` is missing or daemon URL is `:0`** — run the media dispatcher checks above. Do not resume the old CLI session; reopen the project from the Open Design app so the daemon can inject fresh `OD_*` variables.
|
||||
- **Codex loads too much plugin context** — start Open Design with `OD_CODEX_DISABLE_PLUGINS=1 pnpm tools-dev` to make daemon-spawned Codex processes run with `--disable plugins`.
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
} from './EntryView';
|
||||
import { Icon } from './Icon';
|
||||
import { CenteredLoader } from './Loading';
|
||||
import { Toast } from './Toast';
|
||||
|
||||
const CONNECTOR_CALLBACK_MESSAGE_TYPE = 'open-design:connector-connected';
|
||||
const CONNECTOR_AUTH_PENDING_STORAGE_KEY = 'od-connectors-authorization-pending';
|
||||
|
|
@ -556,6 +557,7 @@ export function ConnectorsBrowser({
|
|||
const [connectorAuthorizationPending, setConnectorAuthorizationPending] = useState<ConnectorAuthorizationPendingState>(() => loadConnectorAuthorizationPending());
|
||||
const [connectorAuthorizationCancelFailed, setConnectorAuthorizationCancelFailed] = useState<Record<string, boolean>>({});
|
||||
const [connectorAuthorizationError, setConnectorAuthorizationError] = useState<Record<string, string>>({});
|
||||
const [connectErrorToast, setConnectErrorToast] = useState<string | null>(null);
|
||||
const [detailConnectorId, setDetailConnectorId] = useState<string | null>(null);
|
||||
const [toolPreviewLoadingIds, setToolPreviewLoadingIds] = useState<Record<string, boolean>>({});
|
||||
const [toolPreviewFetchedIds, setToolPreviewFetchedIds] = useState<Record<string, boolean>>({});
|
||||
|
|
@ -710,6 +712,7 @@ export function ConnectorsBrowser({
|
|||
const result = await connectConnector(connectorId);
|
||||
updateConnector(result.connector);
|
||||
if (result.connector && !result.error) {
|
||||
setConnectErrorToast(null);
|
||||
setConnectorAuthorizationPending((curr) => updateConnectorAuthorizationPendingFromConnectResponse(curr, {
|
||||
connector: result.connector!,
|
||||
...(result.auth === undefined ? {} : { auth: result.auth }),
|
||||
|
|
@ -717,7 +720,7 @@ export function ConnectorsBrowser({
|
|||
} else {
|
||||
setConnectorAuthorizationPending((curr) => clearConnectorAuthorizationPending(curr, connectorId));
|
||||
if (result.error) {
|
||||
setConnectorAuthorizationError((curr) => ({ ...curr, [connectorId]: result.error! }));
|
||||
setConnectErrorToast(result.error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -815,6 +818,15 @@ export function ConnectorsBrowser({
|
|||
|
||||
return (
|
||||
<div className="tab-panel connectors-panel connectors-panel-embedded">
|
||||
{connectErrorToast ? (
|
||||
<div className="connectors-toast-anchor">
|
||||
<Toast
|
||||
message={connectErrorToast}
|
||||
role="alert"
|
||||
onDismiss={() => setConnectErrorToast(null)}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="tab-panel-toolbar">
|
||||
<div className="toolbar-left connectors-heading">
|
||||
<div>
|
||||
|
|
@ -1173,11 +1185,6 @@ function ConnectorCard({
|
|||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{authorizationError ? (
|
||||
<p className="connector-authorization-hint connector-authorization-error" role="alert">
|
||||
{authorizationError}
|
||||
</p>
|
||||
) : null}
|
||||
{authorizationCancelFailed ? (
|
||||
<p className="connector-authorization-hint connector-authorization-error" role="alert">
|
||||
{AUTHORIZATION_CANCEL_FAILED_MESSAGE}
|
||||
|
|
@ -1343,11 +1350,6 @@ function ConnectorDetailDrawer({
|
|||
) : null}
|
||||
</section>
|
||||
) : null}
|
||||
{authorizationError ? (
|
||||
<p className="connector-authorization-hint connector-authorization-error" role="alert">
|
||||
{authorizationError}
|
||||
</p>
|
||||
) : null}
|
||||
{authorizationCancelFailed ? (
|
||||
<p className="connector-authorization-hint connector-authorization-error" role="alert">
|
||||
{AUTHORIZATION_CANCEL_FAILED_MESSAGE}
|
||||
|
|
|
|||
|
|
@ -107,13 +107,6 @@ export function DesignSystemsSection({ cfg, setCfg }: Props) {
|
|||
|
||||
return (
|
||||
<section className="settings-section settings-design-systems">
|
||||
<div className="section-head">
|
||||
<div>
|
||||
<h3>{t('settings.designSystems')}</h3>
|
||||
<p className="hint">{t('settings.designSystemsHint')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="library-toolbar">
|
||||
<input
|
||||
type="search"
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ type IconName =
|
|||
| 'history'
|
||||
| 'image'
|
||||
| 'import'
|
||||
| 'info'
|
||||
| 'kanban'
|
||||
| 'languages'
|
||||
| 'link'
|
||||
|
|
@ -47,6 +48,8 @@ type IconName =
|
|||
| 'spinner'
|
||||
| 'sparkles'
|
||||
| 'stop'
|
||||
| 'sun'
|
||||
| 'moon'
|
||||
| 'sun-moon'
|
||||
| 'thumbs-down'
|
||||
| 'thumbs-up'
|
||||
|
|
@ -260,6 +263,14 @@ export function Icon({ name, size = 14, strokeWidth = 1.6, ...rest }: Props) {
|
|||
<path d="M12 3v12" />
|
||||
</svg>
|
||||
);
|
||||
case 'info':
|
||||
return (
|
||||
<svg {...common}>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="12" y1="16" x2="12" y2="12" />
|
||||
<line x1="12" y1="8" x2="12.01" y2="8" />
|
||||
</svg>
|
||||
);
|
||||
case 'kanban':
|
||||
return (
|
||||
<svg {...common}>
|
||||
|
|
@ -435,6 +446,19 @@ export function Icon({ name, size = 14, strokeWidth = 1.6, ...rest }: Props) {
|
|||
<rect x="6" y="6" width="12" height="12" rx="1.5" />
|
||||
</svg>
|
||||
);
|
||||
case 'sun':
|
||||
return (
|
||||
<svg {...common}>
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" />
|
||||
</svg>
|
||||
);
|
||||
case 'moon':
|
||||
return (
|
||||
<svg {...common}>
|
||||
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
|
||||
</svg>
|
||||
);
|
||||
case 'sun-moon':
|
||||
return (
|
||||
<svg {...common}>
|
||||
|
|
|
|||
|
|
@ -366,10 +366,7 @@ export const McpClientSection = forwardRef<McpClientSectionHandle, Props>(
|
|||
<div className="section-head">
|
||||
<div>
|
||||
<h3>External MCP servers</h3>
|
||||
<p className="hint">
|
||||
Surface tools from third-party MCP servers (Higgsfield, GitHub,
|
||||
filesystem…) to your coding agent.
|
||||
</p>
|
||||
<p className="hint">Third-party tools for your coding agent.</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Inline "Memory model" picker — sits right next to the chat model
|
||||
// dropdown (both in CLI mode and BYOK mode) inside Settings →
|
||||
// Configure execution mode.
|
||||
// Execution mode.
|
||||
//
|
||||
// Why one tiny dropdown instead of a separate panel:
|
||||
// User feedback was explicit — the memory extractor isn't a parallel
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ function formatDuration(record: MemoryExtractionRecord): string | null {
|
|||
return `${Math.round(ms / 1000)}s`;
|
||||
}
|
||||
|
||||
type FlashKind = 'created' | 'saved' | 'deleted' | 'indexSaved';
|
||||
type FlashKind = 'created' | 'saved' | 'deleted' | 'indexSaved' | 'pathCopied';
|
||||
|
||||
export function MemorySection() {
|
||||
const t = useT();
|
||||
|
|
@ -305,10 +305,32 @@ export function MemorySection() {
|
|||
saved: t('settings.memoryFlashSaved'),
|
||||
deleted: t('settings.memoryFlashDeleted'),
|
||||
indexSaved: t('settings.memoryFlashIndexSaved'),
|
||||
pathCopied: t('settings.memoryFlashPathCopied'),
|
||||
}),
|
||||
[t],
|
||||
);
|
||||
|
||||
const onCopyPath = useCallback(async () => {
|
||||
if (!rootDir) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(rootDir);
|
||||
fireFlash('pathCopied');
|
||||
} catch {
|
||||
// Some sandboxed contexts block clipboard writes silently. Fall
|
||||
// back to a transient input so the user can still grab the path
|
||||
// with a manual select-all + copy.
|
||||
const input = document.createElement('input');
|
||||
input.value = rootDir;
|
||||
input.style.position = 'fixed';
|
||||
input.style.opacity = '0';
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(input);
|
||||
fireFlash('pathCopied');
|
||||
}
|
||||
}, [rootDir, fireFlash]);
|
||||
|
||||
const TYPE_LABEL: Record<MemoryType, string> = useMemo(
|
||||
() => ({
|
||||
user: t('settings.memoryTypeUser'),
|
||||
|
|
@ -540,10 +562,43 @@ export function MemorySection() {
|
|||
}, [reloadExtractions]);
|
||||
|
||||
return (
|
||||
<section className={`settings-section${enabled ? '' : ' is-disabled'}`}>
|
||||
<section
|
||||
className={`settings-section settings-section-card${enabled ? '' : ' is-disabled'}`}
|
||||
>
|
||||
<div className="section-head">
|
||||
<div>
|
||||
<h3>{t('settings.memory')}</h3>
|
||||
<h3 className="memory-title-row">
|
||||
<span>{t('settings.memory')}</span>
|
||||
{/*
|
||||
Storage path used to render as a permanently-visible
|
||||
<code>/Users/.../.od/memory</code> line in the body. Most
|
||||
users only need this once (to peek at the markdown files)
|
||||
and then never again, so the line was pure noise after the
|
||||
first glance. We tucked it behind an info button next to
|
||||
the title: native tooltip on hover reveals the full path,
|
||||
and a click copies it to clipboard with a "Path copied"
|
||||
flash. Inline English for the aria-label; PR-time
|
||||
translation sweep can lift it later.
|
||||
*/}
|
||||
{rootDir ? (
|
||||
<span className="memory-info-wrap">
|
||||
<button
|
||||
type="button"
|
||||
className="memory-info-btn"
|
||||
onClick={() => void onCopyPath()}
|
||||
title={rootDir}
|
||||
aria-label="Memory storage path — click to copy"
|
||||
>
|
||||
<Icon name="info" size={13} />
|
||||
</button>
|
||||
{flash?.kind === 'pathCopied' ? (
|
||||
<span key={flash.key} className="memory-path-copied-badge">
|
||||
{flashLabel.pathCopied}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
) : null}
|
||||
</h3>
|
||||
<p className="hint">{t('settings.memoryDescription')}</p>
|
||||
</div>
|
||||
<label
|
||||
|
|
@ -574,12 +629,6 @@ export function MemorySection() {
|
|||
</div>
|
||||
) : null}
|
||||
|
||||
{rootDir ? (
|
||||
<p className="hint memory-root-dir">
|
||||
<code>{rootDir}</code>
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<div className="library-toolbar is-row">
|
||||
<div className="library-filters">
|
||||
<button
|
||||
|
|
@ -617,7 +666,7 @@ export function MemorySection() {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
{flash ? (
|
||||
{flash && flash.kind !== 'pathCopied' ? (
|
||||
<div
|
||||
key={flash.key}
|
||||
role="status"
|
||||
|
|
@ -804,20 +853,37 @@ export function MemorySection() {
|
|||
|
||||
<div className="library-content">
|
||||
{filtered.length === 0 ? (
|
||||
<p className="library-empty">
|
||||
{t('settings.memoryEmpty')}{' '}
|
||||
<code>{t('settings.memoryEmptyHintZh')}</code> /{' '}
|
||||
<code>{t('settings.memoryEmptyHintEn')}</code>
|
||||
</p>
|
||||
/*
|
||||
Empty state — the previous one inlined two side-by-side
|
||||
<code> snippets ("记住:用户偏好深色主题 / I prefer dark
|
||||
mode") which read like duelling locales and made the user
|
||||
wonder if the chips were tap-to-prefill or just decorative.
|
||||
We now show one clear "no rows yet" line and a one-sentence
|
||||
primer that explains the mechanism (talk in chat, fact gets
|
||||
extracted) with a single example. Inline English; PR-time
|
||||
translation sweep can lift this into the dictionary.
|
||||
*/
|
||||
<div className="library-empty">
|
||||
<p className="library-empty-title">
|
||||
{t('settings.memoryEmpty')}
|
||||
</p>
|
||||
<p className="library-empty-hint">
|
||||
Tell the assistant a fact in chat — e.g.{' '}
|
||||
<code>I prefer dark mode</code> — and it will be saved
|
||||
here automatically.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
TYPES.filter((tt) => grouped.has(tt)).map((type) => (
|
||||
TYPES.filter((tt) => grouped.has(tt)).map((type, _i, arr) => (
|
||||
<div key={type} className="library-group">
|
||||
<h4 className="library-group-title">
|
||||
{TYPE_LABEL[type]}{' '}
|
||||
<span className="library-group-count">
|
||||
{grouped.get(type)!.length}
|
||||
</span>
|
||||
</h4>
|
||||
{arr.length > 1 ? (
|
||||
<h4 className="library-group-title">
|
||||
{TYPE_LABEL[type]}{' '}
|
||||
<span className="library-group-count">
|
||||
{grouped.get(type)!.length}
|
||||
</span>
|
||||
</h4>
|
||||
) : null}
|
||||
{grouped.get(type)!.map((entry) => (
|
||||
<div key={entry.id} className="library-card">
|
||||
<div className="library-card-info">
|
||||
|
|
@ -876,7 +942,7 @@ export function MemorySection() {
|
|||
)}
|
||||
</div>
|
||||
|
||||
<details className="library-group memory-extractions" style={{ marginTop: 16 }}>
|
||||
<details className="library-group memory-extractions memory-collapsible-card">
|
||||
<summary className="memory-details-summary">
|
||||
<span className="memory-details-title">
|
||||
{t('settings.memoryExtractions')}
|
||||
|
|
@ -894,17 +960,20 @@ export function MemorySection() {
|
|||
</span>
|
||||
) : null}
|
||||
</summary>
|
||||
<p className="hint" style={{ marginTop: 4, marginBottom: 8 }}>
|
||||
{t('settings.memoryExtractionsHint')}
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
gap: 6,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
marginTop: 8,
|
||||
marginBottom: 8,
|
||||
}}
|
||||
>
|
||||
<span style={{ flex: 1, fontSize: 12, color: 'var(--text-muted)', margin: 0 }}>
|
||||
{t('settings.memoryExtractionsHint')}
|
||||
</span>
|
||||
<div style={{ display: 'flex', gap: 6, flexShrink: 0 }}>
|
||||
{extractions.length > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -930,7 +999,8 @@ export function MemorySection() {
|
|||
{isRefreshing ? t('settings.memoryExtractionsRefreshing') : t('settings.memoryExtractionsRefresh')}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>{/* end buttons */}
|
||||
</div>{/* end hint+buttons row */}
|
||||
{extractions.length === 0 ? (
|
||||
<p className="library-empty">{t('settings.memoryExtractionsEmpty')}</p>
|
||||
) : (
|
||||
|
|
@ -1036,7 +1106,7 @@ export function MemorySection() {
|
|||
)}
|
||||
</details>
|
||||
|
||||
<details className="library-group" style={{ marginTop: 16 }}>
|
||||
<details className="library-group memory-collapsible-card">
|
||||
<summary className="memory-details-summary">
|
||||
<span className="memory-details-title">
|
||||
{t('settings.memoryIndex')}
|
||||
|
|
|
|||
|
|
@ -70,13 +70,6 @@ export function PrivacySection({ cfg, setCfg }: Props): JSX.Element {
|
|||
|
||||
return (
|
||||
<section className="settings-section">
|
||||
<div className="section-head">
|
||||
<div>
|
||||
<h3>{t('settings.privacy')}</h3>
|
||||
<p className="hint">{t('settings.privacyHint')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!hasMadeConsentDecision ? (
|
||||
<ConsentCard onShare={shareUsage} onDecline={declineUsage} />
|
||||
) : (
|
||||
|
|
@ -121,9 +114,9 @@ export function PrivacySection({ cfg, setCfg }: Props): JSX.Element {
|
|||
type="button"
|
||||
className="ghost"
|
||||
onClick={deleteMyData}
|
||||
style={{ alignSelf: 'flex-start' }}
|
||||
style={{ alignSelf: 'flex-start', marginTop: 12 }}
|
||||
>
|
||||
<Icon name="refresh" size={13} />
|
||||
<Icon name="trash" size={13} />
|
||||
<span style={{ marginLeft: 6 }}>{t('settings.privacyDataDeletion')}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -532,11 +532,6 @@ export function RoutinesSection({ onClose }: RoutinesSectionProps) {
|
|||
<div className="section-head">
|
||||
<div>
|
||||
<h3>Routines</h3>
|
||||
<p className="hint">
|
||||
Scheduled, unattended agent sessions. Each run starts a new
|
||||
conversation — either inside an existing project, or in a fresh
|
||||
project minted on the spot.
|
||||
</p>
|
||||
</div>
|
||||
{!showForm ? (
|
||||
<button
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -335,91 +335,88 @@ export function SkillsSection({ cfg, setCfg }: Props) {
|
|||
|
||||
return (
|
||||
<section className="settings-section settings-skills">
|
||||
<div className="section-head">
|
||||
<div>
|
||||
<h3>{t('settings.skills')}</h3>
|
||||
<p className="hint">{t('settings.skillsHint')}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="primary skills-add-btn"
|
||||
onClick={startCreate}
|
||||
data-testid="skills-new"
|
||||
>
|
||||
<Icon name="plus" size={13} />
|
||||
<span>{t('settings.skillsNew')}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="library-toolbar">
|
||||
<input
|
||||
type="search"
|
||||
className="library-search"
|
||||
placeholder={t('settings.librarySearch')}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
<div className="library-filters">
|
||||
{(['all', 'user', 'built-in'] as const).map((s) => {
|
||||
const count =
|
||||
s === 'all'
|
||||
? skills.length
|
||||
: skills.filter((skill) => skill.source === s).length;
|
||||
return (
|
||||
<button
|
||||
key={s}
|
||||
type="button"
|
||||
className={`filter-pill${sourceFilter === s ? ' active' : ''}`}
|
||||
onClick={() => setSourceFilter(s)}
|
||||
>
|
||||
{s === 'all' ? t('settings.libraryAll') : s}
|
||||
<span className="filter-pill-count">{count}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="library-filters">
|
||||
<div className="library-toolbar skills-toolbar">
|
||||
{/* Row 1: search + New skill button */}
|
||||
<div className="skills-toolbar-top">
|
||||
<input
|
||||
type="search"
|
||||
className="library-search"
|
||||
placeholder={t('settings.librarySearch')}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className={`filter-pill${modeFilter === 'all' ? ' active' : ''}`}
|
||||
onClick={() => setModeFilter('all')}
|
||||
className="primary skills-add-btn"
|
||||
onClick={startCreate}
|
||||
data-testid="skills-new"
|
||||
>
|
||||
{t('settings.libraryAll')}
|
||||
<Icon name="plus" size={13} />
|
||||
<span>{t('settings.skillsNew')}</span>
|
||||
</button>
|
||||
{modeOptions.map(([mode, count]) => (
|
||||
<button
|
||||
key={mode}
|
||||
type="button"
|
||||
className={`filter-pill${modeFilter === mode ? ' active' : ''}`}
|
||||
onClick={() => setModeFilter(mode)}
|
||||
>
|
||||
{mode}
|
||||
<span className="filter-pill-count">{count}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{categoryOptions.length > 0 ? (
|
||||
<div className="library-filters" data-testid="skills-category-filters">
|
||||
<button
|
||||
type="button"
|
||||
className={`filter-pill${categoryFilter === 'all' ? ' active' : ''}`}
|
||||
onClick={() => setCategoryFilter('all')}
|
||||
{/* Row 2: filter dropdowns */}
|
||||
<div className="library-filter-selects">
|
||||
<label className="library-filter-select">
|
||||
<span className="library-filter-select-label">Source</span>
|
||||
<select
|
||||
value={sourceFilter}
|
||||
data-active={sourceFilter !== 'all' ? 'true' : undefined}
|
||||
onChange={(e) => setSourceFilter(e.target.value as SourceFilter)}
|
||||
>
|
||||
{t('settings.libraryAll')}
|
||||
</button>
|
||||
{categoryOptions.map(([cat, count]) => (
|
||||
<button
|
||||
key={cat}
|
||||
type="button"
|
||||
className={`filter-pill${categoryFilter === cat ? ' active' : ''}`}
|
||||
onClick={() => setCategoryFilter(cat)}
|
||||
<option value="all">
|
||||
{t('settings.libraryAll')} ({skills.length})
|
||||
</option>
|
||||
{(['user', 'built-in'] as const).map((s) => {
|
||||
const count = skills.filter((sk) => sk.source === s).length;
|
||||
return (
|
||||
<option key={s} value={s}>
|
||||
{s} ({count})
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</label>
|
||||
<label className="library-filter-select">
|
||||
<span className="library-filter-select-label">Type</span>
|
||||
<select
|
||||
value={modeFilter}
|
||||
data-active={modeFilter !== 'all' ? 'true' : undefined}
|
||||
onChange={(e) => setModeFilter(e.target.value)}
|
||||
>
|
||||
<option value="all">
|
||||
{t('settings.libraryAll')} ({skills.length})
|
||||
</option>
|
||||
{modeOptions.map(([mode, count]) => (
|
||||
<option key={mode} value={mode}>
|
||||
{mode} ({count})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
{categoryOptions.length > 0 ? (
|
||||
<label
|
||||
className="library-filter-select"
|
||||
data-testid="skills-category-filters"
|
||||
>
|
||||
<span className="library-filter-select-label">Category</span>
|
||||
<select
|
||||
value={categoryFilter}
|
||||
data-active={categoryFilter !== 'all' ? 'true' : undefined}
|
||||
onChange={(e) => setCategoryFilter(e.target.value)}
|
||||
>
|
||||
{humanizeCategory(cat)}
|
||||
<span className="filter-pill-count">{count}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<option value="all">
|
||||
{t('settings.libraryAll')} ({skills.length})
|
||||
</option>
|
||||
{categoryOptions.map(([cat, count]) => (
|
||||
<option key={cat} value={cat}>
|
||||
{humanizeCategory(cat)} ({count})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{creating ? (
|
||||
|
|
|
|||
|
|
@ -24,11 +24,14 @@ export interface ToastProps {
|
|||
code?: string | null;
|
||||
ttlMs?: number;
|
||||
onDismiss?: () => void;
|
||||
/** ARIA role. Use "alert" for error messages (announced immediately),
|
||||
* "status" (default) for non-urgent confirmations. */
|
||||
role?: 'status' | 'alert';
|
||||
}
|
||||
|
||||
const DEFAULT_TTL = 4000;
|
||||
|
||||
export function Toast({ message, details, code, ttlMs = DEFAULT_TTL, onDismiss }: ToastProps) {
|
||||
export function Toast({ message, details, code, ttlMs = DEFAULT_TTL, onDismiss, role = 'status' }: ToastProps) {
|
||||
// When code is present the toast is a manual-action surface; never
|
||||
// auto-dismiss it out from under the user mid-copy.
|
||||
const effectiveTtl = code ? 0 : ttlMs;
|
||||
|
|
@ -42,7 +45,7 @@ export function Toast({ message, details, code, ttlMs = DEFAULT_TTL, onDismiss }
|
|||
}, [message, details, code, effectiveTtl, onDismiss]);
|
||||
|
||||
return (
|
||||
<div className="od-toast" role="status" aria-live="polite">
|
||||
<div className="od-toast" role={role} aria-live={role === 'alert' ? 'assertive' : 'polite'}>
|
||||
<div className="od-toast-message">{message}</div>
|
||||
{details ? <div className="od-toast-details">{details}</div> : null}
|
||||
{code ? (
|
||||
|
|
|
|||
|
|
@ -460,19 +460,16 @@ export function PetSettings({ cfg, setCfg }: Props) {
|
|||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`seg-btn small${isActive ? ' active' : ''}`}
|
||||
className={`seg-btn small pet-codex-adopt-btn${isActive ? ' active' : ''}`}
|
||||
onClick={() => void adoptCodexPet(p)}
|
||||
disabled={adopting || codexAdopting !== null}
|
||||
aria-pressed={isActive}
|
||||
aria-label={isActive ? t('pet.adoptedBadge') : t('pet.codexAdopt')}
|
||||
>
|
||||
<Icon name={adopting ? 'spinner' : 'check'} size={12} />
|
||||
<span>
|
||||
{adopting
|
||||
? t('pet.codexAdopting')
|
||||
: isActive
|
||||
? t('pet.adoptedBadge')
|
||||
: t('pet.codexAdopt')}
|
||||
</span>
|
||||
{!isActive ? (
|
||||
<span>{adopting ? t('pet.codexAdopting') : t('pet.codexAdopt')}</span>
|
||||
) : null}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -480,58 +477,53 @@ export function PetSettings({ cfg, setCfg }: Props) {
|
|||
|
||||
return (
|
||||
<section className="settings-section">
|
||||
<div className="section-head">
|
||||
<div>
|
||||
<h3>{t('pet.title')}</h3>
|
||||
<p className="hint">{t('pet.subtitle')}</p>
|
||||
</div>
|
||||
<div className="pet-wake-controls">
|
||||
<button
|
||||
type="button"
|
||||
className={`seg-btn small${pet.enabled ? ' active' : ''}`}
|
||||
onClick={() => update({ enabled: !pet.enabled, adopted: pet.adopted || pet.petId !== '' })}
|
||||
disabled={!pet.adopted}
|
||||
title={pet.enabled ? t('pet.tuckTitle') : t('pet.wakeTitle')}
|
||||
>
|
||||
<Icon name={pet.enabled ? 'eye' : 'sparkles'} size={14} />
|
||||
<span>{pet.enabled ? t('pet.tuck') : t('pet.wake')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pet-tabs">
|
||||
<div
|
||||
className="subtab-pill"
|
||||
role="tablist"
|
||||
aria-label={t('pet.tabsAria')}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeTab === 'builtIn'}
|
||||
className={activeTab === 'builtIn' ? 'active' : ''}
|
||||
onClick={() => setActiveTab('builtIn')}
|
||||
<div className="pet-tabs-top-row">
|
||||
<div
|
||||
className="subtab-pill"
|
||||
role="tablist"
|
||||
aria-label={t('pet.tabsAria')}
|
||||
>
|
||||
{t('pet.tabBuiltIn')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeTab === 'custom'}
|
||||
className={activeTab === 'custom' ? 'active' : ''}
|
||||
onClick={() => setActiveTab('custom')}
|
||||
>
|
||||
{t('pet.tabCustom')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeTab === 'community'}
|
||||
className={activeTab === 'community' ? 'active' : ''}
|
||||
onClick={() => setActiveTab('community')}
|
||||
>
|
||||
{t('pet.tabCommunity')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeTab === 'builtIn'}
|
||||
className={activeTab === 'builtIn' ? 'active' : ''}
|
||||
onClick={() => setActiveTab('builtIn')}
|
||||
>
|
||||
{t('pet.tabBuiltIn')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeTab === 'custom'}
|
||||
className={activeTab === 'custom' ? 'active' : ''}
|
||||
onClick={() => setActiveTab('custom')}
|
||||
>
|
||||
{t('pet.tabCustom')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeTab === 'community'}
|
||||
className={activeTab === 'community' ? 'active' : ''}
|
||||
onClick={() => setActiveTab('community')}
|
||||
>
|
||||
{t('pet.tabCommunity')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="pet-wake-controls">
|
||||
<button
|
||||
type="button"
|
||||
className={`seg-btn small${pet.enabled ? ' active' : ''}`}
|
||||
onClick={() => update({ enabled: !pet.enabled, adopted: pet.adopted || pet.petId !== '' })}
|
||||
disabled={!pet.adopted}
|
||||
title={pet.enabled ? t('pet.tuckTitle') : t('pet.wakeTitle')}
|
||||
>
|
||||
<Icon name={pet.enabled ? 'eye' : 'sparkles'} size={14} />
|
||||
<span>{pet.enabled ? t('pet.tuck') : t('pet.wake')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="hint pet-tabs-hint">
|
||||
{activeTab === 'builtIn'
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const ar: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
"اختر كيف تريد تشغيل الأجيال. يمكنك تغيير هذا في أي وقت من زر الإعدادات في الشريط العلوي.",
|
||||
'settings.kicker': 'الإعدادات',
|
||||
'settings.title': 'التنفيذ والنموذج',
|
||||
'settings.title': 'وضع التنفيذ',
|
||||
'settings.subtitle': 'اختر بين CLI المحلي و BYOK. يتم حفظ مفتاح API في هذا المتصفح فقط.',
|
||||
'settings.modeAria': 'وضع التنفيذ',
|
||||
'settings.protocolAria': 'بروتوكول API',
|
||||
|
|
@ -121,7 +121,7 @@ export const ar: Dict = {
|
|||
'settings.apiHint': 'تُرسل الطلبات عبر وكيل daemon المحلي إلى Base URL الذي تحدده. يُحفظ المفتاح في هذا المتصفح فقط ويُرسل مع طلبات المزود.',
|
||||
'settings.skipForNow': 'تخطي الآن',
|
||||
'settings.getStarted': 'ابدأ الآن',
|
||||
'settings.envConfigure': 'تكوين وضع التنفيذ',
|
||||
'settings.envConfigure': 'وضع التنفيذ',
|
||||
'settings.localCli': 'CLI محلي',
|
||||
'settings.anthropicApi': 'Anthropic API',
|
||||
'settings.noAgentSelected': 'لم يتم اختيار وكيل',
|
||||
|
|
@ -132,6 +132,7 @@ export const ar: Dict = {
|
|||
'settings.themeSystem': 'النظام',
|
||||
'settings.themeLight': 'فاتح',
|
||||
'settings.themeDark': 'داكن',
|
||||
'settings.agentModelHead': 'النموذج لـ:',
|
||||
'settings.modelPicker': 'النموذج',
|
||||
'settings.reasoningPicker': 'جهد التفكير',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -195,6 +196,8 @@ export const ar: Dict = {
|
|||
'settings.runtimePackaged': 'تطبيق معبأ',
|
||||
'settings.runtimeDevelopment': 'تطوير',
|
||||
'settings.versionUnavailable': 'تفاصيل النسخة غير متوفرة بينما البرنامج الخفي غير متصل.',
|
||||
'settings.installLatest': 'تثبيت الأحدث',
|
||||
'settings.alreadyLatest': 'أنت تستخدم أحدث إصدار',
|
||||
|
||||
'entry.tabDesigns': 'التصاميم',
|
||||
'entry.tabTemplates': 'قوالب',
|
||||
|
|
@ -1267,6 +1270,7 @@ export const ar: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ تم نسخ المسار',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const de: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
'Wählen Sie aus, wie Generierungen ausgeführt werden sollen. Sie können dies jederzeit über die Schaltfläche „Einstellungen“ in der oberen Leiste ändern.',
|
||||
'settings.kicker': 'Einstellungen',
|
||||
'settings.title': 'Ausführung & Modell',
|
||||
'settings.title': 'Ausführungsmodus',
|
||||
'settings.subtitle': 'Wählen Sie zwischen lokaler CLI und BYOK. Ihr API-Schlüssel wird nur in diesem Browser gespeichert.',
|
||||
'settings.modeAria': 'Ausführungsmodus',
|
||||
'settings.protocolAria': 'API-Protokoll',
|
||||
|
|
@ -121,7 +121,7 @@ export const de: Dict = {
|
|||
'settings.apiHint': 'Anfragen werden über den lokalen Daemon-Proxy an die festgelegte Base URL gesendet. Der Schlüssel wird nur in diesem Browser gespeichert und mit Provider-Anfragen gesendet.',
|
||||
'settings.skipForNow': 'Vorerst überspringen',
|
||||
'settings.getStarted': 'Loslegen',
|
||||
'settings.envConfigure': 'Ausführungsmodus konfigurieren',
|
||||
'settings.envConfigure': 'Ausführungsmodus',
|
||||
'settings.localCli': 'Lokale CLI',
|
||||
'settings.anthropicApi': 'Anthropic API',
|
||||
'settings.noAgentSelected': 'kein Agent ausgewählt',
|
||||
|
|
@ -132,6 +132,7 @@ export const de: Dict = {
|
|||
'settings.themeSystem': 'System',
|
||||
'settings.themeLight': 'Hell',
|
||||
'settings.themeDark': 'Dunkel',
|
||||
'settings.agentModelHead': 'Modell für:',
|
||||
'settings.modelPicker': 'Modell',
|
||||
'settings.reasoningPicker': 'Reasoning-Aufwand',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -195,6 +196,8 @@ export const de: Dict = {
|
|||
'settings.runtimePackaged': 'Paketierte App',
|
||||
'settings.runtimeDevelopment': 'Entwicklung',
|
||||
'settings.versionUnavailable': 'Versionsdetails sind nicht verfügbar, solange der Daemon offline ist.',
|
||||
'settings.installLatest': 'Neueste Version installieren',
|
||||
'settings.alreadyLatest': 'Sie verwenden die neueste Version',
|
||||
|
||||
'entry.tabDesigns': 'Designs',
|
||||
'entry.tabTemplates': 'Vorlagen',
|
||||
|
|
@ -1196,6 +1199,7 @@ export const de: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ Pfad kopiert',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ export const en: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
"Pick how you'd like to run generations. You can change this any time from the Settings button in the top bar.",
|
||||
'settings.kicker': 'Settings',
|
||||
'settings.title': 'Execution & model',
|
||||
'settings.subtitle': 'Choose between Local CLI and BYOK. Your API key is stored only in this browser.',
|
||||
'settings.title': 'Execution mode',
|
||||
'settings.subtitle': 'Choose Local CLI or BYOK. API keys are stored only in this browser.',
|
||||
'settings.modeAria': 'Execution mode',
|
||||
'settings.protocolAria': 'API protocol',
|
||||
'settings.modeDaemon': 'Local CLI',
|
||||
|
|
@ -59,8 +59,7 @@ export const en: Dict = {
|
|||
'settings.modeApi': 'API provider',
|
||||
'settings.modeApiMeta': 'BYOK',
|
||||
'settings.codeAgent': 'Code agent',
|
||||
'settings.codeAgentHint':
|
||||
'Detected by scanning your PATH. Pick the CLI you want generations to flow through.',
|
||||
'settings.codeAgentHint': 'Pick the CLI to route generations through.',
|
||||
'settings.rescan': '↻ Rescan',
|
||||
'settings.rescanTitle': 'Re-scan PATH',
|
||||
'settings.rescanRunning': 'Scanning...',
|
||||
|
|
@ -139,13 +138,14 @@ export const en: Dict = {
|
|||
'settings.themeSystem': 'System',
|
||||
'settings.themeLight': 'Light',
|
||||
'settings.themeDark': 'Dark',
|
||||
'settings.agentModelHead': 'Model for:',
|
||||
'settings.modelPicker': 'Model',
|
||||
'settings.reasoningPicker': 'Reasoning effort',
|
||||
'settings.modelPickerHint':
|
||||
'Fetched from the CLI when it exposes a `models` command. "Default" leaves the choice to the CLI’s own config; "Custom…" lets you type any model id the CLI accepts.',
|
||||
'settings.cliEnvTitle': 'CLI proxy and config',
|
||||
'Default uses the CLI’s own config. Custom… lets you type any model id.',
|
||||
'settings.cliEnvTitle': 'Advanced: proxy & custom paths',
|
||||
'settings.cliEnvHint':
|
||||
'Optional per-agent environment for packaged app runs, detection, and local proxy auth. Secrets are stored in local app config and only passed to the selected CLI.',
|
||||
'Use these only if you route CLI traffic through your own proxy or installed the binary in a non-standard location. Secrets stay in local app config and only the selected CLI sees them.',
|
||||
'settings.cliEnvClaudeConfigDir': 'Claude Code config directory',
|
||||
'settings.cliEnvClaudeBaseUrl': 'Claude proxy base URL',
|
||||
'settings.cliEnvClaudeApiKey': 'Claude proxy API key',
|
||||
|
|
@ -158,7 +158,7 @@ export const en: Dict = {
|
|||
'settings.modelCustomPlaceholder': 'e.g. anthropic/claude-sonnet-4-6',
|
||||
'settings.mediaProviders': 'Media providers',
|
||||
'settings.mediaProvidersHint':
|
||||
'API keys for image, video and audio generation. Stored locally and synced to the local daemon.',
|
||||
'API keys for image, video, and audio generation.',
|
||||
'settings.mcpServerTitle': 'MCP server',
|
||||
'settings.mcpServerHint': 'Expose Open Design as an MCP server for your coding agent.',
|
||||
'settings.externalMcpTitle': 'External MCP',
|
||||
|
|
@ -202,11 +202,12 @@ export const en: Dict = {
|
|||
'settings.runtimePackaged': 'Packaged app',
|
||||
'settings.runtimeDevelopment': 'Development',
|
||||
'settings.versionUnavailable': 'Version details are unavailable while the daemon is offline.',
|
||||
'settings.installLatest': 'Install latest',
|
||||
'settings.alreadyLatest': 'You\'re on the latest version',
|
||||
|
||||
// MCP server settings
|
||||
'settings.mcpTitle': 'MCP server',
|
||||
'settings.mcpHint':
|
||||
'Lets a coding agent in another repo (Claude Code, Cursor, VS Code, Antigravity, Zed, Windsurf) read your Open Design projects. Use it to pull a design into your app without exporting a zip first.',
|
||||
'settings.mcpHint': 'Let coding agents (Cursor, Claude Code, VS Code…) read your Open Design projects directly.',
|
||||
'settings.mcpDaemonError':
|
||||
"Couldn't reach the local daemon to resolve install paths ({error}). Make sure Open Design is running, then reopen this panel.",
|
||||
'settings.mcpBuildDaemon': 'Build the daemon first.',
|
||||
|
|
@ -1438,7 +1439,7 @@ export const en: Dict = {
|
|||
'settings.connectorsKeyError': 'Couldn\u2019t save the key. Check that the local daemon is running and try again.',
|
||||
'settings.connectorsHelpSaved': 'Your key is saved in the local daemon. Paste a new key to replace it, or Clear to remove.',
|
||||
'settings.connectorsHelpUnsaved': 'Unsaved changes \u2014 click Save key to store this credential in the local daemon and refresh the catalog below.',
|
||||
'settings.connectorsHelpEmpty': 'Add a key to load the catalog below. Keys are stored locally in the daemon and never sent through environment variables.',
|
||||
'settings.connectorsHelpEmpty': 'Keys are stored locally and never shared.',
|
||||
'settings.connectorsLoadingSavedKey': 'Checking for a saved key in the local daemon\u2026',
|
||||
'settings.autosaveSaving': 'Saving\u2026',
|
||||
'settings.autosaveSaved': 'All changes saved',
|
||||
|
|
@ -1448,9 +1449,9 @@ export const en: Dict = {
|
|||
'settings.memory': 'Memory',
|
||||
'settings.memoryHint': 'Personal facts auto-extracted from chats',
|
||||
'settings.customInstructionsTitle': 'Custom instructions',
|
||||
'settings.customInstructionsHint': 'Persistent instructions applied to every project. Use this to set preferences the model should always follow.',
|
||||
'settings.customInstructionsHint': 'Standing rules the assistant follows in every chat.',
|
||||
'settings.customInstructionsPlaceholder': 'e.g. "Always use TypeScript. Prefer functional components. Keep responses concise."',
|
||||
'settings.memoryDescription': 'Auto-extracted facts about you and your preferences. Saved as Markdown files and folded into every chat.',
|
||||
'settings.memoryDescription': 'Auto-extracted facts the assistant recalls in every chat.',
|
||||
'settings.memoryEnabled': 'Enabled',
|
||||
'settings.memoryDisabled': 'Disabled',
|
||||
'settings.memoryEnableLabel': 'Enable memory injection',
|
||||
|
|
@ -1483,6 +1484,7 @@ export const en: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ Path copied',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
@ -1498,7 +1500,7 @@ export const en: Dict = {
|
|||
'settings.memoryToastClickHint': 'View',
|
||||
'settings.memoryAll': 'All',
|
||||
'settings.memoryExtractions': 'Extraction history',
|
||||
'settings.memoryExtractionsHint': 'Recent extraction attempts. Heuristic regex extraction runs first; LLM extraction runs in the background after each turn.',
|
||||
'settings.memoryExtractionsHint': 'Regex runs first, LLM runs in background.',
|
||||
'settings.memoryExtractionsEmpty': 'No extractions yet. The next chat turn will populate this list.',
|
||||
'settings.memoryExtractionsRefresh': 'Refresh',
|
||||
'settings.memoryExtractionsRefreshing': 'Refreshing…',
|
||||
|
|
@ -1602,9 +1604,9 @@ export const en: Dict = {
|
|||
'settings.memoryModelInlineSameAsChat': 'Same as chat',
|
||||
'settings.memoryModelInlineSameAsChatWithModel': 'Same as chat ({model})',
|
||||
'settings.memoryModelInlineSameAsChatWithProvider': 'Same as chat ({provider})',
|
||||
'settings.memoryModelInlineHintCli': 'Optional. The memory extractor uses an env-var or media-providers API key on this provider; pinning a model here just overrides the auto-pick.',
|
||||
'settings.memoryModelInlineHintCliConstrained': 'Optional. Memory will call {provider}; needs an env-var or media-providers API key for that provider, or pick a model below to override.',
|
||||
'settings.memoryModelInlineHintByok': 'Optional. Reuses your chat API key on the same provider — picking a different (usually cheaper) model only changes the request body.',
|
||||
'settings.memoryModelInlineHintCli': 'Optional. Uses an env-var or media-providers key for the auto-picked provider.',
|
||||
'settings.memoryModelInlineHintCliConstrained': 'Optional. Calls {provider} with your env-var or media-providers key.',
|
||||
'settings.memoryModelInlineHintByok': 'Optional. Reuses your chat API key — pick a cheaper model to save cost.',
|
||||
'settings.memoryModelInlineFlashSaved': 'Saved',
|
||||
'settings.memoryModelInlineFlashCleared': 'Cleared',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const esES: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
'Elige cómo quieres ejecutar las generaciones. Puedes cambiarlo en cualquier momento desde el botón Ajustes en la barra superior.',
|
||||
'settings.kicker': 'Ajustes',
|
||||
'settings.title': 'Ejecución y modelo',
|
||||
'settings.title': 'Modo de ejecución',
|
||||
'settings.subtitle': 'Elige entre CLI local y BYOK. Tu clave de API se guarda solo en este navegador.',
|
||||
'settings.modeAria': 'Modo de ejecución',
|
||||
'settings.protocolAria': 'Protocolo de API',
|
||||
|
|
@ -121,7 +121,7 @@ export const esES: Dict = {
|
|||
'settings.apiHint': 'Las llamadas pasan por el proxy del daemon local hasta la URL base configurada. La clave se guarda solo en este navegador y se envía con las solicitudes al proveedor.',
|
||||
'settings.skipForNow': 'Omitir por ahora',
|
||||
'settings.getStarted': 'Empezar',
|
||||
'settings.envConfigure': 'Configurar el modo de ejecución',
|
||||
'settings.envConfigure': 'Modo de ejecución',
|
||||
'settings.localCli': 'CLI local',
|
||||
'settings.anthropicApi': 'API de Anthropic',
|
||||
'settings.noAgentSelected': 'ningún agente seleccionado',
|
||||
|
|
@ -132,6 +132,7 @@ export const esES: Dict = {
|
|||
'settings.themeSystem': 'Sistema',
|
||||
'settings.themeLight': 'Claro',
|
||||
'settings.themeDark': 'Oscuro',
|
||||
'settings.agentModelHead': 'Modelo para:',
|
||||
'settings.modelPicker': 'Modelo',
|
||||
'settings.reasoningPicker': 'Esfuerzo de razonamiento',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -195,6 +196,8 @@ export const esES: Dict = {
|
|||
'settings.runtimePackaged': 'App empaquetada',
|
||||
'settings.runtimeDevelopment': 'Desarrollo',
|
||||
'settings.versionUnavailable': 'Los detalles de versión no están disponibles mientras el daemon está offline.',
|
||||
'settings.installLatest': 'Instalar la última versión',
|
||||
'settings.alreadyLatest': 'Ya tienes la última versión',
|
||||
|
||||
'entry.tabDesigns': 'Diseños',
|
||||
'entry.tabTemplates': 'Plantillas',
|
||||
|
|
@ -1156,6 +1159,7 @@ export const esES: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ Ruta copiada',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const fa: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
'نحوه اجرای تولیدات را انتخاب کنید. میتوانید هر زمان از دکمه تنظیمات در نوار بالا این را تغییر دهید.',
|
||||
'settings.kicker': 'تنظیمات',
|
||||
'settings.title': 'اجرا و مدل',
|
||||
'settings.title': 'حالت اجرا',
|
||||
'settings.subtitle': 'بین CLI محلی و BYOK انتخاب کنید. کلید API فقط در همین مرورگر ذخیره میشود.',
|
||||
'settings.modeAria': 'حالت اجرا',
|
||||
'settings.protocolAria': 'پروتکل API',
|
||||
|
|
@ -121,7 +121,7 @@ export const fa: Dict = {
|
|||
'settings.apiHint': 'درخواستها از طریق پراکسی daemon محلی به Base URL تنظیمشده ارسال میشوند. کلید فقط در همین مرورگر ذخیره میشود و همراه درخواستهای ارائهدهنده فرستاده میشود.',
|
||||
'settings.skipForNow': 'فعلاً رد کنید',
|
||||
'settings.getStarted': 'شروع کنید',
|
||||
'settings.envConfigure': 'پیکربندی حالت اجرا',
|
||||
'settings.envConfigure': 'حالت اجرا',
|
||||
'settings.localCli': 'CLI محلی',
|
||||
'settings.anthropicApi': 'Anthropic API',
|
||||
'settings.noAgentSelected': 'هیچ عاملی انتخاب نشده',
|
||||
|
|
@ -132,6 +132,7 @@ export const fa: Dict = {
|
|||
'settings.themeSystem': 'سیستم',
|
||||
'settings.themeLight': 'روشن',
|
||||
'settings.themeDark': 'تاریک',
|
||||
'settings.agentModelHead': 'مدل برای:',
|
||||
'settings.modelPicker': 'مدل',
|
||||
'settings.reasoningPicker': 'سطح استدلال',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -195,6 +196,8 @@ export const fa: Dict = {
|
|||
'settings.runtimePackaged': 'برنامه بستهبندیشده',
|
||||
'settings.runtimeDevelopment': 'توسعه',
|
||||
'settings.versionUnavailable': 'تا وقتی daemon آفلاین است جزئیات نسخه در دسترس نیست.',
|
||||
'settings.installLatest': 'نصب جدیدترین',
|
||||
'settings.alreadyLatest': 'شما آخرین نسخه را دارید',
|
||||
|
||||
'entry.tabDesigns': 'طرحها',
|
||||
'entry.tabTemplates': 'قالبها',
|
||||
|
|
@ -1310,6 +1313,7 @@ export const fa: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ مسیر کپی شد',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const fr: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
'Choisissez comment vous souhaitez exécuter les générations. Vous pouvez changer cela à tout moment depuis le bouton Paramètres dans la barre supérieure.',
|
||||
'settings.kicker': 'Paramètres',
|
||||
'settings.title': 'Exécution et modèle',
|
||||
'settings.title': 'Mode d\'exécution',
|
||||
'settings.subtitle': 'Choisissez entre CLI local et BYOK. Votre clé API est stockée uniquement dans ce navigateur.',
|
||||
'settings.modeAria': 'Mode d\'exécution',
|
||||
'settings.protocolAria': 'Protocole d\'API',
|
||||
|
|
@ -121,7 +121,7 @@ export const fr: Dict = {
|
|||
'settings.apiHint': 'Les appels passent par le proxy du daemon local vers la Base URL définie. La clé est stockée uniquement dans ce navigateur et envoyée avec les requêtes au fournisseur.',
|
||||
'settings.skipForNow': 'Passer pour l\'instant',
|
||||
'settings.getStarted': 'Commencer',
|
||||
'settings.envConfigure': 'Configurer le mode d\'exécution',
|
||||
'settings.envConfigure': 'Mode d\'exécution',
|
||||
'settings.localCli': 'CLI local',
|
||||
'settings.anthropicApi': 'API Anthropic',
|
||||
'settings.noAgentSelected': 'aucun agent sélectionné',
|
||||
|
|
@ -132,6 +132,7 @@ export const fr: Dict = {
|
|||
'settings.themeSystem': 'Système',
|
||||
'settings.themeLight': 'Clair',
|
||||
'settings.themeDark': 'Sombre',
|
||||
'settings.agentModelHead': 'Modèle pour :',
|
||||
'settings.modelPicker': 'Modèle',
|
||||
'settings.reasoningPicker': 'Effort de raisonnement',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -195,6 +196,8 @@ export const fr: Dict = {
|
|||
'settings.runtimePackaged': 'Application empaquetée',
|
||||
'settings.runtimeDevelopment': 'Développement',
|
||||
'settings.versionUnavailable': 'Les informations de version sont indisponibles lorsque le daemon est hors ligne.',
|
||||
'settings.installLatest': 'Installer la dernière version',
|
||||
'settings.alreadyLatest': 'Vous utilisez la dernière version',
|
||||
|
||||
'entry.tabDesigns': 'Designs',
|
||||
'entry.tabTemplates': 'Modèles',
|
||||
|
|
@ -1267,6 +1270,7 @@ export const fr: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ Chemin copié',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const hu: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
'Válaszd ki, hogyan szeretnéd futtatni a generálásokat. Ezt bármikor módosíthatod a felső sáv Beállítások gombjával.',
|
||||
'settings.kicker': 'Beállítások',
|
||||
'settings.title': 'Végrehajtás és modell',
|
||||
'settings.title': 'Végrehajtási mód',
|
||||
'settings.subtitle': 'Válassz helyi CLI és BYOK között. Az API-kulcs csak ebben a böngészőben tárolódik.',
|
||||
'settings.modeAria': 'Végrehajtási mód',
|
||||
'settings.protocolAria': 'API protokoll',
|
||||
|
|
@ -121,7 +121,7 @@ export const hu: Dict = {
|
|||
'settings.apiHint': 'A hívások a helyi daemon proxyn keresztül mennek a beállított Base URL-re. A kulcs csak ebben a böngészőben tárolódik, és a szolgáltatói kérésekkel együtt kerül elküldésre.',
|
||||
'settings.skipForNow': 'Most kihagyom',
|
||||
'settings.getStarted': 'Kezdjük',
|
||||
'settings.envConfigure': 'Végrehajtási mód beállítása',
|
||||
'settings.envConfigure': 'Végrehajtási mód',
|
||||
'settings.localCli': 'Helyi CLI',
|
||||
'settings.anthropicApi': 'Anthropic API',
|
||||
'settings.noAgentSelected': 'nincs kiválasztott ügynök',
|
||||
|
|
@ -132,6 +132,7 @@ export const hu: Dict = {
|
|||
'settings.themeSystem': 'Rendszer',
|
||||
'settings.themeLight': 'Világos',
|
||||
'settings.themeDark': 'Sötét',
|
||||
'settings.agentModelHead': 'Modell ehhez:',
|
||||
'settings.modelPicker': 'Modell',
|
||||
'settings.reasoningPicker': 'Gondolkodási erőfeszítés',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -195,6 +196,8 @@ export const hu: Dict = {
|
|||
'settings.runtimePackaged': 'Csomagolt alkalmazás',
|
||||
'settings.runtimeDevelopment': 'Fejlesztői',
|
||||
'settings.versionUnavailable': 'A verzió adatai nem érhetők el, amíg a daemon offline.',
|
||||
'settings.installLatest': 'Legújabb verzió telepítése',
|
||||
'settings.alreadyLatest': 'Ön már a legújabb verziót használja',
|
||||
|
||||
'entry.tabDesigns': 'Tervek',
|
||||
'entry.tabTemplates': 'Sablonok',
|
||||
|
|
@ -1277,6 +1280,7 @@ export const hu: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ Útvonal másolva',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export const id: Dict = {
|
|||
'settings.welcomeTitle': 'Siapkan Open Design',
|
||||
'settings.welcomeSubtitle': 'Pilih cara menjalankan generasi. Ini bisa diubah kapan saja dari tombol Settings di bar atas.',
|
||||
'settings.kicker': 'Pengaturan',
|
||||
'settings.title': 'Eksekusi & model',
|
||||
'settings.title': 'Mode eksekusi',
|
||||
'settings.subtitle': 'Pilih antara CLI code-agent lokal dan Anthropic API (BYOK). API key hanya disimpan di browser ini.',
|
||||
'settings.modeAria': 'Mode eksekusi',
|
||||
'settings.protocolAria': 'Protokol API',
|
||||
|
|
@ -121,7 +121,7 @@ export const id: Dict = {
|
|||
'Panggilan dikirim langsung dari browser ini ke base URL yang kamu atur. Tanpa proxy. Key tidak keluar dari localStorage.',
|
||||
'settings.skipForNow': 'Lewati dulu',
|
||||
'settings.getStarted': 'Mulai',
|
||||
'settings.envConfigure': 'Atur mode eksekusi',
|
||||
'settings.envConfigure': 'Mode eksekusi',
|
||||
'settings.localCli': 'CLI lokal',
|
||||
'settings.anthropicApi': 'Anthropic API',
|
||||
'settings.noAgentSelected': 'belum ada agent dipilih',
|
||||
|
|
@ -132,6 +132,7 @@ export const id: Dict = {
|
|||
'settings.themeSystem': 'Sistem',
|
||||
'settings.themeLight': 'Terang',
|
||||
'settings.themeDark': 'Gelap',
|
||||
'settings.agentModelHead': 'Model untuk:',
|
||||
'settings.modelPicker': 'Model',
|
||||
'settings.reasoningPicker': 'Kekuatan penalaran',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -193,6 +194,8 @@ export const id: Dict = {
|
|||
'settings.runtimePackaged': 'Aplikasi paket',
|
||||
'settings.runtimeDevelopment': 'Development',
|
||||
'settings.versionUnavailable': 'Detail versi tidak tersedia saat daemon offline.',
|
||||
'settings.installLatest': 'Pasang versi terbaru',
|
||||
'settings.alreadyLatest': 'Anda sudah menggunakan versi terbaru',
|
||||
'settings.connectorsNavHint': 'Koneksi sistem eksternal',
|
||||
'settings.connectorsHint': 'Kelola pengaturan konektor dan penyedia alat untuk perangkat ini.',
|
||||
'settings.connectorsComposioApiKey': 'API key Composio',
|
||||
|
|
@ -1387,6 +1390,7 @@ export const id: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ Jalur disalin',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const ja: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
'生成の実行方法を選んでください。この設定はいつでもトップバーの設定ボタンから変更できます。',
|
||||
'settings.kicker': '設定',
|
||||
'settings.title': '実行モデル',
|
||||
'settings.title': '実行モード',
|
||||
'settings.subtitle': 'ローカル CLI と BYOK のどちらを使うか選択します。API キーはこのブラウザ内にのみ保存されます。',
|
||||
'settings.modeAria': '実行モード',
|
||||
'settings.protocolAria': 'API プロトコル',
|
||||
|
|
@ -121,7 +121,7 @@ export const ja: Dict = {
|
|||
'settings.apiHint': 'リクエストはローカル daemon プロキシ経由で設定した Base URL に送信されます。キーはこのブラウザ内にのみ保存され、プロバイダーへのリクエスト時に送信されます。',
|
||||
'settings.skipForNow': '今はスキップ',
|
||||
'settings.getStarted': '始める',
|
||||
'settings.envConfigure': '実行モードを設定',
|
||||
'settings.envConfigure': '実行モード',
|
||||
'settings.localCli': 'ローカル CLI',
|
||||
'settings.anthropicApi': 'Anthropic API',
|
||||
'settings.noAgentSelected': 'エージェント未選択',
|
||||
|
|
@ -132,6 +132,7 @@ export const ja: Dict = {
|
|||
'settings.themeSystem': 'システム',
|
||||
'settings.themeLight': 'ライト',
|
||||
'settings.themeDark': 'ダーク',
|
||||
'settings.agentModelHead': 'モデル:',
|
||||
'settings.modelPicker': 'モデル',
|
||||
'settings.reasoningPicker': '推論の強さ',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -195,6 +196,8 @@ export const ja: Dict = {
|
|||
'settings.runtimePackaged': 'パッケージ版アプリ',
|
||||
'settings.runtimeDevelopment': '開発環境',
|
||||
'settings.versionUnavailable': 'daemon がオフラインの間はバージョン詳細を取得できません。',
|
||||
'settings.installLatest': '最新版をインストール',
|
||||
'settings.alreadyLatest': '最新バージョンです',
|
||||
|
||||
'entry.tabDesigns': 'デザイン',
|
||||
'entry.tabTemplates': 'テンプレート',
|
||||
|
|
@ -1195,6 +1198,7 @@ export const ja: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ パスをコピーしました',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const ko: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
"생성을 실행할 방법을 선택하세요. 상단 바의 Settings 버튼을 통해 언제든지 변경할 수 있습니다.",
|
||||
'settings.kicker': '설정',
|
||||
'settings.title': '실행 및 모델',
|
||||
'settings.title': '실행 모드',
|
||||
'settings.subtitle': '로컬 CLI와 BYOK 중에서 선택하세요. API 키는 이 브라우저에만 저장됩니다.',
|
||||
'settings.modeAria': '실행 모드',
|
||||
'settings.protocolAria': 'API 프로토콜',
|
||||
|
|
@ -121,7 +121,7 @@ export const ko: Dict = {
|
|||
'settings.apiHint': '요청은 로컬 daemon 프록시를 통해 설정한 Base URL로 전송됩니다. 키는 이 브라우저에만 저장되며 제공자 요청과 함께 전송됩니다.',
|
||||
'settings.skipForNow': '지금은 건너뛰기',
|
||||
'settings.getStarted': '시작하기',
|
||||
'settings.envConfigure': '실행 모드 구성',
|
||||
'settings.envConfigure': '실행 모드',
|
||||
'settings.localCli': '로컬 CLI',
|
||||
'settings.anthropicApi': 'Anthropic API',
|
||||
'settings.noAgentSelected': '선택된 에이전트 없음',
|
||||
|
|
@ -132,6 +132,7 @@ export const ko: Dict = {
|
|||
'settings.themeSystem': '시스템',
|
||||
'settings.themeLight': '라이트',
|
||||
'settings.themeDark': '다크',
|
||||
'settings.agentModelHead': '모델:',
|
||||
'settings.modelPicker': '모델',
|
||||
'settings.reasoningPicker': '추론 (Reasoning)',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -195,6 +196,8 @@ export const ko: Dict = {
|
|||
'settings.runtimePackaged': '패키징된 앱',
|
||||
'settings.runtimeDevelopment': '개발 (Development)',
|
||||
'settings.versionUnavailable': '데몬이 오프라인 상태일 때는 버전 세부 정보를 확인할 수 없습니다.',
|
||||
'settings.installLatest': '최신 버전 설치',
|
||||
'settings.alreadyLatest': '최신 버전입니다',
|
||||
|
||||
'entry.tabDesigns': '디자인',
|
||||
'entry.tabTemplates': '템플릿',
|
||||
|
|
@ -1308,6 +1311,7 @@ export const ko: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ 경로 복사됨',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const pl: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
"Wybierz sposób generowania projektów. Możesz to zmienić w dowolnym momencie w Ustawieniach na górnym pasku.",
|
||||
'settings.kicker': 'Ustawienia',
|
||||
'settings.title': 'Wykonanie i model',
|
||||
'settings.title': 'Tryb wykonywania',
|
||||
'settings.subtitle': 'Wybierz lokalne CLI albo BYOK. Klucz API jest przechowywany tylko w tej przeglądarce.',
|
||||
'settings.modeAria': 'Tryb wykonywania',
|
||||
'settings.protocolAria': 'Protokół API',
|
||||
|
|
@ -121,7 +121,7 @@ export const pl: Dict = {
|
|||
'settings.apiHint': 'Wywołania są wysyłane przez lokalny proxy daemon do ustawionego Base URL. Klucz jest przechowywany tylko w tej przeglądarce i wysyłany z żądaniami do dostawcy.',
|
||||
'settings.skipForNow': 'Pomiń na razie',
|
||||
'settings.getStarted': 'Rozpocznij',
|
||||
'settings.envConfigure': 'Skonfiguruj tryb wykonywania',
|
||||
'settings.envConfigure': 'Tryb wykonywania',
|
||||
'settings.localCli': 'Lokalne CLI',
|
||||
'settings.anthropicApi': 'Anthropic API',
|
||||
'settings.noAgentSelected': 'nie wybrano agenta',
|
||||
|
|
@ -132,6 +132,7 @@ export const pl: Dict = {
|
|||
'settings.themeSystem': 'Systemowy',
|
||||
'settings.themeLight': 'Jasny',
|
||||
'settings.themeDark': 'Ciemny',
|
||||
'settings.agentModelHead': 'Model dla:',
|
||||
'settings.modelPicker': 'Model',
|
||||
'settings.reasoningPicker': 'Poziom rozumowania',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -195,6 +196,8 @@ export const pl: Dict = {
|
|||
'settings.runtimePackaged': 'Aplikacja spakowana',
|
||||
'settings.runtimeDevelopment': 'Rozwojowe',
|
||||
'settings.versionUnavailable': 'Szczegóły wersji są niedostępne, gdy daemon jest offline.',
|
||||
'settings.installLatest': 'Zainstaluj najnowszą wersję',
|
||||
'settings.alreadyLatest': 'Masz już najnowszą wersję',
|
||||
|
||||
'entry.tabDesigns': 'Projekty',
|
||||
'entry.tabTemplates': 'Szablony',
|
||||
|
|
@ -1267,6 +1270,7 @@ export const pl: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ Ścieżka skopiowana',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const ptBR: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
'Escolha como você quer executar as gerações. Você pode mudar isso a qualquer momento no botão Configurações da barra superior.',
|
||||
'settings.kicker': 'Configurações',
|
||||
'settings.title': 'Execução e modelo',
|
||||
'settings.title': 'Modo de execução',
|
||||
'settings.subtitle': 'Escolha entre CLI local e BYOK. Sua chave de API fica armazenada apenas neste navegador.',
|
||||
'settings.modeAria': 'Modo de execução',
|
||||
'settings.protocolAria': 'Protocolo de API',
|
||||
|
|
@ -121,7 +121,7 @@ export const ptBR: Dict = {
|
|||
'settings.apiHint': 'As chamadas passam pelo proxy do daemon local até a Base URL definida. A chave fica armazenada apenas neste navegador e é enviada com as requisições ao provedor.',
|
||||
'settings.skipForNow': 'Pular por enquanto',
|
||||
'settings.getStarted': 'Começar',
|
||||
'settings.envConfigure': 'Configurar modo de execução',
|
||||
'settings.envConfigure': 'Modo de execução',
|
||||
'settings.localCli': 'CLI local',
|
||||
'settings.anthropicApi': 'API da Anthropic',
|
||||
'settings.noAgentSelected': 'nenhum agente selecionado',
|
||||
|
|
@ -132,6 +132,7 @@ export const ptBR: Dict = {
|
|||
'settings.themeSystem': 'Sistema',
|
||||
'settings.themeLight': 'Claro',
|
||||
'settings.themeDark': 'Escuro',
|
||||
'settings.agentModelHead': 'Modelo para:',
|
||||
'settings.modelPicker': 'Modelo',
|
||||
'settings.reasoningPicker': 'Esforço de raciocínio',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -194,6 +195,8 @@ export const ptBR: Dict = {
|
|||
'settings.runtimePackaged': 'App empacotado',
|
||||
'settings.runtimeDevelopment': 'Desenvolvimento',
|
||||
'settings.versionUnavailable': 'Os detalhes de versão ficam indisponíveis enquanto o daemon está offline.',
|
||||
'settings.installLatest': 'Instalar a versão mais recente',
|
||||
'settings.alreadyLatest': 'Você já está na versão mais recente',
|
||||
|
||||
'entry.tabDesigns': 'Designs',
|
||||
'entry.tabTemplates': 'Modelos',
|
||||
|
|
@ -1308,6 +1311,7 @@ export const ptBR: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ Caminho copiado',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const ru: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
'Выберите, как запускать генерации. Вы можете изменить это в любое время через кнопку Настройки в верхней панели.',
|
||||
'settings.kicker': 'Настройки',
|
||||
'settings.title': 'Выполнение и модель',
|
||||
'settings.title': 'Режим выполнения',
|
||||
'settings.subtitle': 'Выберите локальный CLI или BYOK. Ваш API-ключ хранится только в этом браузере.',
|
||||
'settings.modeAria': 'Режим выполнения',
|
||||
'settings.protocolAria': 'Протокол API',
|
||||
|
|
@ -121,7 +121,7 @@ export const ru: Dict = {
|
|||
'settings.apiHint': 'Запросы отправляются через локальный прокси daemon на указанную Base URL. Ключ хранится только в этом браузере и отправляется в запросах к провайдеру.',
|
||||
'settings.skipForNow': 'Пропустить сейчас',
|
||||
'settings.getStarted': 'Начать',
|
||||
'settings.envConfigure': 'Настроить режим выполнения',
|
||||
'settings.envConfigure': 'Режим выполнения',
|
||||
'settings.localCli': 'Локальный CLI',
|
||||
'settings.anthropicApi': 'Anthropic API',
|
||||
'settings.noAgentSelected': 'агент не выбран',
|
||||
|
|
@ -132,6 +132,7 @@ export const ru: Dict = {
|
|||
'settings.themeSystem': 'Системная',
|
||||
'settings.themeLight': 'Светлая',
|
||||
'settings.themeDark': 'Тёмная',
|
||||
'settings.agentModelHead': 'Модель для:',
|
||||
'settings.modelPicker': 'Модель',
|
||||
'settings.reasoningPicker': 'Сложность рассуждений',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -194,6 +195,8 @@ export const ru: Dict = {
|
|||
'settings.runtimePackaged': 'Упакованное приложение',
|
||||
'settings.runtimeDevelopment': 'Разработка',
|
||||
'settings.versionUnavailable': 'Сведения о версии недоступны, пока daemon не запущен.',
|
||||
'settings.installLatest': 'Установить последнюю версию',
|
||||
'settings.alreadyLatest': 'У вас установлена последняя версия',
|
||||
|
||||
'entry.tabDesigns': 'Дизайны',
|
||||
'entry.tabTemplates': 'Шаблоны',
|
||||
|
|
@ -1308,6 +1311,7 @@ export const ru: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ Путь скопирован',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export const th: Dict = {
|
|||
'settings.welcomeTitle': 'ตั้งค่า Open Design',
|
||||
'settings.welcomeSubtitle': "เลือกวิธีที่คุณต้องการให้ระบบสร้างผลงาน คุณสามารถเปลี่ยนค่านี้ได้ตลอดเวลาจากปุ่มการตั้งค่าที่แถบด้านบน",
|
||||
'settings.kicker': 'การตั้งค่า',
|
||||
'settings.title': 'การรันและโมเดล',
|
||||
'settings.title': 'โหมดการรัน',
|
||||
'settings.subtitle': 'เลือกระหว่าง Local CLI และ BYOK (ใช้ API Key ของคุณเอง) โดย API Key จะถูกเก็บไว้ในเบราว์เซอร์นี้เท่านั้น',
|
||||
'settings.modeAria': 'โหมดการทำงาน',
|
||||
'settings.protocolAria': 'โปรโตคอล API',
|
||||
|
|
@ -115,7 +115,7 @@ export const th: Dict = {
|
|||
'settings.apiHint': 'คำสั่งจะถูกส่งผ่าน local daemon proxy ไปยัง base URL ที่คุณตั้งไว้ API Key จะถูกเก็บในเบราว์เซอร์นี้เท่านั้น',
|
||||
'settings.skipForNow': 'ข้ามไปก่อน',
|
||||
'settings.getStarted': 'เริ่มต้นใช้งาน',
|
||||
'settings.envConfigure': 'กำหนดค่าโหมดการทำงาน',
|
||||
'settings.envConfigure': 'โหมดการรัน',
|
||||
'settings.localCli': 'Local CLI',
|
||||
'settings.anthropicApi': 'Anthropic API',
|
||||
'settings.noAgentSelected': 'ไม่ได้เลือกเอเจนต์',
|
||||
|
|
@ -126,6 +126,7 @@ export const th: Dict = {
|
|||
'settings.themeSystem': 'ระบบ',
|
||||
'settings.themeLight': 'สว่าง',
|
||||
'settings.themeDark': 'มืด',
|
||||
'settings.agentModelHead': 'โมเดลสำหรับ:',
|
||||
'settings.modelPicker': 'โมเดล',
|
||||
'settings.reasoningPicker': 'ความพยายามในการให้เหตุผล',
|
||||
'settings.modelPickerHint': 'ดึงข้อมูลจาก CLI เมื่อมีคำสั่ง `models`',
|
||||
|
|
@ -178,6 +179,8 @@ export const th: Dict = {
|
|||
'settings.runtimePackaged': 'แอปที่แพ็กเกจแล้ว',
|
||||
'settings.runtimeDevelopment': 'การพัฒนา',
|
||||
'settings.versionUnavailable': 'ข้อมูลเวอร์ชันไม่พร้อมใช้งานขณะที่ daemon ออฟไลน์',
|
||||
'settings.installLatest': 'ติดตั้งเวอร์ชันล่าสุด',
|
||||
'settings.alreadyLatest': 'คุณใช้เวอร์ชันล่าสุดอยู่แล้ว',
|
||||
|
||||
'entry.tabDesigns': 'ดีไซน์',
|
||||
'entry.tabTemplates': 'ตัวอย่าง',
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const tr: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
"Oluşturmaları nasıl çalıştıracağınızı seçin. Bunu her zaman üst çubuktaki Ayarlar sekmesinden değiştirebilirsiniz.",
|
||||
'settings.kicker': 'Ayarlar',
|
||||
'settings.title': 'Çalıştırma & model',
|
||||
'settings.title': 'Yürütme modu',
|
||||
'settings.subtitle': 'Yerel CLI ile BYOK arasında seçim yapın. API anahtarınız yalnızca bu tarayıcıda saklanır.',
|
||||
'settings.modeAria': 'Çalıştırma modu',
|
||||
'settings.protocolAria': 'API protokolü',
|
||||
|
|
@ -121,7 +121,7 @@ export const tr: Dict = {
|
|||
'settings.apiHint': 'İstekler yerel daemon proxy üzerinden ayarladığınız Base URLye gönderilir. Anahtar yalnızca bu tarayıcıda saklanır ve sağlayıcı istekleriyle birlikte gönderilir.',
|
||||
'settings.skipForNow': 'Şimdilik atla',
|
||||
'settings.getStarted': 'Başla',
|
||||
'settings.envConfigure': 'Yürütme modunu ayarlayın',
|
||||
'settings.envConfigure': 'Yürütme modu',
|
||||
'settings.localCli': 'Yerel CLI',
|
||||
'settings.anthropicApi': 'Anthropic API',
|
||||
'settings.noAgentSelected': 'Ajan seçilmedi',
|
||||
|
|
@ -132,6 +132,7 @@ export const tr: Dict = {
|
|||
'settings.themeSystem': 'Sistem',
|
||||
'settings.themeLight': 'Açık',
|
||||
'settings.themeDark': 'Koyu',
|
||||
'settings.agentModelHead': 'Model için:',
|
||||
'settings.modelPicker': 'Model',
|
||||
'settings.reasoningPicker': 'Akıl yürütme eforu',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -185,6 +186,8 @@ export const tr: Dict = {
|
|||
'settings.runtimePackaged': 'Paketlenmiş uygulama',
|
||||
'settings.runtimeDevelopment': 'Geliştirme',
|
||||
'settings.versionUnavailable': 'Arka plan servisi devre dışıyken sürüm detayları mevcut değildir.',
|
||||
'settings.installLatest': 'En son sürümü yükle',
|
||||
'settings.alreadyLatest': 'En son sürüme sahipsiniz',
|
||||
|
||||
'entry.tabDesigns': 'Tasarımlar',
|
||||
'entry.tabTemplates': 'Şablonlar',
|
||||
|
|
@ -1254,6 +1257,7 @@ export const tr: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ Yol kopyalandı',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const uk: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
'Виберіть, як ви хочете запускати генерацію. Ви можете змінити це в будь-який час за допомогою кнопки Налаштування в верхній панелі.',
|
||||
'settings.kicker': 'Налаштування',
|
||||
'settings.title': 'Виконання та модель',
|
||||
'settings.title': 'Режим виконання',
|
||||
'settings.subtitle': 'Виберіть локальний CLI або BYOK. Ваш API-ключ зберігається лише в цьому браузері.',
|
||||
'settings.modeAria': 'Режим виконання',
|
||||
'settings.protocolAria': 'Протокол API',
|
||||
|
|
@ -122,7 +122,7 @@ export const uk: Dict = {
|
|||
'settings.apiHint': 'Запити надсилаються через локальний проксі daemon до вказаного Base URL. Ключ зберігається лише в цьому браузері й надсилається із запитами до провайдера.',
|
||||
'settings.skipForNow': 'Пропустити зараз',
|
||||
'settings.getStarted': 'Почати',
|
||||
'settings.envConfigure': 'Налаштування режиму виконання',
|
||||
'settings.envConfigure': 'Режим виконання',
|
||||
'settings.localCli': 'Локальний CLI',
|
||||
'settings.anthropicApi': 'Anthropic API',
|
||||
'settings.noAgentSelected': 'агент не вибран',
|
||||
|
|
@ -133,6 +133,7 @@ export const uk: Dict = {
|
|||
'settings.themeSystem': 'Системна',
|
||||
'settings.themeLight': 'Світла',
|
||||
'settings.themeDark': 'Темна',
|
||||
'settings.agentModelHead': 'Модель для:',
|
||||
'settings.modelPicker': 'Модель',
|
||||
'settings.reasoningPicker': 'Інтенсивність міркувань',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -196,6 +197,8 @@ export const uk: Dict = {
|
|||
'settings.runtimePackaged': 'Упакований додаток',
|
||||
'settings.runtimeDevelopment': 'Розробка',
|
||||
'settings.versionUnavailable': 'Деталі версії недоступні, поки фоновий процес перебуває в офлайні.',
|
||||
'settings.installLatest': 'Встановити останню версію',
|
||||
'settings.alreadyLatest': 'У вас встановлена остання версія',
|
||||
|
||||
'entry.tabDesigns': 'Дизайни',
|
||||
'entry.tabTemplates': 'Шаблони',
|
||||
|
|
@ -1309,6 +1312,7 @@ export const uk: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ Memory saved',
|
||||
'settings.memoryFlashDeleted': '✓ Memory deleted',
|
||||
'settings.memoryFlashIndexSaved': '✓ Index saved',
|
||||
'settings.memoryFlashPathCopied': '✓ Шлях скопійовано',
|
||||
'settings.memoryNameLabel': 'Title',
|
||||
'settings.memoryTypeLabel': 'Type',
|
||||
'settings.memoryDescLabel': 'Description',
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export const zhCN: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
'选择你希望使用的执行方式。后续可以随时从顶部「设置」按钮中修改。',
|
||||
'settings.kicker': '设置',
|
||||
'settings.title': '执行模式与模型',
|
||||
'settings.title': '执行模式',
|
||||
'settings.subtitle': '在本机 CLI 与 BYOK 之间选择。API Key 只保存在当前浏览器中。',
|
||||
'settings.modeAria': '执行模式',
|
||||
'settings.protocolAria': 'API 协议',
|
||||
|
|
@ -127,7 +127,7 @@ export const zhCN: Dict = {
|
|||
'settings.apiHint': '请求会通过本机 daemon 代理发送到你设置的 Base URL。Key 只保存在当前浏览器中,并随提供方请求发送。',
|
||||
'settings.skipForNow': '暂时跳过',
|
||||
'settings.getStarted': '开始使用',
|
||||
'settings.envConfigure': '配置执行模式',
|
||||
'settings.envConfigure': '执行模式',
|
||||
'settings.localCli': '本机 CLI',
|
||||
'settings.anthropicApi': 'Anthropic API',
|
||||
'settings.noAgentSelected': '尚未选择代理',
|
||||
|
|
@ -138,6 +138,7 @@ export const zhCN: Dict = {
|
|||
'settings.themeSystem': '系统',
|
||||
'settings.themeLight': '浅色',
|
||||
'settings.themeDark': '深色',
|
||||
'settings.agentModelHead': '模型:',
|
||||
'settings.modelPicker': '模型',
|
||||
'settings.reasoningPicker': '推理强度',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -200,6 +201,8 @@ export const zhCN: Dict = {
|
|||
'settings.runtimePackaged': '已打包应用',
|
||||
'settings.runtimeDevelopment': '开发环境',
|
||||
'settings.versionUnavailable': '守护进程离线时无法获取版本详情。',
|
||||
'settings.installLatest': '安装最新版本',
|
||||
'settings.alreadyLatest': '当前为最新版本',
|
||||
|
||||
// MCP server settings
|
||||
'settings.mcpTitle': 'MCP server',
|
||||
|
|
@ -1447,6 +1450,7 @@ export const zhCN: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ 已保存',
|
||||
'settings.memoryFlashDeleted': '✓ 已删除',
|
||||
'settings.memoryFlashIndexSaved': '✓ 索引已保存',
|
||||
'settings.memoryFlashPathCopied': '✓ 路径已复制',
|
||||
'settings.memoryNameLabel': '标题',
|
||||
'settings.memoryTypeLabel': '类型',
|
||||
'settings.memoryDescLabel': '描述',
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const zhTW: Dict = {
|
|||
'settings.welcomeSubtitle':
|
||||
'選擇你希望使用的執行方式。後續可以隨時從頂端「設定」按鈕中修改。',
|
||||
'settings.kicker': '設定',
|
||||
'settings.title': '執行模式與模型',
|
||||
'settings.title': '執行模式',
|
||||
'settings.subtitle': '在本機 CLI 與 BYOK 之間選擇。API Key 只儲存在目前瀏覽器中。',
|
||||
'settings.modeAria': '執行模式',
|
||||
'settings.protocolAria': 'API 協定',
|
||||
|
|
@ -120,7 +120,7 @@ export const zhTW: Dict = {
|
|||
'settings.apiHint': '請求會透過本機 daemon 代理送到你設定的 Base URL。Key 只儲存在目前瀏覽器中,並隨提供方請求送出。',
|
||||
'settings.skipForNow': '暫時跳過',
|
||||
'settings.getStarted': '開始使用',
|
||||
'settings.envConfigure': '設定執行模式',
|
||||
'settings.envConfigure': '執行模式',
|
||||
'settings.localCli': '本機 CLI',
|
||||
'settings.anthropicApi': 'Anthropic API',
|
||||
'settings.noAgentSelected': '尚未選擇代理',
|
||||
|
|
@ -131,6 +131,7 @@ export const zhTW: Dict = {
|
|||
'settings.themeSystem': '系統',
|
||||
'settings.themeLight': '淺色',
|
||||
'settings.themeDark': '深色',
|
||||
'settings.agentModelHead': '模型:',
|
||||
'settings.modelPicker': '模型',
|
||||
'settings.reasoningPicker': '推理強度',
|
||||
'settings.modelPickerHint':
|
||||
|
|
@ -193,6 +194,8 @@ export const zhTW: Dict = {
|
|||
'settings.runtimePackaged': '已封裝應用程式',
|
||||
'settings.runtimeDevelopment': '開發環境',
|
||||
'settings.versionUnavailable': '守護程式離線時無法取得版本詳情。',
|
||||
'settings.installLatest': '安裝最新版本',
|
||||
'settings.alreadyLatest': '目前已是最新版本',
|
||||
|
||||
// MCP server settings
|
||||
'settings.mcpTitle': 'MCP server',
|
||||
|
|
@ -1397,6 +1400,7 @@ export const zhTW: Dict = {
|
|||
'settings.memoryFlashSaved': '✓ 已儲存',
|
||||
'settings.memoryFlashDeleted': '✓ 已刪除',
|
||||
'settings.memoryFlashIndexSaved': '✓ 索引已儲存',
|
||||
'settings.memoryFlashPathCopied': '✓ 路徑已複製',
|
||||
'settings.memoryNameLabel': '標題',
|
||||
'settings.memoryTypeLabel': '類型',
|
||||
'settings.memoryDescLabel': '描述',
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ export interface Dict {
|
|||
'settings.themeSystem': string;
|
||||
'settings.themeLight': string;
|
||||
'settings.themeDark': string;
|
||||
'settings.agentModelHead': string;
|
||||
'settings.modelPicker': string;
|
||||
'settings.reasoningPicker': string;
|
||||
'settings.modelPickerHint': string;
|
||||
|
|
@ -221,6 +222,8 @@ export interface Dict {
|
|||
'settings.runtimePackaged': string;
|
||||
'settings.runtimeDevelopment': string;
|
||||
'settings.versionUnavailable': string;
|
||||
'settings.installLatest': string;
|
||||
'settings.alreadyLatest': string;
|
||||
'settings.skills': string;
|
||||
'settings.skillsHint': string;
|
||||
'settings.skillsNew': string;
|
||||
|
|
@ -391,6 +394,7 @@ export interface Dict {
|
|||
'settings.memoryFlashSaved': string;
|
||||
'settings.memoryFlashDeleted': string;
|
||||
'settings.memoryFlashIndexSaved': string;
|
||||
'settings.memoryFlashPathCopied': string;
|
||||
'settings.memoryNameLabel': string;
|
||||
'settings.memoryTypeLabel': string;
|
||||
'settings.memoryDescLabel': string;
|
||||
|
|
|
|||
|
|
@ -227,6 +227,44 @@ button.ghost {
|
|||
color: var(--text);
|
||||
}
|
||||
button.ghost:hover:not(:disabled) { background: var(--bg-subtle); border-color: var(--border-strong); }
|
||||
/*
|
||||
* Transient success state for ghost buttons. Used by the Media
|
||||
* Providers "Reload from daemon" button after a successful reload —
|
||||
* the button briefly turns green with a check icon for ~2s then snaps
|
||||
* back to its idle treatment, replacing what used to be a permanent
|
||||
* success paragraph under the section header. Reusable on any
|
||||
* ghost button that wants the same "did it" pulse.
|
||||
*/
|
||||
button.ghost.is-success-flash {
|
||||
border-color: var(--green-border, color-mix(in srgb, #1f9d55 32%, var(--border)));
|
||||
background: var(--green-bg, color-mix(in srgb, #1f9d55 8%, transparent));
|
||||
color: var(--green, #137a3d);
|
||||
}
|
||||
button.ghost.is-success-flash:hover:not(:disabled) {
|
||||
background: var(--green-bg, color-mix(in srgb, #1f9d55 14%, transparent));
|
||||
}
|
||||
button.ghost.is-success-flash svg {
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
/*
|
||||
* Visually-hidden but assistive-tech accessible. We use it to keep
|
||||
* a `role="status"` live-region for actions whose visible feedback
|
||||
* is too transient (e.g. a 2s button-label flash) for a screen
|
||||
* reader to catch reliably. Borrowed from Tailwind's `sr-only` so
|
||||
* it reads as the same primitive everyone already knows.
|
||||
*/
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
button.subtle {
|
||||
background: var(--bg-subtle);
|
||||
|
|
@ -1998,6 +2036,31 @@ code {
|
|||
padding-right: calc(var(--modal-padding) + 56px);
|
||||
}
|
||||
|
||||
/* Compact settings header: place subtitle on the same line as the h2
|
||||
so opening the dialog doesn't burn 2-3 rows of header height. The
|
||||
row uses baseline alignment so the smaller subtitle sits at the
|
||||
h2 baseline and wraps to its own line on narrow viewports. The
|
||||
welcome hero variant is unaffected — it keeps the stacked layout. */
|
||||
.modal-settings .modal-head-line {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 16px;
|
||||
row-gap: 4px;
|
||||
}
|
||||
.modal-settings .modal-head-line > h2 {
|
||||
flex: 0 0 auto;
|
||||
margin: 0;
|
||||
}
|
||||
.modal-settings .modal-head-line > .subtitle {
|
||||
margin: 0;
|
||||
flex: 1 1 240px;
|
||||
font-size: 12.5px;
|
||||
/* Override the 72ch cap from the stacked variant so the subtitle
|
||||
can take the full remaining inline width before wrapping. */
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
/* Section-local Save key button for the Composio API key field. We do
|
||||
NOT autosave secrets, so this is the explicit gesture. Styled as a
|
||||
primary button to stand out next to the password input + ghost
|
||||
|
|
@ -2357,6 +2420,7 @@ code {
|
|||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 4px 12px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
|
|
@ -2383,6 +2447,17 @@ code {
|
|||
.seg-btn.active .seg-title { font-weight: 600; }
|
||||
.seg-btn .seg-meta { display: none; }
|
||||
.seg-btn:disabled { opacity: 0.55; cursor: not-allowed; }
|
||||
/* Inline variant: title and meta sit on a single row instead of
|
||||
stacking, which saves a row of vertical space inside the
|
||||
execution-mode tabs (Local CLI · 2 installed / BYOK · API provider). */
|
||||
.seg-btn--inline {
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
padding-block: 8px;
|
||||
}
|
||||
.seg-btn--inline > .seg-title { min-width: 0; flex: 0 0 auto; }
|
||||
.seg-btn--inline > .seg-meta { min-width: 0; flex: 1 1 auto; }
|
||||
|
||||
/* Secondary protocol selector — pill chips, wraps to multiple rows */
|
||||
.protocol-chips {
|
||||
|
|
@ -2501,6 +2576,12 @@ code {
|
|||
background: var(--accent-tint);
|
||||
color: var(--accent-strong);
|
||||
}
|
||||
.section-head-actions .seg-control {
|
||||
display: inline-grid;
|
||||
grid-template-columns: repeat(var(--seg-cols, 2), auto);
|
||||
min-height: unset;
|
||||
min-width: unset;
|
||||
}
|
||||
.section-head-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -3290,6 +3371,7 @@ code {
|
|||
border-radius: 50%;
|
||||
border: 1px solid color-mix(in srgb, var(--accent) 18%, transparent);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
@keyframes orbitConfigGateIn {
|
||||
from { opacity: 0; transform: translateY(4px); }
|
||||
|
|
@ -3369,6 +3451,8 @@ code {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.orbit-config-gate-action {
|
||||
display: inline-flex;
|
||||
|
|
@ -3617,28 +3701,147 @@ code {
|
|||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.mcp-client-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* Group 1: what the MCP server does */
|
||||
.mcp-capabilities-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 12px 14px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
line-height: 1.55;
|
||||
}
|
||||
.mcp-capabilities-label {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.mcp-capabilities-list {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
font-size: 13px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* Group 2: setup flow */
|
||||
.mcp-setup-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.mcp-setup-card .ds-picker {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.mcp-running-note {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.settings-about-list > div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.settings-about-version-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.settings-about-list dt {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.settings-about-list dd {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.settings-notify-card {
|
||||
padding: 12px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.settings-notify-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
.settings-notify-card-header h4 {
|
||||
margin: 0;
|
||||
}
|
||||
.settings-notify-card-hint {
|
||||
margin: 16px 0 0;
|
||||
}
|
||||
.settings-about-version-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.settings-about-version-num {
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.settings-about-download-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 9px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.settings-about-download-link:hover:not(:disabled) {
|
||||
background: var(--bg-subtle);
|
||||
color: var(--text);
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
.settings-about-download-link:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.media-provider-reload-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.media-provider-reload-btn {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
padding: 4px 8px;
|
||||
gap: 4px;
|
||||
}
|
||||
.media-provider-reload-btn:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
.media-provider-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -3666,6 +3869,13 @@ code {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
.media-provider-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.media-provider-name {
|
||||
font-size: 13px;
|
||||
|
|
@ -3710,6 +3920,48 @@ code {
|
|||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
|
||||
gap: 6px;
|
||||
}
|
||||
/*
|
||||
* "Coming soon" drawer under the media provider list. We render the
|
||||
* roadmap providers as a denser, read-only list rather than the same
|
||||
* editable cards as the main list — the user can't actually configure
|
||||
* these, so giving them the same visual weight is misleading. The
|
||||
* <details>/<summary> is styled by the shared .memory-details-summary
|
||||
* rules; only the inner list needs new styles.
|
||||
*/
|
||||
.media-provider-coming-soon {
|
||||
margin-top: 12px;
|
||||
padding: 10px 12px;
|
||||
background: var(--bg-subtle);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.media-provider-coming-soon[open] {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.media-provider-coming-soon-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.media-provider-coming-soon-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 8px 0;
|
||||
border-top: 1px solid var(--border-soft, var(--border));
|
||||
}
|
||||
.media-provider-coming-soon-item:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
.media-provider-coming-soon-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
.media-provider-secret-field {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
|
|
@ -3754,6 +4006,19 @@ code {
|
|||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
/*
|
||||
Section-head action buttons (Reload, Test, Rescan, Fetch models, …)
|
||||
must read on a single line. Without an explicit nowrap they wrap when
|
||||
the section title + hint take most of the row, producing label
|
||||
fragments like "Reload from / daemon" that look broken. flex-shrink:0
|
||||
keeps the button at its content width even when the description gets
|
||||
long; long button labels are then guarded by their own copy budget.
|
||||
*/
|
||||
.section-head > button,
|
||||
.section-head .section-head-actions > button {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.section-head h3 { margin: 0; font-size: 13px; font-weight: 600; letter-spacing: 0.01em; }
|
||||
.section-head .hint { margin-top: 2px; }
|
||||
.field { display: flex; flex-direction: column; gap: 4px; }
|
||||
|
|
@ -3832,6 +4097,22 @@ code {
|
|||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
/* Test feedback row injected into the agent grid right after the
|
||||
tested/selected card. Spans both columns so the result reads as a
|
||||
continuation of that card's row, instead of a top-of-section banner
|
||||
that pushes the grid down on every Test/Rescan click. */
|
||||
.agent-test-result-row {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.agent-test-result-row > .settings-test-status {
|
||||
margin: 0;
|
||||
}
|
||||
.agent-test-result-row > .settings-test-actions {
|
||||
margin-top: 0;
|
||||
}
|
||||
.agent-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -3853,17 +4134,14 @@ code {
|
|||
}
|
||||
.agent-card:hover:not(.disabled) {
|
||||
border-color: var(--border-strong);
|
||||
background: var(--bg-subtle);
|
||||
}
|
||||
.agent-card.active {
|
||||
border-color: var(--border-strong);
|
||||
background: var(--bg-subtle);
|
||||
box-shadow: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 1px var(--accent);
|
||||
}
|
||||
.agent-card.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
background: var(--bg-subtle);
|
||||
}
|
||||
.agent-card.disabled.agent-card-unavailable {
|
||||
opacity: 0.72;
|
||||
|
|
@ -3874,6 +4152,16 @@ code {
|
|||
gap: 6px 10px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
/* Inline variant: links sit to the right of the agent name as a sibling
|
||||
of .agent-card-body, so unavailable cards collapse to a single row
|
||||
(no "not installed" label, no version meta) and the long tail of
|
||||
unavailable adapters shrinks. */
|
||||
.agent-card-actions--inline {
|
||||
margin-top: 0;
|
||||
flex: 0 0 auto;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
}
|
||||
.agent-card-link {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
|
|
@ -3886,6 +4174,34 @@ code {
|
|||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
/* Docs link: secondary affordance, plain muted text. Sits to the left
|
||||
of the Install ghost button so the primary "do this" action keeps the
|
||||
right edge. Hover toggles the underline only — no background, no
|
||||
color change — to keep visual weight low. */
|
||||
.agent-card-link--muted {
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
.agent-card-link--muted:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
/* Install link: primary affordance for an unavailable card, styled as
|
||||
a ghost button (transparent + outlined) so it reads as a real
|
||||
actionable target rather than yet another text link. */
|
||||
.agent-card-link--ghost {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 3px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
}
|
||||
.agent-card-link--ghost:hover {
|
||||
background: var(--bg-subtle);
|
||||
border-color: var(--border-strong);
|
||||
text-decoration: none;
|
||||
}
|
||||
.agent-install-path-hint {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 0;
|
||||
|
|
@ -3915,6 +4231,13 @@ code {
|
|||
line-height: 1.35;
|
||||
}
|
||||
.agent-card-meta .muted { color: var(--text-soft); font-style: italic; }
|
||||
.agent-model-row-head {
|
||||
font-size: 12px;
|
||||
color: var(--text);
|
||||
}
|
||||
.agent-model-row-head strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
.agent-model-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -3922,7 +4245,28 @@ code {
|
|||
padding: 12px;
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-subtle);
|
||||
background: var(--bg-panel);
|
||||
}
|
||||
.agent-model-row select,
|
||||
.agent-model-row input,
|
||||
.agent-cli-env select,
|
||||
.agent-cli-env input,
|
||||
.settings-section-byok select,
|
||||
.settings-section-byok input {
|
||||
background-color: var(--bg-subtle);
|
||||
}
|
||||
/*
|
||||
Generic Settings card — wraps a single panel of related controls in a
|
||||
white-on-soft-border container so users get an obvious "this lump of
|
||||
fields belongs together" boundary instead of a long flat scroll. Used
|
||||
by the BYOK form, the Custom instructions block, the Memory list, and
|
||||
any future Settings section that should read as a card.
|
||||
*/
|
||||
.settings-section-card {
|
||||
padding: 16px;
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-panel);
|
||||
}
|
||||
.agent-model-row .field { gap: 4px; }
|
||||
.agent-model-row .field-label {
|
||||
|
|
@ -3933,26 +4277,50 @@ code {
|
|||
}
|
||||
.agent-model-row .hint { margin: 0; font-size: 11.5px; }
|
||||
.agent-cli-env {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-panel);
|
||||
}
|
||||
.agent-cli-env-head {
|
||||
.agent-cli-env-summary {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
user-select: none;
|
||||
}
|
||||
.agent-cli-env-head h4 {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
.agent-cli-env-summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
.agent-cli-env-summary::before {
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid currentColor;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
color: var(--text-muted);
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
.agent-cli-env[open] > .agent-cli-env-summary::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.agent-cli-env-summary-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
.agent-cli-env-head .hint { margin: 0; font-size: 11.5px; }
|
||||
.agent-cli-env-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.agent-cli-env-body .hint {
|
||||
margin: 0;
|
||||
font-size: 11.5px;
|
||||
}
|
||||
.agent-cli-env-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
|
|
@ -6261,6 +6629,23 @@ button.connector-action.is-loading {
|
|||
blowing the dialog out vertically. */
|
||||
.tab-panel.connectors-panel.connectors-panel-embedded {
|
||||
gap: 14px;
|
||||
position: relative;
|
||||
}
|
||||
/* Toast anchor: positions the connector error toast at the center-top
|
||||
of the settings panel rather than at the bottom of the viewport. */
|
||||
.connectors-toast-anchor {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
width: max-content;
|
||||
}
|
||||
.connectors-toast-anchor .od-toast {
|
||||
position: static;
|
||||
transform: none;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.connectors-panel-embedded .tab-panel-toolbar {
|
||||
justify-content: flex-end;
|
||||
|
|
@ -6296,6 +6681,10 @@ button.connector-action.is-loading {
|
|||
border: 1px solid var(--border);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
/* Hide the provider tabs when there is only one option — no choice = no need for a tab. */
|
||||
.connectors-provider-tabs:has(> :only-child) {
|
||||
display: none;
|
||||
}
|
||||
.connectors-provider-tab {
|
||||
appearance: none;
|
||||
border: 0;
|
||||
|
|
@ -11391,6 +11780,19 @@ button.ghost.mcp-copy-btn:hover:not(:disabled) {
|
|||
border: 1px solid color-mix(in srgb, #1f9d55 28%, var(--border));
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
/*
|
||||
* Inline variant lives next to a label on the same row instead of taking
|
||||
* its own line. We also drop the green "success" treatment because the
|
||||
* media-provider row already has a green "Integrated" badge — two green
|
||||
* pills in one row read as duplicate state. Neutral muted gray keeps it
|
||||
* legible as metadata without competing for attention.
|
||||
*/
|
||||
.field-status-badge--inline {
|
||||
color: var(--text-muted);
|
||||
background: var(--bg-subtle);
|
||||
border-color: var(--border);
|
||||
font-weight: 500;
|
||||
}
|
||||
/* ---------- Composio API key skeleton ----------
|
||||
The Composio config is daemon-backed, so on first paint after a
|
||||
restart there is a window where the section renders empty even
|
||||
|
|
@ -12663,21 +13065,25 @@ button.ghost.mcp-copy-btn:hover:not(:disabled) {
|
|||
align-items: center;
|
||||
}
|
||||
.filter-pill:hover { border-color: var(--border-strong); color: var(--text); }
|
||||
/*
|
||||
Selected filter pill — quiet "I am the chosen one" treatment, NOT a CTA.
|
||||
Earlier the active pill borrowed the full `--accent` fill, which read as
|
||||
a primary button competing with the real action ("+ New memory") next to
|
||||
it. Users would mis-read the count (e.g. "All 0") as a clickable big
|
||||
orange button. We now keep the selection bordered + slightly weighted so
|
||||
it still looks "on" but stops shouting; the lone primary button on the
|
||||
right keeps its monopoly on the accent color.
|
||||
*/
|
||||
.filter-pill.active {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: white;
|
||||
background: var(--bg-subtle);
|
||||
border-color: var(--border-strong);
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
}
|
||||
/* The global `button:hover:not(:disabled)` rule (specificity 0,2,1) is more
|
||||
specific than `.filter-pill.active` (0,2,0), so without an explicit
|
||||
active-hover rule the active pill loses its accent background on hover and
|
||||
the white label drops onto a cream `--bg-subtle` background — invisible.
|
||||
This rule (0,3,0) wins and keeps the accent treatment, swapping in
|
||||
`--accent-hover` for a subtle hover shift. */
|
||||
.filter-pill.active:hover {
|
||||
background: var(--accent-hover);
|
||||
border-color: var(--accent-hover);
|
||||
color: white;
|
||||
background: var(--bg-muted);
|
||||
border-color: var(--border-strong);
|
||||
color: var(--text);
|
||||
}
|
||||
.filter-pill-count {
|
||||
font-size: 11px;
|
||||
|
|
@ -14503,8 +14909,14 @@ body.entry-resizing { cursor: col-resize; user-select: none; }
|
|||
gap: 6px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.pet-tabs .subtab-pill {
|
||||
align-self: flex-start;
|
||||
.pet-tabs-top-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
.pet-tabs-top-row .subtab-pill {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.pet-tabs-hint {
|
||||
margin: 0;
|
||||
|
|
@ -15648,8 +16060,9 @@ body.entry-resizing { cursor: col-resize; user-select: none; }
|
|||
}
|
||||
|
||||
.pet-codex-card {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 56px 1fr auto;
|
||||
grid-template-columns: 56px 1fr;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
|
|
@ -15742,6 +16155,7 @@ body.entry-resizing { cursor: col-resize; user-select: none; }
|
|||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.pet-codex-meta strong {
|
||||
|
|
@ -15762,6 +16176,26 @@ body.entry-resizing { cursor: col-resize; user-select: none; }
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Adopt button: absolutely positioned at card's right edge so the
|
||||
meta text always fills the full remaining width. Hidden until hover;
|
||||
adopted (active) icon-only check stays visible at all times. */
|
||||
.pet-codex-adopt-btn {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.pet-codex-adopt-btn:not(.active) {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 120ms ease;
|
||||
}
|
||||
.pet-codex-card:hover .pet-codex-adopt-btn:not(.active),
|
||||
.pet-codex-card:focus-within .pet-codex-adopt-btn:not(.active) {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Slash-command popover in the chat composer */
|
||||
|
||||
.slash-popover {
|
||||
|
|
@ -16023,6 +16457,22 @@ body.entry-resizing { cursor: col-resize; user-select: none; }
|
|||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
/* Skills toolbar: two-row layout.
|
||||
Row 1: search (grows) + New skill button (right, 12px gap).
|
||||
Row 2: filter dropdowns with inline labels. */
|
||||
.library-toolbar.skills-toolbar {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.skills-toolbar-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.skills-toolbar-top .library-search {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Variant: filter pills on the left, primary action on the right.
|
||||
Used by Memory's toolbar where there is no search input above. */
|
||||
|
|
@ -16080,6 +16530,59 @@ body.entry-resizing { cursor: col-resize; user-select: none; }
|
|||
gap: 6px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compact dropdown row for Skills "Type" + "Category" filters.
|
||||
* Pills don't scale once the option set crosses ~10 entries (Skills'
|
||||
* Category list has 14 today and is growing); they wrap to 2-3 rows
|
||||
* and push the actual cards far below the fold. A labelled <select>
|
||||
* each keeps the same filter affordances in a single fixed-height
|
||||
* row. Source filter (3 options total) keeps its pill row above
|
||||
* because pills are still cheaper for a one-click toggle.
|
||||
*/
|
||||
.library-filter-selects {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.library-filter-select {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
min-width: 0;
|
||||
}
|
||||
.library-filter-select-label {
|
||||
font-weight: 500;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.library-filter-select select {
|
||||
padding: 5px 28px 5px 10px;
|
||||
font-size: 12px;
|
||||
background-color: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text);
|
||||
min-width: 100px;
|
||||
max-width: 180px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.library-filter-select select:hover:not(:disabled) {
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
/* Active = something other than "All" is selected. The darker border
|
||||
tells the user at a glance that a filter is in effect, without
|
||||
relying on the user remembering what the dropdown's idle state
|
||||
looked like. */
|
||||
.library-filter-select select[data-active='true'] {
|
||||
border-color: var(--text);
|
||||
background-color: var(--bg-subtle);
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.library-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -16342,7 +16845,35 @@ body.entry-resizing { cursor: col-resize; user-select: none; }
|
|||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
padding: 32px 0;
|
||||
/* Tinted fill so the empty state reads as its own slot inside the
|
||||
card. Without it the message floats in the same white as the card
|
||||
itself and looks like a layout gap rather than an intentional
|
||||
"you have nothing here yet" panel. */
|
||||
padding: 28px 24px;
|
||||
background: var(--bg-subtle);
|
||||
border-radius: var(--radius-md, 8px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.library-empty-title {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
.library-empty-hint {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
max-width: 420px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.library-empty-hint code {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11.5px;
|
||||
padding: 1px 6px;
|
||||
background: var(--bg-subtle);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Memory section: warning banner shown when the memory feature is
|
||||
|
|
@ -16374,9 +16905,66 @@ body.entry-resizing { cursor: col-resize; user-select: none; }
|
|||
|
||||
/* Memory section: monospace path showing where the memory files live on
|
||||
disk. `break-all` so even long absolute paths fit inside the panel. */
|
||||
.memory-root-dir {
|
||||
word-break: break-all;
|
||||
.memory-title-row {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
/*
|
||||
Tiny info button anchored to the Memory title. Hover surfaces the
|
||||
storage path via the native tooltip; a click copies it to the
|
||||
clipboard. Sized to the type so it sits as a quiet glyph rather than
|
||||
a clickable affordance — the hover affordance + copy flash do all
|
||||
the discovery work without a permanent string of /Users/... text
|
||||
occupying a whole row in the card.
|
||||
*/
|
||||
.memory-info-wrap {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.memory-info-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
transition: color 120ms ease, background 120ms ease;
|
||||
}
|
||||
.memory-info-btn:hover {
|
||||
color: var(--text);
|
||||
background: var(--bg-subtle);
|
||||
}
|
||||
.memory-path-copied-badge {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: var(--success-fg, #16a34a);
|
||||
background: var(--success-bg, #dcfce7);
|
||||
padding: 1px 6px;
|
||||
border-radius: 999px;
|
||||
animation: memory-badge-in 160ms ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@keyframes memory-badge-in {
|
||||
from { opacity: 0; transform: translateY(-2px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Extraction history + MEMORY.md (index) as standalone bordered cards,
|
||||
matching the "Advanced: proxy & custom paths" card style. */
|
||||
.memory-collapsible-card {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-panel);
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.memory-details-summary {
|
||||
|
|
@ -16395,10 +16983,16 @@ body.entry-resizing { cursor: col-resize; user-select: none; }
|
|||
}
|
||||
|
||||
.memory-details-summary::before {
|
||||
/* Caret used to render at 10px in --text-faint (#aaa). On hi-dpi
|
||||
panels that's a fuzzy speck the user can't even tell points right.
|
||||
Bumping to 13px (matches the summary text weight) and switching to
|
||||
--text-muted gives the disclosure marker enough mass to be obvious
|
||||
without dominating the row. */
|
||||
content: '▸';
|
||||
flex: 0 0 1em;
|
||||
color: var(--text-faint, #aaa);
|
||||
font-size: 10px;
|
||||
flex: 0 0 14px;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
transition: transform 120ms ease, color 120ms ease;
|
||||
}
|
||||
|
||||
|
|
@ -16408,7 +17002,7 @@ body.entry-resizing { cursor: col-resize; user-select: none; }
|
|||
|
||||
.memory-details-summary:hover::before,
|
||||
.memory-details-summary:focus-visible::before {
|
||||
color: var(--text-muted, #888);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.memory-details-summary:focus-visible {
|
||||
|
|
|
|||
|
|
@ -218,6 +218,11 @@ export const MEDIA_PROVIDERS: MediaProvider[] = [
|
|||
label: 'Stub (placeholder)',
|
||||
hint: 'Deterministic local placeholder bytes',
|
||||
integrated: true,
|
||||
// Internal fixture provider used by the daemon for deterministic
|
||||
// tests / offline demos. Hidden from Settings the same way
|
||||
// HyperFrames is — end users have nothing to configure here, and
|
||||
// exposing it pollutes the provider list.
|
||||
settingsVisible: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -841,11 +841,6 @@ describe('SettingsDialog execution settings Local CLI interactions', () => {
|
|||
{ agents: availableAgents },
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: /Local CLI.*1 installed/i }));
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Claude Code config directory'), {
|
||||
target: { value: ' ~/.claude-qa ' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText('Codex home'), {
|
||||
target: { value: ' ~/.codex-team ' },
|
||||
});
|
||||
|
|
@ -856,7 +851,6 @@ describe('SettingsDialog execution settings Local CLI interactions', () => {
|
|||
mode: 'daemon',
|
||||
agentId: 'codex',
|
||||
agentCliEnv: {
|
||||
claude: { CLAUDE_CONFIG_DIR: '~/.claude-qa' },
|
||||
codex: { CODEX_HOME: '~/.codex-team' },
|
||||
},
|
||||
}),
|
||||
|
|
@ -952,20 +946,19 @@ describe('SettingsDialog media providers interactions', () => {
|
|||
node.textContent?.trim(),
|
||||
);
|
||||
expect(names.slice(0, 2)).toEqual(['MiniMax', 'OpenAI']);
|
||||
expect(screen.getAllByText('Configured').length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it('renders unsupported providers as disabled rows', () => {
|
||||
it('renders non-integrated providers in the coming-soon section without input fields', () => {
|
||||
renderSettingsDialog(
|
||||
{ mode: 'daemon', agentId: 'codex' },
|
||||
{ initialSection: 'media' },
|
||||
);
|
||||
|
||||
expect(screen.getAllByText('Unsupported').length).toBeGreaterThan(0);
|
||||
const bflApiKey = screen.getByLabelText('Black Forest Labs API key') as HTMLInputElement;
|
||||
const bflBaseUrl = screen.getByLabelText('Black Forest Labs Base URL') as HTMLInputElement;
|
||||
expect(bflApiKey.disabled).toBe(true);
|
||||
expect(bflBaseUrl.disabled).toBe(true);
|
||||
// Non-integrated providers (e.g. Fal.ai, Black Forest Labs) are shown in
|
||||
// a separate "Coming soon" disclosure without editable inputs.
|
||||
expect(screen.queryByLabelText('Black Forest Labs API key')).toBeNull();
|
||||
expect(screen.queryByLabelText('Black Forest Labs Base URL')).toBeNull();
|
||||
expect(document.querySelector('.media-provider-coming-soon')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders ElevenLabs as an integrated media provider with enabled inputs', () => {
|
||||
|
|
@ -976,9 +969,6 @@ describe('SettingsDialog media providers interactions', () => {
|
|||
|
||||
const apiKeyInput = screen.getByLabelText('ElevenLabs API key') as HTMLInputElement;
|
||||
const baseUrlInput = screen.getByLabelText('ElevenLabs Base URL') as HTMLInputElement;
|
||||
const row = apiKeyInput.closest('.media-provider-row') as HTMLElement;
|
||||
|
||||
expect(within(row).getByText('Integrated')).toBeTruthy();
|
||||
expect(apiKeyInput.disabled).toBe(false);
|
||||
expect(baseUrlInput.disabled).toBe(false);
|
||||
});
|
||||
|
|
@ -1291,7 +1281,7 @@ describe('SettingsDialog connectors interactions', () => {
|
|||
apiKeyTail: '',
|
||||
});
|
||||
});
|
||||
expect(screen.getByText(/keys are stored locally in the daemon/i)).toBeTruthy();
|
||||
expect(screen.getByText(/keys are stored locally and never shared/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('closes Composio settings via the close button or backdrop', () => {
|
||||
|
|
@ -1393,10 +1383,6 @@ describe('SettingsDialog MCP server interactions', () => {
|
|||
await waitFor(() => {
|
||||
expect(fetchMock).toHaveBeenCalledWith('/api/mcp/install-info');
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('heading', { level: 3, name: 'MCP server' })).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(screen.getByText(/Run this in your terminal/i)).toBeTruthy();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/claude mcp add-json --scope user open-design/i)).toBeTruthy();
|
||||
|
|
@ -2032,7 +2018,9 @@ describe('SettingsDialog skills section', () => {
|
|||
expect(screen.getByText('sales-deck')).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /deck1/i }));
|
||||
fireEvent.change(screen.getByRole('combobox', { name: 'Type' }), {
|
||||
target: { value: 'deck' },
|
||||
});
|
||||
expect(screen.queryByText('blog-post')).toBeNull();
|
||||
expect(screen.getByText('sales-deck')).toBeTruthy();
|
||||
|
||||
|
|
@ -2146,7 +2134,6 @@ describe('SettingsDialog about interactions', () => {
|
|||
},
|
||||
);
|
||||
|
||||
expect(screen.getByRole('heading', { level: 3, name: 'About' })).toBeTruthy();
|
||||
expect(screen.getByText('Version')).toBeTruthy();
|
||||
expect(screen.getByText('0.4.1')).toBeTruthy();
|
||||
expect(screen.getByText('Channel')).toBeTruthy();
|
||||
|
|
|
|||
|
|
@ -113,10 +113,9 @@ describe('SettingsDialog media providers', () => {
|
|||
expect((screen.getByLabelText('OpenAI Base URL') as HTMLInputElement).value).toBe(
|
||||
'https://daemon.example/v1',
|
||||
);
|
||||
expect((screen.getByLabelText('Fal.ai Base URL') as HTMLInputElement).value).toBe(
|
||||
'https://queue.fal.run',
|
||||
);
|
||||
expect((screen.getByLabelText('Fal.ai API key') as HTMLInputElement).value).toBe('sk-local-fal');
|
||||
// Fal.ai is a non-integrated (coming-soon) provider and no longer has
|
||||
// editable input fields in the UI; its config is preserved in state via
|
||||
// mergeDaemonMediaProviders (covered by state/config.test.ts).
|
||||
});
|
||||
|
||||
it('preserves saved media keys when clearing only a non-secret field', async () => {
|
||||
|
|
|
|||
|
|
@ -227,4 +227,4 @@ where.exe opencode # should show C:\Users\YOUR_USERNAME\AppData\Roaming\npm\op
|
|||
opencode --version
|
||||
```
|
||||
|
||||
If Open Design still shows OpenCode as *not installed* in **Settings → Execution & model**, click **Rescan** after confirming the `opencode.cmd` directory is on your user `PATH`.
|
||||
If Open Design still shows OpenCode as *not installed* in **Settings → Execution mode**, click **Rescan** after confirming the `opencode.cmd` directory is on your user `PATH`.
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ export function createDesktopHarness(name: string) {
|
|||
const ready = await this.eval<boolean>(`
|
||||
(() => Boolean(
|
||||
document.querySelector('[role="dialog"]') ||
|
||||
document.querySelector('button[title="Configure execution mode"]') ||
|
||||
document.querySelector('button[title="Execution mode"]') ||
|
||||
document.querySelector('.settings-icon-btn')
|
||||
))()
|
||||
`);
|
||||
|
|
@ -121,7 +121,7 @@ export function createDesktopHarness(name: string) {
|
|||
const clicked = await this.eval(`
|
||||
(() => {
|
||||
if (document.querySelector('[role="dialog"]')) return true;
|
||||
const homeButton = document.querySelector('button[title="Configure execution mode"]');
|
||||
const homeButton = document.querySelector('button[title="Execution mode"]');
|
||||
if (homeButton instanceof HTMLElement) {
|
||||
homeButton.click();
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -238,12 +238,12 @@ desktopMacDescribe('mac desktop settings smoke', () => {
|
|||
}, 'model');
|
||||
|
||||
await desktop.openSettings();
|
||||
await openDesktopSettingsSection(desktop, 'Configure execution mode');
|
||||
await openDesktopSettingsSection(desktop, 'Execution mode');
|
||||
|
||||
await waitFor(async () => {
|
||||
const snapshot = await readDesktopSettingsSnapshot(desktop);
|
||||
expect(snapshot.dialogOpen).toBe(true);
|
||||
expect(snapshot.heading).toBe('Execution & model');
|
||||
expect(snapshot.heading).toBe('Execution mode');
|
||||
expect(snapshot.selectedProtocol).toBe('Anthropic API');
|
||||
expect(snapshot.quickFillProvider).toBe('Anthropic (Claude)');
|
||||
expect(snapshot.baseUrl).toBe('https://api.anthropic.com');
|
||||
|
|
@ -266,7 +266,7 @@ desktopMacDescribe('mac desktop settings smoke', () => {
|
|||
}, 'baseUrl');
|
||||
|
||||
await desktop.openSettings();
|
||||
await openDesktopSettingsSection(desktop, 'Configure execution mode');
|
||||
await openDesktopSettingsSection(desktop, 'Execution mode');
|
||||
|
||||
await waitFor(async () => {
|
||||
const snapshot = await readDesktopSettingsSnapshot(desktop);
|
||||
|
|
@ -350,13 +350,13 @@ desktopMacDescribe('mac desktop settings smoke', () => {
|
|||
}, 'agentId');
|
||||
|
||||
await desktop.openSettings();
|
||||
await openDesktopSettingsSection(desktop, 'Configure execution mode');
|
||||
await openDesktopSettingsSection(desktop, 'Execution mode');
|
||||
await clickDesktopExecutionModeTab(desktop, 'Local CLI');
|
||||
|
||||
await waitFor(async () => {
|
||||
const snapshot = await readDesktopLocalCliSnapshot(desktop);
|
||||
expect(snapshot.dialogOpen).toBe(true);
|
||||
expect(snapshot.heading).toBe('Execution & model');
|
||||
expect(snapshot.heading).toBe('Execution mode');
|
||||
expect(snapshot.localCliTabSelected).toBe(true);
|
||||
expect(snapshot.selectedAgent).toBe('Codex CLI');
|
||||
expect(snapshot.codexHome).toBe('~/.codex-team');
|
||||
|
|
@ -388,7 +388,7 @@ desktopMacDescribe('mac desktop settings smoke', () => {
|
|||
}, 'baseUrl');
|
||||
|
||||
await desktop.openSettings();
|
||||
await openDesktopSettingsSection(desktop, 'Configure execution mode');
|
||||
await openDesktopSettingsSection(desktop, 'Execution mode');
|
||||
|
||||
await waitFor(async () => {
|
||||
const snapshot = await readDesktopSettingsSnapshot(desktop);
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ test('live artifact empty connector CTA opens the gated connector setup path', a
|
|||
const settingsDialog = page.getByRole('dialog');
|
||||
await expect(settingsDialog).toBeVisible();
|
||||
await expect(
|
||||
settingsDialog.getByRole('heading', { level: 3, name: 'Connectors' }),
|
||||
settingsDialog.getByRole('heading', { level: 2, name: 'Connectors' }),
|
||||
).toBeVisible();
|
||||
await expect(settingsDialog.getByPlaceholder('Paste Composio API key')).toBeVisible();
|
||||
await expect(settingsDialog.getByTestId('connector-gate')).toBeVisible();
|
||||
|
|
@ -196,10 +196,10 @@ test('connectors search supports empty results and keyboard-closeable details',
|
|||
|
||||
await page.goto('/');
|
||||
// Connector cards + search now live under Settings → Connectors. Open the
|
||||
// settings dialog via the entry sidebar's "Configure execution mode" pill
|
||||
// settings dialog via the entry sidebar's "Execution mode" pill
|
||||
// and switch to the Connectors section before exercising the
|
||||
// search/empty/details flow.
|
||||
await page.getByRole('button', { name: 'Configure execution mode' }).click();
|
||||
await page.getByRole('button', { name: 'Execution mode', exact: true }).click();
|
||||
const settingsDialog = page.getByRole('dialog');
|
||||
await expect(settingsDialog).toBeVisible();
|
||||
await settingsDialog.getByRole('button', { name: /^Connectors\b/ }).click();
|
||||
|
|
@ -254,7 +254,7 @@ test('saving a Composio key from Settings unlocks the connectors gate immediatel
|
|||
});
|
||||
|
||||
await gotoEntryHome(page);
|
||||
await page.getByRole('button', { name: 'Configure execution mode' }).click();
|
||||
await page.getByRole('button', { name: 'Execution mode', exact: true }).click();
|
||||
const settingsDialog = page.getByRole('dialog');
|
||||
await expect(settingsDialog).toBeVisible();
|
||||
await settingsDialog.getByRole('button', { name: /^Connectors\b/ }).click();
|
||||
|
|
@ -316,7 +316,7 @@ test('typing a draft replacement Composio key does not trigger global autosave',
|
|||
});
|
||||
|
||||
await gotoEntryHome(page);
|
||||
await page.getByRole('button', { name: 'Configure execution mode' }).click();
|
||||
await page.getByRole('button', { name: 'Execution mode', exact: true }).click();
|
||||
const settingsDialog = page.getByRole('dialog');
|
||||
await expect(settingsDialog).toBeVisible();
|
||||
await settingsDialog.getByRole('button', { name: /^Connectors\b/ }).click();
|
||||
|
|
|
|||
|
|
@ -357,7 +357,7 @@ test('change pet opens pet settings and updates the custom companion draft', asy
|
|||
|
||||
const dialog = page.getByRole('dialog');
|
||||
await expect(dialog).toBeVisible();
|
||||
await expect(dialog.getByRole('heading', { level: 3, name: 'Pets' })).toBeVisible();
|
||||
await expect(dialog.getByRole('heading', { level: 2, name: 'Pets' })).toBeVisible();
|
||||
|
||||
await dialog.getByRole('tab', { name: 'Custom' }).click();
|
||||
const customPanel = dialog.locator('.pet-custom');
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ async function openExecutionSettings(
|
|||
});
|
||||
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: /Configure execution mode|配置执行模式/i }).click();
|
||||
await page.getByTitle('Execution mode').click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ async function openExecutionSettingsWithAgents(
|
|||
});
|
||||
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: /Configure execution mode|配置执行模式/i }).click();
|
||||
await page.getByTitle('Execution mode').click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
}
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ test('BYOK quick fill provider updates fields and saved settings persist after c
|
|||
apiProviderBaseUrl: 'https://api.deepseek.com',
|
||||
});
|
||||
|
||||
await page.getByRole('button', { name: /Configure execution mode|配置执行模式/i }).click();
|
||||
await page.getByTitle('Execution mode').click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
const reopenedDialog = page.getByRole('dialog');
|
||||
await expect(reopenedDialog.getByRole('tab', { name: 'OpenAI', exact: true })).toHaveAttribute('aria-selected', 'true');
|
||||
|
|
@ -351,8 +351,8 @@ test('saving Local CLI updates the entry status pill with the selected agent', a
|
|||
await dialog.getByRole('button', { name: 'Close', exact: true }).click();
|
||||
await expect(page.getByRole('dialog')).toHaveCount(0);
|
||||
|
||||
const executionPill = page.getByRole('button', { name: /Configure execution mode|配置执行模式/i });
|
||||
await expect(executionPill).toContainText(/Local CLI|本机 CLI/i);
|
||||
const executionPill = page.getByTitle('Execution mode');
|
||||
await expect(executionPill).toContainText('Local CLI');
|
||||
await expect(executionPill).toContainText('Codex CLI');
|
||||
await expect(executionPill).toContainText('0.80.0');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ async function openConnectorsSettings(
|
|||
});
|
||||
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: /Configure execution mode|配置执行模式/i }).click();
|
||||
await page.getByTitle('Execution mode').click();
|
||||
|
||||
const dialog = page.getByRole('dialog');
|
||||
await expect(dialog).toBeVisible();
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ async function openConnectorsSettings(
|
|||
});
|
||||
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: /Configure execution mode|配置执行模式/i }).click();
|
||||
await page.getByTitle('Execution mode').click();
|
||||
|
||||
const dialog = page.getByRole('dialog');
|
||||
await expect(dialog).toBeVisible();
|
||||
|
|
@ -301,7 +301,7 @@ test('clears pending authorization when OAuth launch is blocked after redirect_r
|
|||
await expect(githubCard.getByRole('button', { name: 'Cancel' })).toBeVisible();
|
||||
|
||||
await page.reload();
|
||||
await page.getByRole('button', { name: /Configure execution mode|配置执行模式/i }).click();
|
||||
await page.getByTitle('Execution mode').click();
|
||||
|
||||
const reloadedDialog = page.getByRole('dialog');
|
||||
await expect(reloadedDialog).toBeVisible();
|
||||
|
|
|
|||
|
|
@ -136,11 +136,9 @@ async function openLocalCliSettings(
|
|||
});
|
||||
|
||||
await page.goto('/');
|
||||
const settingsButton = page.locator(
|
||||
'button[aria-label="Configure execution mode"], button[aria-label="配置执行模式"], button[title="Configure execution mode"], button[title="配置执行模式"]',
|
||||
).first();
|
||||
await expect(settingsButton).toBeVisible();
|
||||
await settingsButton.click();
|
||||
await page
|
||||
.getByRole('button', { name: /Execution mode|执行模式/i })
|
||||
.click();
|
||||
|
||||
const dialog = page.getByRole('dialog');
|
||||
await expect(dialog).toBeVisible();
|
||||
|
|
|
|||
|
|
@ -55,11 +55,7 @@ async function seedSettingsBase(page: Page) {
|
|||
|
||||
async function openSettings(page: Page) {
|
||||
await page.goto('/');
|
||||
const settingsButton = page.locator(
|
||||
'button[aria-label="Configure execution mode"], button[aria-label="配置执行模式"], button[title="Configure execution mode"], button[title="配置执行模式"]',
|
||||
).first();
|
||||
await expect(settingsButton).toBeVisible();
|
||||
await settingsButton.click();
|
||||
await page.getByTitle('Execution mode').click();
|
||||
const dialog = page.getByRole('dialog');
|
||||
await expect(dialog).toBeVisible();
|
||||
return dialog;
|
||||
|
|
|
|||
Loading…
Reference in a new issue