Spaces:
Paused
Paused
Selects: our chevron in every engine
Browse files- piweb/hf-design.patch +331 -67
piweb/hf-design.patch
CHANGED
|
@@ -6571,7 +6571,7 @@ index 79c8282..97731f1 100644
|
|
| 6571 |
lang: "en",
|
| 6572 |
icons: [
|
| 6573 |
diff --git a/app/settings.css b/app/settings.css
|
| 6574 |
-
index f0e2e95..
|
| 6575 |
--- a/app/settings.css
|
| 6576 |
+++ b/app/settings.css
|
| 6577 |
@@ -35,14 +35,15 @@
|
|
@@ -8365,7 +8365,7 @@ index f0e2e95..52a7d18 100644
|
|
| 8365 |
background: var(--bg-panel);
|
| 8366 |
color: var(--text-muted);
|
| 8367 |
font-family: var(--font-mono);
|
| 8368 |
-
@@ -2256,
|
| 8369 |
min-width: 0;
|
| 8370 |
height: 32px;
|
| 8371 |
padding: 0 10px;
|
|
@@ -8377,7 +8377,25 @@ index f0e2e95..52a7d18 100644
|
|
| 8377 |
color: var(--text);
|
| 8378 |
font-family: var(--font-mono);
|
| 8379 |
font-size: 12px;
|
| 8380 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8381 |
max-width: 100%;
|
| 8382 |
max-height: 100%;
|
| 8383 |
overflow: hidden;
|
|
@@ -8391,7 +8409,7 @@ index f0e2e95..52a7d18 100644
|
|
| 8391 |
}
|
| 8392 |
|
| 8393 |
/* Focus moves to the dialog itself when it opens (tabIndex -1); it is not a control, so no ring. */
|
| 8394 |
-
@@ -2366,7 +
|
| 8395 |
flex: 1 1 auto;
|
| 8396 |
gap: 12px;
|
| 8397 |
min-height: 0;
|
|
@@ -8400,7 +8418,7 @@ index f0e2e95..52a7d18 100644
|
|
| 8400 |
overflow: auto;
|
| 8401 |
overscroll-behavior: contain;
|
| 8402 |
}
|
| 8403 |
-
@@ -2399,7 +
|
| 8404 |
margin-top: 10px;
|
| 8405 |
padding: 8px 10px;
|
| 8406 |
border: 1px solid var(--border);
|
|
@@ -8409,7 +8427,7 @@ index f0e2e95..52a7d18 100644
|
|
| 8409 |
background: var(--bg);
|
| 8410 |
color: var(--text);
|
| 8411 |
font-family: var(--font-mono);
|
| 8412 |
-
@@ -2417,7 +
|
| 8413 |
.project-trust-error {
|
| 8414 |
flex-shrink: 0;
|
| 8415 |
max-height: 30vh;
|
|
@@ -8418,7 +8436,7 @@ index f0e2e95..52a7d18 100644
|
|
| 8418 |
overflow: auto;
|
| 8419 |
color: #ef4444;
|
| 8420 |
font-size: 12px;
|
| 8421 |
-
@@ -2437,19 +
|
| 8422 |
flex-shrink: 0;
|
| 8423 |
justify-content: flex-end;
|
| 8424 |
gap: 8px;
|
|
@@ -8446,7 +8464,7 @@ index f0e2e95..52a7d18 100644
|
|
| 8446 |
}
|
| 8447 |
|
| 8448 |
.project-trust-button:disabled {
|
| 8449 |
-
@@ -2457,10 +
|
| 8450 |
}
|
| 8451 |
|
| 8452 |
.project-trust-button.is-primary {
|
|
@@ -8459,7 +8477,7 @@ index f0e2e95..52a7d18 100644
|
|
| 8459 |
}
|
| 8460 |
|
| 8461 |
.project-trust-button.is-primary:disabled {
|
| 8462 |
-
@@ -2534,7 +
|
| 8463 |
min-width: 0;
|
| 8464 |
padding: 8px 10px;
|
| 8465 |
border: 1px solid var(--border);
|
|
@@ -8468,7 +8486,7 @@ index f0e2e95..52a7d18 100644
|
|
| 8468 |
background: var(--bg);
|
| 8469 |
}
|
| 8470 |
|
| 8471 |
-
@@ -2556,7 +
|
| 8472 |
.project-trust-mcp-tag {
|
| 8473 |
flex-shrink: 0;
|
| 8474 |
padding: 1px 5px;
|
|
@@ -8477,7 +8495,7 @@ index f0e2e95..52a7d18 100644
|
|
| 8477 |
background: rgba(120, 120, 120, 0.12);
|
| 8478 |
color: var(--text-dim);
|
| 8479 |
font-size: 10px;
|
| 8480 |
-
@@ -2577,7 +
|
| 8481 |
display: inline-block;
|
| 8482 |
margin: 2px 4px 0 0;
|
| 8483 |
padding: 0 4px;
|
|
@@ -10769,10 +10787,10 @@ index 0b8f66a..9a60b64 100644
|
|
| 10769 |
.sidebar-tool-button.is-active,
|
| 10770 |
diff --git a/app/ui.css b/app/ui.css
|
| 10771 |
new file mode 100644
|
| 10772 |
-
index 0000000..
|
| 10773 |
--- /dev/null
|
| 10774 |
+++ b/app/ui.css
|
| 10775 |
-
@@ -0,0 +1,
|
| 10776 |
+/* The shared components on Base UI (components/ui/*): menus, tooltips,
|
| 10777 |
+ popovers and the composer's suggestion list. Popups are portaled to <body>,
|
| 10778 |
+ so they stack by the positioner's z-index, above every pane. */
|
|
@@ -11674,6 +11692,40 @@ index 0000000..bf28dc3
|
|
| 11674 |
+}
|
| 11675 |
+/* Quiet, on the left, its icon on the folders' column. */
|
| 11676 |
+.directory-picker-new { margin-right: auto; padding-left: var(--popup-row-pad); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11677 |
diff --git a/components/AgentSessionPanel.tsx b/components/AgentSessionPanel.tsx
|
| 11678 |
index 8d51f3c..61fcf9e 100644
|
| 11679 |
--- a/components/AgentSessionPanel.tsx
|
|
@@ -11793,19 +11845,20 @@ index 8d51f3c..61fcf9e 100644
|
|
| 11793 |
}}
|
| 11794 |
/>
|
| 11795 |
diff --git a/components/AgentsConfig.tsx b/components/AgentsConfig.tsx
|
| 11796 |
-
index 861a1c6..
|
| 11797 |
--- a/components/AgentsConfig.tsx
|
| 11798 |
+++ b/components/AgentsConfig.tsx
|
| 11799 |
-
@@ -38,6 +38,
|
| 11800 |
} from "./SettingsUi";
|
| 11801 |
import { ModelSelector } from "./ModelSelector";
|
| 11802 |
import { projectTrustReloadKey } from "./settings-ui-helpers";
|
| 11803 |
+import { CarbonIcon } from "./CarbonIcon";
|
| 11804 |
+import * as Carbon from "./carbon/icons";
|
|
|
|
| 11805 |
|
| 11806 |
const TOOL_OPTIONS = ["read", "bash", "edit", "write", "grep", "find", "ls"];
|
| 11807 |
const THINKING_OPTIONS = ["", "off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
| 11808 |
-
@@ -67,7 +
|
| 11809 |
height: 34,
|
| 11810 |
padding: "0 9px",
|
| 11811 |
border: "1px solid var(--border)",
|
|
@@ -11814,7 +11867,43 @@ index 861a1c6..d5d466e 100644
|
|
| 11814 |
background: "var(--bg)",
|
| 11815 |
color: "var(--text)",
|
| 11816 |
fontSize: 12,
|
| 11817 |
-
@@ -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11818 |
className={savedOk ? "is-success" : undefined}
|
| 11819 |
>
|
| 11820 |
{savedOk && (
|
|
@@ -22587,8 +22676,64 @@ index e04cdc1..3bedf1f 100644
|
|
| 22587 |
const filePath = onOpenFile ? resolveLocalFileHref(href, cwd) : null;
|
| 22588 |
const openFile = onOpenFile;
|
| 22589 |
if (!filePath || !openFile) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22590 |
diff --git a/components/McpConfig.tsx b/components/McpConfig.tsx
|
| 22591 |
-
index 97e86ee..
|
| 22592 |
--- a/components/McpConfig.tsx
|
| 22593 |
+++ b/components/McpConfig.tsx
|
| 22594 |
@@ -1,5 +1,6 @@
|
|
@@ -22598,7 +22743,15 @@ index 97e86ee..faf8050 100644
|
|
| 22598 |
import { useCallback, useEffect, useId, useRef, useState, type Ref } from "react";
|
| 22599 |
import type { McpExposure } from "@earendil-works/pi-coding-agent";
|
| 22600 |
import type {
|
| 22601 |
-
@@ -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22602 |
setAddFailure(null);
|
| 22603 |
}}
|
| 22604 |
onAddSubmit={(request) => void submitAdd(request)}
|
|
@@ -22606,7 +22759,7 @@ index 97e86ee..faf8050 100644
|
|
| 22606 |
onTest={(server) => void testServer(server)}
|
| 22607 |
onSignIn={(server) => void startSignIn(server)}
|
| 22608 |
onSignOut={(server) => void signOut(server)}
|
| 22609 |
-
@@ -850,6 +
|
| 22610 |
onAddOpen = () => {},
|
| 22611 |
onAddDraftChange = () => {},
|
| 22612 |
onAddSubmit = () => {},
|
|
@@ -22614,7 +22767,7 @@ index 97e86ee..faf8050 100644
|
|
| 22614 |
onSelect,
|
| 22615 |
onRefresh,
|
| 22616 |
onCodemodeChange,
|
| 22617 |
-
@@ -896,6 +
|
| 22618 |
onAddOpen?: () => void;
|
| 22619 |
onAddDraftChange?: (draft: McpAddDraft) => void;
|
| 22620 |
onAddSubmit?: (request: McpAddActionRequest) => void;
|
|
@@ -22623,7 +22776,7 @@ index 97e86ee..faf8050 100644
|
|
| 22623 |
onSelect: (key: string) => void;
|
| 22624 |
onRefresh: () => void;
|
| 22625 |
onCodemodeChange: (preference: McpCodemodePreference) => void;
|
| 22626 |
-
@@ -1099,6 +
|
| 22627 |
</>
|
| 22628 |
) : null}
|
| 22629 |
</ConfigSidebarList>
|
|
@@ -22639,6 +22792,24 @@ index 97e86ee..faf8050 100644
|
|
| 22639 |
{data && (
|
| 22640 |
<ConfigListAction ref={addActionRef} active={adding} onClick={onAddOpen}>
|
| 22641 |
{t("mcp.add.action")}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22642 |
diff --git a/components/MermaidBlock.test.mjs b/components/MermaidBlock.test.mjs
|
| 22643 |
index 45088d3..cf1df98 100644
|
| 22644 |
--- a/components/MermaidBlock.test.mjs
|
|
@@ -25027,7 +25198,7 @@ index bc635a4..5872f05 100644
|
|
| 25027 |
+ assert.equal(providerMatchesSearch(" ", oauth.id, oauth.name), true);
|
| 25028 |
+});
|
| 25029 |
diff --git a/components/ModelsConfig.tsx b/components/ModelsConfig.tsx
|
| 25030 |
-
index 3cfaabb..
|
| 25031 |
--- a/components/ModelsConfig.tsx
|
| 25032 |
+++ b/components/ModelsConfig.tsx
|
| 25033 |
@@ -14,9 +14,11 @@ import {
|
|
@@ -25042,16 +25213,17 @@ index 3cfaabb..019cb48 100644
|
|
| 25042 |
trackAddedModels,
|
| 25043 |
collectModelRenames,
|
| 25044 |
renameProviderEntry,
|
| 25045 |
-
@@ -56,6 +58,
|
| 25046 |
import { OAuthPastePanel } from "./OAuthPastePanel";
|
| 25047 |
import { ProviderIcon } from "./ProviderIcon";
|
| 25048 |
import { ProviderUsageSummary } from "./ProviderUsageSummary";
|
| 25049 |
+import { CarbonIcon } from "./CarbonIcon";
|
| 25050 |
+import * as Carbon from "./carbon/icons";
|
|
|
|
| 25051 |
|
| 25052 |
// ── Types ─────────────────────────────────────────────────────────────────────
|
| 25053 |
|
| 25054 |
-
@@ -93,6 +
|
| 25055 |
id: string;
|
| 25056 |
name?: string;
|
| 25057 |
api?: string;
|
|
@@ -25059,7 +25231,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25059 |
reasoning?: boolean;
|
| 25060 |
thinkingLevelMap?: Record<string, string | null>;
|
| 25061 |
input?: string[];
|
| 25062 |
-
@@ -186,7 +
|
| 25063 |
padding: "6px 9px",
|
| 25064 |
background: "var(--bg-panel)",
|
| 25065 |
border: "1px solid var(--border)",
|
|
@@ -25068,7 +25240,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25068 |
color: "var(--text)",
|
| 25069 |
fontSize: 12,
|
| 25070 |
outline: "none",
|
| 25071 |
-
@@ -260,17 +
|
| 25072 |
}}
|
| 25073 |
>
|
| 25074 |
{visible ? (
|
|
@@ -25088,7 +25260,22 @@ index 3cfaabb..019cb48 100644
|
|
| 25088 |
)}
|
| 25089 |
</button>
|
| 25090 |
</div>
|
| 25091 |
-
@@ -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25092 |
<TextInput value={editingName} onChange={onEditingNameChange} placeholder="provider-name" mono />
|
| 25093 |
{editingName !== name && editingName.trim() && (
|
| 25094 |
<button onClick={() => onRename(editingName.trim())}
|
|
@@ -25097,7 +25284,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25097 |
{t("i18n.rename")}
|
| 25098 |
</button>
|
| 25099 |
)}
|
| 25100 |
-
@@ -424,17 +
|
| 25101 |
<Field label="Base URL">
|
| 25102 |
<TextInput value={provider.baseUrl ?? ""} onChange={(v) => set("baseUrl", v || undefined)}
|
| 25103 |
placeholder="https://api.example.com/v1" mono />
|
|
@@ -25116,7 +25303,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25116 |
</Field>
|
| 25117 |
|
| 25118 |
<Field label="API">
|
| 25119 |
-
@@ -446,9 +
|
| 25120 |
headers={provider.headers}
|
| 25121 |
onChange={(headers) => set("headers", headers)}
|
| 25122 |
/>
|
|
@@ -25126,7 +25313,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25126 |
</Field>
|
| 25127 |
|
| 25128 |
<div style={{ borderTop: "1px solid var(--border)", paddingTop: 14, display: "flex", flexDirection: "column", gap: 10 }}>
|
| 25129 |
-
@@ -457,7 +
|
| 25130 |
onClick={handleDiscoverModels}
|
| 25131 |
disabled={discoveryState.phase === "loading"}
|
| 25132 |
style={{
|
|
@@ -25135,7 +25322,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25135 |
background: "var(--bg-panel)", color: discoveryState.phase === "loading" ? "var(--text-dim)" : "var(--text-muted)",
|
| 25136 |
cursor: discoveryState.phase === "loading" ? "not-allowed" : "pointer", fontSize: 11,
|
| 25137 |
}}
|
| 25138 |
-
@@ -467,7 +
|
| 25139 |
)}
|
| 25140 |
|
| 25141 |
{discoveryState.phase === "error" && (
|
|
@@ -25144,7 +25331,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25144 |
{discoveryState.message}
|
| 25145 |
</div>
|
| 25146 |
)}
|
| 25147 |
-
@@ -482,7 +
|
| 25148 |
style={{ ...inputStyle, width: "100%", minWidth: 0 }}
|
| 25149 |
/>
|
| 25150 |
|
|
@@ -25153,7 +25340,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25153 |
<label
|
| 25154 |
style={{
|
| 25155 |
minHeight: 32, padding: "5px 9px", display: "flex", alignItems: "center", gap: 8,
|
| 25156 |
-
@@ -541,7 +
|
| 25157 |
<button
|
| 25158 |
onClick={addSelectedModels}
|
| 25159 |
disabled={selectedCount === 0}
|
|
@@ -25162,7 +25349,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25162 |
>
|
| 25163 |
{selectedCount
|
| 25164 |
? t("models.discoveryAddSelectedCount", { count: selectedCount })
|
| 25165 |
-
@@ -563,10 +
|
| 25166 |
|
| 25167 |
const LEVEL_COLORS: Record<ThinkingLevel, string> = {
|
| 25168 |
off: "var(--text-dim)",
|
|
@@ -25178,7 +25365,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25178 |
xhigh: "#fb923c",
|
| 25179 |
max: "#ef4444",
|
| 25180 |
};
|
| 25181 |
-
@@ -629,7 +
|
| 25182 |
alignItems: "center",
|
| 25183 |
gap: 8,
|
| 25184 |
padding: "5px 4px",
|
|
@@ -25187,7 +25374,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25187 |
background: "transparent",
|
| 25188 |
border: "1px solid transparent",
|
| 25189 |
}}
|
| 25190 |
-
@@ -646,7 +
|
| 25191 |
</span>
|
| 25192 |
</div>
|
| 25193 |
|
|
@@ -25196,7 +25383,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25196 |
<button
|
| 25197 |
onClick={() => setLevel(level, "omit")}
|
| 25198 |
style={{ ...btnBase, ...(state === "omit" ? btnActive : {}) }}
|
| 25199 |
-
@@ -661,7 +
|
| 25200 |
</button>
|
| 25201 |
</div>
|
| 25202 |
|
|
@@ -25205,7 +25392,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25205 |
<button
|
| 25206 |
onClick={() => setLevel(level, strVal || level)}
|
| 25207 |
style={{ ...btnBase, ...(state === "string" ? btnActive : {}), borderRight: "1px solid var(--border)", flexShrink: 0 }}
|
| 25208 |
-
@@ -749,7 +
|
| 25209 |
padding: "6px 9px",
|
| 25210 |
background: "none",
|
| 25211 |
border: "1px solid rgba(239,68,68,0.3)",
|
|
@@ -25214,7 +25401,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25214 |
color: "#ef4444",
|
| 25215 |
cursor: "pointer",
|
| 25216 |
fontSize: 11,
|
| 25217 |
-
@@ -770,7 +
|
| 25218 |
...current,
|
| 25219 |
{ id: nextRowIdRef.current++, name: "", value: "" },
|
| 25220 |
])}
|
|
@@ -25223,7 +25410,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25223 |
+ Add header
|
| 25224 |
</button>
|
| 25225 |
</div>
|
| 25226 |
-
@@ -842,6 +
|
| 25227 |
const [costDraft, setCostDraft] = useState<ModelCostDraft>(() => modelCostToDraft(model.cost));
|
| 25228 |
const costDraftRef = useRef(costDraft);
|
| 25229 |
const [advancedOpen, setAdvancedOpen] = useState(false);
|
|
@@ -25232,7 +25419,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25232 |
const catalogRequestIdRef = useRef(0);
|
| 25233 |
const catalogUndoRef = useRef<ModelEntry | null>(null);
|
| 25234 |
const costTemplateRef = useRef(model.cost);
|
| 25235 |
-
@@ -885,17 +
|
| 25236 |
})();
|
| 25237 |
|
| 25238 |
useEffect(() => {
|
|
@@ -25256,7 +25443,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25256 |
setTestState({ phase: "testing" });
|
| 25257 |
try {
|
| 25258 |
const res = await fetch("/api/models-config/test", {
|
| 25259 |
-
@@ -910,6 +
|
| 25260 |
status?: number;
|
| 25261 |
responseText?: string;
|
| 25262 |
};
|
|
@@ -25264,7 +25451,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25264 |
if (!res.ok || !d.ok) {
|
| 25265 |
setTestState({
|
| 25266 |
phase: "error",
|
| 25267 |
-
@@ -926,6 +
|
| 25268 |
responseText: d.responseText,
|
| 25269 |
});
|
| 25270 |
} catch (e) {
|
|
@@ -25272,7 +25459,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25272 |
setTestState({ phase: "error", message: e instanceof Error ? e.message : String(e) });
|
| 25273 |
}
|
| 25274 |
}, [model, provider, providerName, testState.phase]);
|
| 25275 |
-
@@ -937,7 +
|
| 25276 |
setCatalogState({ phase: "loading" });
|
| 25277 |
try {
|
| 25278 |
const params = new URLSearchParams({ q: query, provider: providerName, limit: "50" });
|
|
@@ -25281,7 +25468,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25281 |
const res = await fetch(`/api/models-config/catalog?${params}`);
|
| 25282 |
const data = await res.json() as { recommendation?: ModelCatalogRecommendation; error?: string };
|
| 25283 |
if (requestId !== catalogRequestIdRef.current) return;
|
| 25284 |
-
@@ -960,7 +
|
| 25285 |
if (requestId !== catalogRequestIdRef.current) return;
|
| 25286 |
setCatalogState({ phase: "error", message: error instanceof Error ? error.message : String(error) });
|
| 25287 |
}
|
|
@@ -25290,7 +25477,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25290 |
|
| 25291 |
const undoCatalogFill = () => {
|
| 25292 |
const previous = catalogUndoRef.current;
|
| 25293 |
-
@@ -1023,6 +
|
| 25294 |
}
|
| 25295 |
compatibilityOverrideCount += remainingCompatKeys.size;
|
| 25296 |
const advancedSummaryParts = [
|
|
@@ -25298,7 +25485,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25298 |
model.api ? `API: ${model.api}` : null,
|
| 25299 |
Object.keys(model.headers ?? {}).length
|
| 25300 |
? t("models.headersSummary", { count: Object.keys(model.headers ?? {}).length })
|
| 25301 |
-
@@ -1053,9 +
|
| 25302 |
height: 28,
|
| 25303 |
padding: "0 8px",
|
| 25304 |
border: `1px solid ${testState.phase === "error" ? "#fecaca" : testState.phase === "success" ? "#bbf7d0" : "var(--border)"}`,
|
|
@@ -25311,7 +25498,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25311 |
fontSize: 11,
|
| 25312 |
display: "inline-flex",
|
| 25313 |
alignItems: "center",
|
| 25314 |
-
@@ -1077,9 +
|
| 25315 |
className={testState.phase === "success" ? "is-success" : undefined}
|
| 25316 |
>
|
| 25317 |
{testState.phase === "success" && (
|
|
@@ -25322,7 +25509,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25322 |
)}
|
| 25323 |
{testState.phase === "testing" ? t("i18n.checking") : testState.phase === "success" ? t("common.ok") : t("i18n.test")}
|
| 25324 |
</ConfigButton>
|
| 25325 |
-
@@ -1098,7 +
|
| 25326 |
onClick={() => void handleCatalogFill()}
|
| 25327 |
disabled={!model.id.trim() || catalogState.phase === "loading"}
|
| 25328 |
style={{
|
|
@@ -25331,7 +25518,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25331 |
background: "var(--bg-panel)",
|
| 25332 |
color: !model.id.trim() || catalogState.phase === "loading" ? "var(--text-dim)" : "var(--text-muted)",
|
| 25333 |
cursor: !model.id.trim() || catalogState.phase === "loading" ? "not-allowed" : "pointer",
|
| 25334 |
-
@@ -1177,7 +
|
| 25335 |
</div>
|
| 25336 |
|
| 25337 |
<div style={{ marginTop: 16 }}>
|
|
@@ -25340,7 +25527,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25340 |
{t("models.costPerMillion")}
|
| 25341 |
</div>
|
| 25342 |
{costEditing ? (
|
| 25343 |
-
@@ -1229,24 +
|
| 25344 |
{advancedSummary}
|
| 25345 |
</span>
|
| 25346 |
</span>
|
|
@@ -25371,7 +25558,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25371 |
<Field label={t("models.apiOverride")}>
|
| 25372 |
<Select value={model.api ?? ""} onChange={(v) => set("api", v || undefined)} options={API_OPTIONS} />
|
| 25373 |
</Field>
|
| 25374 |
-
@@ -1274,6 +
|
| 25375 |
checked={effectiveCompat(provider, model)["supportsDeveloperRole"] !== false}
|
| 25376 |
onChange={(v) => onChange(setCompatBool(model, "supportsDeveloperRole", v))}
|
| 25377 |
/>
|
|
@@ -25390,7 +25577,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25390 |
<div style={{ marginTop: 4 }}>
|
| 25391 |
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, marginBottom: 8 }}>
|
| 25392 |
<SectionTitle>{t("models.thinkingLevelMap")}</SectionTitle>
|
| 25393 |
-
@@ -1495,7 +
|
| 25394 |
<button
|
| 25395 |
key={option.id}
|
| 25396 |
onClick={() => submitSelection(loginState.token, option.id)}
|
|
@@ -25399,7 +25586,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25399 |
>
|
| 25400 |
{option.label}
|
| 25401 |
</button>
|
| 25402 |
-
@@ -1530,7 +
|
| 25403 |
<p style={{ margin: 0, fontSize: 12, color: "var(--text-muted)", lineHeight: 1.5 }}>
|
| 25404 |
Open the verification page and enter this code:
|
| 25405 |
</p>
|
|
@@ -25408,7 +25595,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25408 |
{loginState.userCode}
|
| 25409 |
</div>
|
| 25410 |
<p style={{ margin: 0, fontSize: 11, color: "var(--text-dim)", lineHeight: 1.5 }}>
|
| 25411 |
-
@@ -1670,7 +
|
| 25412 |
style={{
|
| 25413 |
padding: "6px 12px",
|
| 25414 |
background: savedOk ? "#16a34a" : apiKey.trim() ? "var(--accent)" : "var(--bg-panel)",
|
|
@@ -25417,7 +25604,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25417 |
color: savedOk ? "#fff" : apiKey.trim() ? "var(--accent-contrast)" : "var(--text-dim)",
|
| 25418 |
cursor: (saving || !apiKey.trim() || savedOk) ? "not-allowed" : "pointer",
|
| 25419 |
fontSize: 12, fontWeight: 600, flexShrink: 0,
|
| 25420 |
-
@@ -1678,9 +
|
| 25421 |
}}
|
| 25422 |
>
|
| 25423 |
{savedOk && (
|
|
@@ -25428,7 +25615,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25428 |
)}
|
| 25429 |
{savedOk ? t("i18n.saved") : saving ? t("i18n.saving") : t("i18n.save")}
|
| 25430 |
</button>
|
| 25431 |
-
@@ -1718,8 +
|
| 25432 |
|
| 25433 |
const q = search.trim().toLowerCase();
|
| 25434 |
|
|
@@ -25439,7 +25626,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25439 |
const showCustom = !q || "custom".includes(q) || "openai-compatible".includes(q) || "anthropic-compatible".includes(q);
|
| 25440 |
|
| 25441 |
const totalCount = availableOAuth.length + availableApiKey.length + (showCustom ? 1 : 0);
|
| 25442 |
-
@@ -1729,7 +
|
| 25443 |
padding: "10px 12px",
|
| 25444 |
background: "var(--bg-panel)",
|
| 25445 |
border: "1px solid var(--border)",
|
|
@@ -25448,7 +25635,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25448 |
boxSizing: "border-box",
|
| 25449 |
cursor: "pointer",
|
| 25450 |
minWidth: 0,
|
| 25451 |
-
@@ -1751,12 +
|
| 25452 |
onClose();
|
| 25453 |
}}
|
| 25454 |
>
|
|
@@ -25463,7 +25650,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25463 |
<input
|
| 25464 |
ref={inputRef}
|
| 25465 |
value={search}
|
| 25466 |
-
@@ -1773,7 +
|
| 25467 |
) : (
|
| 25468 |
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(240px, 100%), 1fr))", gap: 8 }}>
|
| 25469 |
{showCustom && (
|
|
@@ -25472,7 +25659,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25472 |
)}
|
| 25473 |
{showCustom && (
|
| 25474 |
<button
|
| 25475 |
-
@@ -1786,16 +
|
| 25476 |
<div style={{ fontSize: 12, fontWeight: 600, color: "var(--text)", lineHeight: 1.3, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>OpenAI / Anthropic compatible</div>
|
| 25477 |
<div style={{ fontSize: 10, color: "var(--text-dim)", marginTop: 2 }}>{t("i18n.customEndpoint")}</div>
|
| 25478 |
</div>
|
|
@@ -25492,7 +25679,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25492 |
)}
|
| 25493 |
{availableOAuth.map((p) => (
|
| 25494 |
<button key={p.id} onClick={() => { onSelectOAuth(p.id); onClose(); }}
|
| 25495 |
-
@@ -1812,7 +
|
| 25496 |
))}
|
| 25497 |
|
| 25498 |
{availableApiKey.length > 0 && (
|
|
@@ -25501,7 +25688,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25501 |
)}
|
| 25502 |
{availableApiKey.map((p) => (
|
| 25503 |
<button key={p.id} onClick={() => { onSelectApiKey(p.id); onClose(); }}
|
| 25504 |
-
@@ -2216,13 +
|
| 25505 |
onClick={() => setSelection({ type: "provider", name: pName })}
|
| 25506 |
active={isProviderSelected}
|
| 25507 |
>
|
|
@@ -25516,7 +25703,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25516 |
<ConfigSidebarText className="is-grow">
|
| 25517 |
{pName}
|
| 25518 |
</ConfigSidebarText>
|
| 25519 |
-
@@ -2243,7 +
|
| 25520 |
{m.id || t("i18n.newModel")}
|
| 25521 |
</ConfigSidebarText>
|
| 25522 |
{m.reasoning && (
|
|
@@ -25525,7 +25712,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25525 |
)}
|
| 25526 |
</ConfigSidebarItem>
|
| 25527 |
);
|
| 25528 |
-
@@ -2276,11 +
|
| 25529 |
</ConfigSplitView>
|
| 25530 |
|
| 25531 |
{/* Footer */}
|
|
@@ -25539,7 +25726,7 @@ index 3cfaabb..019cb48 100644
|
|
| 25539 |
{!embedded && <ConfigButton onClick={onClose}>{t("i18n.cancel")}</ConfigButton>}
|
| 25540 |
<ConfigButton
|
| 25541 |
variant="primary"
|
| 25542 |
-
@@ -2289,10 +
|
| 25543 |
className={savedOk ? "is-success" : undefined}
|
| 25544 |
>
|
| 25545 |
{savedOk && (
|
|
@@ -32534,6 +32721,83 @@ index 0000000..5d2e784
|
|
| 32534 |
+ else if (also) (also as { current: T | null }).current = node;
|
| 32535 |
+ }, [also]);
|
| 32536 |
+}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32537 |
diff --git a/components/ui/tooltip.tsx b/components/ui/tooltip.tsx
|
| 32538 |
new file mode 100644
|
| 32539 |
index 0000000..019f84d
|
|
|
|
| 6571 |
lang: "en",
|
| 6572 |
icons: [
|
| 6573 |
diff --git a/app/settings.css b/app/settings.css
|
| 6574 |
+
index f0e2e95..4d4f447 100644
|
| 6575 |
--- a/app/settings.css
|
| 6576 |
+++ b/app/settings.css
|
| 6577 |
@@ -35,14 +35,15 @@
|
|
|
|
| 8365 |
background: var(--bg-panel);
|
| 8366 |
color: var(--text-muted);
|
| 8367 |
font-family: var(--font-mono);
|
| 8368 |
+
@@ -2256,27 +2461,14 @@
|
| 8369 |
min-width: 0;
|
| 8370 |
height: 32px;
|
| 8371 |
padding: 0 10px;
|
|
|
|
| 8377 |
color: var(--text);
|
| 8378 |
font-family: var(--font-mono);
|
| 8379 |
font-size: 12px;
|
| 8380 |
+
outline: none;
|
| 8381 |
+
}
|
| 8382 |
+
|
| 8383 |
+
-/* macOS draws the native arrow against the border, ignoring the padding: a chevron of
|
| 8384 |
+
- our own, in the theme's dim text color, as far from the right edge as the text is from the left. */
|
| 8385 |
+
-select.mcp-add-input {
|
| 8386 |
+
- appearance: none;
|
| 8387 |
+
- padding-right: 30px;
|
| 8388 |
+
- background-image:
|
| 8389 |
+
- linear-gradient(45deg, transparent 40%, var(--text-dim) 40%, var(--text-dim) 60%, transparent 60%),
|
| 8390 |
+
- linear-gradient(-45deg, transparent 40%, var(--text-dim) 40%, var(--text-dim) 60%, transparent 60%);
|
| 8391 |
+
- background-size: 5px 5px;
|
| 8392 |
+
- background-position: right 15px center, right 10px center;
|
| 8393 |
+
- background-repeat: no-repeat;
|
| 8394 |
+
-}
|
| 8395 |
+
|
| 8396 |
+
.mcp-add-toggle {
|
| 8397 |
+
display: flex;
|
| 8398 |
+
@@ -2350,10 +2542,9 @@ select.mcp-add-input {
|
| 8399 |
max-width: 100%;
|
| 8400 |
max-height: 100%;
|
| 8401 |
overflow: hidden;
|
|
|
|
| 8409 |
}
|
| 8410 |
|
| 8411 |
/* Focus moves to the dialog itself when it opens (tabIndex -1); it is not a control, so no ring. */
|
| 8412 |
+
@@ -2366,7 +2557,7 @@ select.mcp-add-input {
|
| 8413 |
flex: 1 1 auto;
|
| 8414 |
gap: 12px;
|
| 8415 |
min-height: 0;
|
|
|
|
| 8418 |
overflow: auto;
|
| 8419 |
overscroll-behavior: contain;
|
| 8420 |
}
|
| 8421 |
+
@@ -2399,7 +2590,7 @@ select.mcp-add-input {
|
| 8422 |
margin-top: 10px;
|
| 8423 |
padding: 8px 10px;
|
| 8424 |
border: 1px solid var(--border);
|
|
|
|
| 8427 |
background: var(--bg);
|
| 8428 |
color: var(--text);
|
| 8429 |
font-family: var(--font-mono);
|
| 8430 |
+
@@ -2417,7 +2608,7 @@ select.mcp-add-input {
|
| 8431 |
.project-trust-error {
|
| 8432 |
flex-shrink: 0;
|
| 8433 |
max-height: 30vh;
|
|
|
|
| 8436 |
overflow: auto;
|
| 8437 |
color: #ef4444;
|
| 8438 |
font-size: 12px;
|
| 8439 |
+
@@ -2437,19 +2628,20 @@ select.mcp-add-input {
|
| 8440 |
flex-shrink: 0;
|
| 8441 |
justify-content: flex-end;
|
| 8442 |
gap: 8px;
|
|
|
|
| 8464 |
}
|
| 8465 |
|
| 8466 |
.project-trust-button:disabled {
|
| 8467 |
+
@@ -2457,10 +2649,8 @@ select.mcp-add-input {
|
| 8468 |
}
|
| 8469 |
|
| 8470 |
.project-trust-button.is-primary {
|
|
|
|
| 8477 |
}
|
| 8478 |
|
| 8479 |
.project-trust-button.is-primary:disabled {
|
| 8480 |
+
@@ -2534,7 +2724,7 @@ select.mcp-add-input {
|
| 8481 |
min-width: 0;
|
| 8482 |
padding: 8px 10px;
|
| 8483 |
border: 1px solid var(--border);
|
|
|
|
| 8486 |
background: var(--bg);
|
| 8487 |
}
|
| 8488 |
|
| 8489 |
+
@@ -2556,7 +2746,7 @@ select.mcp-add-input {
|
| 8490 |
.project-trust-mcp-tag {
|
| 8491 |
flex-shrink: 0;
|
| 8492 |
padding: 1px 5px;
|
|
|
|
| 8495 |
background: rgba(120, 120, 120, 0.12);
|
| 8496 |
color: var(--text-dim);
|
| 8497 |
font-size: 10px;
|
| 8498 |
+
@@ -2577,7 +2767,7 @@ select.mcp-add-input {
|
| 8499 |
display: inline-block;
|
| 8500 |
margin: 2px 4px 0 0;
|
| 8501 |
padding: 0 4px;
|
|
|
|
| 10787 |
.sidebar-tool-button.is-active,
|
| 10788 |
diff --git a/app/ui.css b/app/ui.css
|
| 10789 |
new file mode 100644
|
| 10790 |
+
index 0000000..32e2abf
|
| 10791 |
--- /dev/null
|
| 10792 |
+++ b/app/ui.css
|
| 10793 |
+
@@ -0,0 +1,935 @@
|
| 10794 |
+/* The shared components on Base UI (components/ui/*): menus, tooltips,
|
| 10795 |
+ popovers and the composer's suggestion list. Popups are portaled to <body>,
|
| 10796 |
+ so they stack by the positioner's z-index, above every pane. */
|
|
|
|
| 11692 |
+}
|
| 11693 |
+/* Quiet, on the left, its icon on the folders' column. */
|
| 11694 |
+.directory-picker-new { margin-right: auto; padding-left: var(--popup-row-pad); }
|
| 11695 |
+
+
|
| 11696 |
+
+/* ---------------------------------------------------------------- selects
|
| 11697 |
+
+ components/ui/select.tsx: a native select with Carbon's 12px chevron instead of the
|
| 11698 |
+
+ engine's arrow (Chromium's sat 3px from the edge whatever the padding, after 4.5px of
|
| 11699 |
+
+ its own before the text; Firefox and Safari placed theirs elsewhere). The chevron's ink
|
| 11700 |
+
+ mirrors the text's padding (--select-pad; its ink starts 2.25px inside its box, the
|
| 11701 |
+
+ text's 0.75px past the padding) and starts 6.25px after the text, as in the menu triggers.
|
| 11702 |
+
+ The select keeps its own colours and border; the padding is set by the component. */
|
| 11703 |
+
+.ui-select {
|
| 11704 |
+
+ position: relative;
|
| 11705 |
+
+ display: inline-flex;
|
| 11706 |
+
+ flex: none;
|
| 11707 |
+
+ min-width: 0;
|
| 11708 |
+
+ max-width: 100%;
|
| 11709 |
+
+ vertical-align: middle;
|
| 11710 |
+
+}
|
| 11711 |
+
+.ui-select.is-fill { display: flex; width: 100%; }
|
| 11712 |
+
+.ui-select > select {
|
| 11713 |
+
+ -webkit-appearance: none;
|
| 11714 |
+
+ appearance: none;
|
| 11715 |
+
+ flex: 1 1 auto;
|
| 11716 |
+
+ min-width: 0;
|
| 11717 |
+
+ cursor: pointer;
|
| 11718 |
+
+}
|
| 11719 |
+
+.ui-select > select:disabled { cursor: default; }
|
| 11720 |
+
+.ui-select-chevron {
|
| 11721 |
+
+ position: absolute;
|
| 11722 |
+
+ top: 50%;
|
| 11723 |
+
+ right: calc(var(--select-pad, 10px) + var(--select-border, 0px) - 1.5px);
|
| 11724 |
+
+ color: var(--text-dim);
|
| 11725 |
+
+ transform: translateY(-50%);
|
| 11726 |
+
+ pointer-events: none;
|
| 11727 |
+
+}
|
| 11728 |
+
+.ui-select:has(> select:disabled) > .ui-select-chevron { opacity: 0.45; }
|
| 11729 |
diff --git a/components/AgentSessionPanel.tsx b/components/AgentSessionPanel.tsx
|
| 11730 |
index 8d51f3c..61fcf9e 100644
|
| 11731 |
--- a/components/AgentSessionPanel.tsx
|
|
|
|
| 11845 |
}}
|
| 11846 |
/>
|
| 11847 |
diff --git a/components/AgentsConfig.tsx b/components/AgentsConfig.tsx
|
| 11848 |
+
index 861a1c6..efd7e87 100644
|
| 11849 |
--- a/components/AgentsConfig.tsx
|
| 11850 |
+++ b/components/AgentsConfig.tsx
|
| 11851 |
+
@@ -38,6 +38,9 @@ import {
|
| 11852 |
} from "./SettingsUi";
|
| 11853 |
import { ModelSelector } from "./ModelSelector";
|
| 11854 |
import { projectTrustReloadKey } from "./settings-ui-helpers";
|
| 11855 |
+import { CarbonIcon } from "./CarbonIcon";
|
| 11856 |
+import * as Carbon from "./carbon/icons";
|
| 11857 |
+
+import { NativeSelect } from "./ui/select";
|
| 11858 |
|
| 11859 |
const TOOL_OPTIONS = ["read", "bash", "edit", "write", "grep", "find", "ls"];
|
| 11860 |
const THINKING_OPTIONS = ["", "off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
| 11861 |
+
@@ -67,7 +70,7 @@ const inputStyle: CSSProperties = {
|
| 11862 |
height: 34,
|
| 11863 |
padding: "0 9px",
|
| 11864 |
border: "1px solid var(--border)",
|
|
|
|
| 11867 |
background: "var(--bg)",
|
| 11868 |
color: "var(--text)",
|
| 11869 |
fontSize: 12,
|
| 11870 |
+
@@ -662,17 +665,20 @@ export function AgentsConfig({
|
| 11871 |
+
|
| 11872 |
+
<Field label={t("agents.codemode")}>
|
| 11873 |
+
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
| 11874 |
+
- <select
|
| 11875 |
+
+ <NativeSelect
|
| 11876 |
+
+ pad={9}
|
| 11877 |
+
+ border={1}
|
| 11878 |
+
+ wrapperStyle={{ alignSelf: "flex-start" }}
|
| 11879 |
+
aria-label={t("agents.codemode")}
|
| 11880 |
+
value={draft.codemode ?? "off"}
|
| 11881 |
+
disabled={disabled}
|
| 11882 |
+
onChange={(event) => update("codemode", event.target.value as EditableProfile["codemode"])}
|
| 11883 |
+
- style={{ ...controlStyle, width: "auto", minWidth: 180, alignSelf: "flex-start" }}
|
| 11884 |
+
+ style={{ ...controlStyle, width: "auto", minWidth: 180 }}
|
| 11885 |
+
>
|
| 11886 |
+
<option value="inherit">{t("agents.codemode.inherit")}</option>
|
| 11887 |
+
<option value="on">{t("agents.codemode.on")}</option>
|
| 11888 |
+
<option value="off">{t("agents.codemode.off")}</option>
|
| 11889 |
+
- </select>
|
| 11890 |
+
+ </NativeSelect>
|
| 11891 |
+
<span style={{ color: "var(--text-dim)", fontSize: 11, lineHeight: 1.4 }}>
|
| 11892 |
+
{t("agents.codemodeHint")}
|
| 11893 |
+
</span>
|
| 11894 |
+
@@ -700,9 +706,9 @@ export function AgentsConfig({
|
| 11895 |
+
</div>
|
| 11896 |
+
</Field>
|
| 11897 |
+
<Field label={t("agents.thinking")}>
|
| 11898 |
+
- <select aria-label={t("agents.thinking")} value={draft.thinking ?? ""} disabled={disabled} onChange={(event) => update("thinking", (event.target.value || undefined) as EditableProfile["thinking"])} style={controlStyle}>
|
| 11899 |
+
+ <NativeSelect pad={9} border={1} fill aria-label={t("agents.thinking")} value={draft.thinking ?? ""} disabled={disabled} onChange={(event) => update("thinking", (event.target.value || undefined) as EditableProfile["thinking"])} style={controlStyle}>
|
| 11900 |
+
{THINKING_OPTIONS.map((value) => <option key={value || "default"} value={value}>{value || t("agents.inherit")}</option>)}
|
| 11901 |
+
- </select>
|
| 11902 |
+
+ </NativeSelect>
|
| 11903 |
+
</Field>
|
| 11904 |
+
<Field label={t("agents.maxTurns")}>
|
| 11905 |
+
<input aria-label={t("agents.maxTurns")} type="number" min={1} value={draft.maxTurns ?? ""} disabled={disabled} onChange={(event) => update("maxTurns", event.target.value ? Number(event.target.value) : undefined)} style={controlStyle} />
|
| 11906 |
+
@@ -727,9 +733,7 @@ export function AgentsConfig({
|
| 11907 |
className={savedOk ? "is-success" : undefined}
|
| 11908 |
>
|
| 11909 |
{savedOk && (
|
|
|
|
| 22676 |
const filePath = onOpenFile ? resolveLocalFileHref(href, cwd) : null;
|
| 22677 |
const openFile = onOpenFile;
|
| 22678 |
if (!filePath || !openFile) {
|
| 22679 |
+
diff --git a/components/McpAddServer.tsx b/components/McpAddServer.tsx
|
| 22680 |
+
index 2753f9e..4b272ca 100644
|
| 22681 |
+
--- a/components/McpAddServer.tsx
|
| 22682 |
+
+++ b/components/McpAddServer.tsx
|
| 22683 |
+
@@ -40,6 +40,7 @@ import {
|
| 22684 |
+
type McpAddSubmitBlock,
|
| 22685 |
+
} from "./mcp-add-helpers";
|
| 22686 |
+
import type { McpActionFailure, McpActionRequest } from "./mcp-config-helpers";
|
| 22687 |
+
+import { NativeSelect } from "./ui/select";
|
| 22688 |
+
|
| 22689 |
+
type Translate = ReturnType<typeof useI18n>["t"];
|
| 22690 |
+
export type McpAddActionRequest = Extract<McpActionRequest, { action: "add" }>;
|
| 22691 |
+
@@ -255,7 +256,7 @@ export function McpAddServer({
|
| 22692 |
+
|
| 22693 |
+
{parsed.ok && parsed.servers.length > 1 && (
|
| 22694 |
+
<ConfigField label={t("mcp.add.serverLabel")}>
|
| 22695 |
+
- <select
|
| 22696 |
+
+ <NativeSelect fill
|
| 22697 |
+
className="mcp-add-input"
|
| 22698 |
+
aria-label={t("mcp.add.serverLabel")}
|
| 22699 |
+
value={String(parsed.servers.indexOf(server ?? parsed.servers[0]))}
|
| 22700 |
+
@@ -264,7 +265,7 @@ export function McpAddServer({
|
| 22701 |
+
{parsed.servers.map((item, index) => (
|
| 22702 |
+
<option key={`${index}\0${item.name}`} value={index}>{revealHiddenCharacters(item.name)}</option>
|
| 22703 |
+
))}
|
| 22704 |
+
- </select>
|
| 22705 |
+
+ </NativeSelect>
|
| 22706 |
+
<span className="mcp-config-line is-dim">{t("mcp.add.serverCount", { count: parsed.servers.length })}</span>
|
| 22707 |
+
</ConfigField>
|
| 22708 |
+
)}
|
| 22709 |
+
@@ -518,10 +519,10 @@ function McpAddFieldInput({
|
| 22710 |
+
onChange={(event) => setReference(event.target.value)}
|
| 22711 |
+
/>
|
| 22712 |
+
) : field.kind === "select" ? (
|
| 22713 |
+
- <select className="mcp-add-input" aria-label={label} value={value} {...invalid} onChange={(event) => setValue(event.target.value)}>
|
| 22714 |
+
+ <NativeSelect fill className="mcp-add-input" aria-label={label} value={value} {...invalid} onChange={(event) => setValue(event.target.value)}>
|
| 22715 |
+
{!field.options?.includes(value) && <option value="">{t("mcp.add.field.choose")}</option>}
|
| 22716 |
+
{(field.options ?? []).map((option) => <option key={option} value={option}>{revealHiddenCharacters(option)}</option>)}
|
| 22717 |
+
- </select>
|
| 22718 |
+
+ </NativeSelect>
|
| 22719 |
+
) : (
|
| 22720 |
+
<input
|
| 22721 |
+
className="mcp-add-input"
|
| 22722 |
+
diff --git a/components/McpConfig.test.mjs b/components/McpConfig.test.mjs
|
| 22723 |
+
index 440f63e..529aea4 100644
|
| 22724 |
+
--- a/components/McpConfig.test.mjs
|
| 22725 |
+
+++ b/components/McpConfig.test.mjs
|
| 22726 |
+
@@ -774,7 +774,7 @@ test("the exposure dropdown waits like a switch, and points at why it cannot be
|
| 22727 |
+
// While any change is on its way it waits; the one for this server says it is saving.
|
| 22728 |
+
const saving = validated({ view: { busy: "exposure:global\0docs" } });
|
| 22729 |
+
assert.match(exposureSelect(saving).tag, /disabled=""/);
|
| 22730 |
+
- assert.match(decode(saving), /<span class="mcp-exposure-choice"><select[^>]*>[\s\S]*?<\/select><span role="status" class="mcp-config-line is-dim">Saving…<\/span><\/span>/);
|
| 22731 |
+
+ assert.match(decode(saving), /<span class="mcp-exposure-choice"><span class="ui-select"[^>]*><select[^>]*>[\s\S]*?<\/select><svg[^>]*class="ui-select-chevron"[\s\S]*?<\/svg><\/span><span role="status" class="mcp-config-line is-dim">Saving…<\/span><\/span>/);
|
| 22732 |
+
assert.match(exposureSelect(validated({ view: { busy: "switch:global\0pw" } })).tag, /disabled=""/);
|
| 22733 |
+
assert.doesNotMatch(exposureSelect(validated()).tag, /disabled/);
|
| 22734 |
+
// Where no change may be written, it points at the note that says why.
|
| 22735 |
diff --git a/components/McpConfig.tsx b/components/McpConfig.tsx
|
| 22736 |
+
index 97e86ee..b5af79b 100644
|
| 22737 |
--- a/components/McpConfig.tsx
|
| 22738 |
+++ b/components/McpConfig.tsx
|
| 22739 |
@@ -1,5 +1,6 @@
|
|
|
|
| 22743 |
import { useCallback, useEffect, useId, useRef, useState, type Ref } from "react";
|
| 22744 |
import type { McpExposure } from "@earendil-works/pi-coding-agent";
|
| 22745 |
import type {
|
| 22746 |
+
@@ -170,6 +171,7 @@ import {
|
| 22747 |
+
type McpSignInRun,
|
| 22748 |
+
} from "./mcp-sign-in-helpers";
|
| 22749 |
+
import { projectTrustReloadKey } from "./settings-ui-helpers";
|
| 22750 |
+
+import { NativeSelect } from "./ui/select";
|
| 22751 |
+
|
| 22752 |
+
type Translate = ReturnType<typeof useI18n>["t"];
|
| 22753 |
+
|
| 22754 |
+
@@ -795,6 +797,7 @@ export function McpConfig({
|
| 22755 |
setAddFailure(null);
|
| 22756 |
}}
|
| 22757 |
onAddSubmit={(request) => void submitAdd(request)}
|
|
|
|
| 22759 |
onTest={(server) => void testServer(server)}
|
| 22760 |
onSignIn={(server) => void startSignIn(server)}
|
| 22761 |
onSignOut={(server) => void signOut(server)}
|
| 22762 |
+
@@ -850,6 +853,7 @@ export function McpConfigView({
|
| 22763 |
onAddOpen = () => {},
|
| 22764 |
onAddDraftChange = () => {},
|
| 22765 |
onAddSubmit = () => {},
|
|
|
|
| 22767 |
onSelect,
|
| 22768 |
onRefresh,
|
| 22769 |
onCodemodeChange,
|
| 22770 |
+
@@ -896,6 +900,8 @@ export function McpConfigView({
|
| 22771 |
onAddOpen?: () => void;
|
| 22772 |
onAddDraftChange?: (draft: McpAddDraft) => void;
|
| 22773 |
onAddSubmit?: (request: McpAddActionRequest) => void;
|
|
|
|
| 22776 |
onSelect: (key: string) => void;
|
| 22777 |
onRefresh: () => void;
|
| 22778 |
onCodemodeChange: (preference: McpCodemodePreference) => void;
|
| 22779 |
+
@@ -1099,6 +1105,15 @@ export function McpConfigView({
|
| 22780 |
</>
|
| 22781 |
) : null}
|
| 22782 |
</ConfigSidebarList>
|
|
|
|
| 22792 |
{data && (
|
| 22793 |
<ConfigListAction ref={addActionRef} active={adding} onClick={onAddOpen}>
|
| 22794 |
{t("mcp.add.action")}
|
| 22795 |
+
@@ -1620,7 +1635,7 @@ function McpServerDetail({
|
| 22796 |
+
{server.exposure && (
|
| 22797 |
+
<>
|
| 22798 |
+
<span className="mcp-exposure-choice">
|
| 22799 |
+
- <select
|
| 22800 |
+
+ <NativeSelect
|
| 22801 |
+
className="mcp-add-input mcp-exposure-select"
|
| 22802 |
+
aria-label={t("mcp.exposure.label", { name })}
|
| 22803 |
+
aria-describedby={[exposureId, block ? noteId : null].filter(Boolean).join(" ")}
|
| 22804 |
+
@@ -1638,7 +1653,7 @@ function McpServerDetail({
|
| 22805 |
+
: t(MCP_EXPOSURE_SHORT_KEYS[exposure])}
|
| 22806 |
+
</option>
|
| 22807 |
+
))}
|
| 22808 |
+
- </select>
|
| 22809 |
+
+ </NativeSelect>
|
| 22810 |
+
{busy === `exposure:${key}` && <span role="status" className="mcp-config-line is-dim">{t("i18n.saving")}</span>}
|
| 22811 |
+
</span>
|
| 22812 |
+
<span id={exposureId} className="mcp-config-line">{t(MCP_EXPOSURE_KEYS[server.exposure])}</span>
|
| 22813 |
diff --git a/components/MermaidBlock.test.mjs b/components/MermaidBlock.test.mjs
|
| 22814 |
index 45088d3..cf1df98 100644
|
| 22815 |
--- a/components/MermaidBlock.test.mjs
|
|
|
|
| 25198 |
+ assert.equal(providerMatchesSearch(" ", oauth.id, oauth.name), true);
|
| 25199 |
+});
|
| 25200 |
diff --git a/components/ModelsConfig.tsx b/components/ModelsConfig.tsx
|
| 25201 |
+
index 3cfaabb..1a5c7e6 100644
|
| 25202 |
--- a/components/ModelsConfig.tsx
|
| 25203 |
+++ b/components/ModelsConfig.tsx
|
| 25204 |
@@ -14,9 +14,11 @@ import {
|
|
|
|
| 25213 |
trackAddedModels,
|
| 25214 |
collectModelRenames,
|
| 25215 |
renameProviderEntry,
|
| 25216 |
+
@@ -56,6 +58,9 @@ import { providerBadgeLabel } from "./enabled-models-helpers";
|
| 25217 |
import { OAuthPastePanel } from "./OAuthPastePanel";
|
| 25218 |
import { ProviderIcon } from "./ProviderIcon";
|
| 25219 |
import { ProviderUsageSummary } from "./ProviderUsageSummary";
|
| 25220 |
+import { CarbonIcon } from "./CarbonIcon";
|
| 25221 |
+import * as Carbon from "./carbon/icons";
|
| 25222 |
+
+import { NativeSelect } from "./ui/select";
|
| 25223 |
|
| 25224 |
// ── Types ─────────────────────────────────────────────────────────────────────
|
| 25225 |
|
| 25226 |
+
@@ -93,6 +98,7 @@ interface ModelEntry {
|
| 25227 |
id: string;
|
| 25228 |
name?: string;
|
| 25229 |
api?: string;
|
|
|
|
| 25231 |
reasoning?: boolean;
|
| 25232 |
thinkingLevelMap?: Record<string, string | null>;
|
| 25233 |
input?: string[];
|
| 25234 |
+
@@ -186,7 +192,7 @@ const inputStyle = {
|
| 25235 |
padding: "6px 9px",
|
| 25236 |
background: "var(--bg-panel)",
|
| 25237 |
border: "1px solid var(--border)",
|
|
|
|
| 25240 |
color: "var(--text)",
|
| 25241 |
fontSize: 12,
|
| 25242 |
outline: "none",
|
| 25243 |
+
@@ -260,17 +266,9 @@ function SecretTextInput({
|
| 25244 |
}}
|
| 25245 |
>
|
| 25246 |
{visible ? (
|
|
|
|
| 25260 |
)}
|
| 25261 |
</button>
|
| 25262 |
</div>
|
| 25263 |
+
@@ -284,11 +282,11 @@ function NumInput({ value, onChange, placeholder }: { value: string; onChange: (
|
| 25264 |
+
function Select({ value, onChange, options, required }: { value: string; onChange: (v: string) => void; options: readonly string[]; required?: boolean }) {
|
| 25265 |
+
const { t } = useI18n();
|
| 25266 |
+
return (
|
| 25267 |
+
- <select value={value} onChange={(e) => onChange(e.target.value)}
|
| 25268 |
+
+ <NativeSelect pad={9} border={1} fill value={value} onChange={(e) => onChange(e.target.value)}
|
| 25269 |
+
style={{ ...inputStyle, color: value ? "var(--text)" : "var(--text-dim)" }}>
|
| 25270 |
+
- {!required && <option value="">— {t("i18n.default")} / none —</option>}
|
| 25271 |
+
+ {!required && <option value="">— {t("i18n.default")} / none —</option>}
|
| 25272 |
+
{options.map((o) => <option key={o} value={o}>{o}</option>)}
|
| 25273 |
+
- </select>
|
| 25274 |
+
+ </NativeSelect>
|
| 25275 |
+
);
|
| 25276 |
+
}
|
| 25277 |
+
|
| 25278 |
+
@@ -415,7 +413,7 @@ function ProviderDetail({ name, editingName, provider, onChange, onEditingNameCh
|
| 25279 |
<TextInput value={editingName} onChange={onEditingNameChange} placeholder="provider-name" mono />
|
| 25280 |
{editingName !== name && editingName.trim() && (
|
| 25281 |
<button onClick={() => onRename(editingName.trim())}
|
|
|
|
| 25284 |
{t("i18n.rename")}
|
| 25285 |
</button>
|
| 25286 |
)}
|
| 25287 |
+
@@ -424,17 +422,11 @@ function ProviderDetail({ name, editingName, provider, onChange, onEditingNameCh
|
| 25288 |
<Field label="Base URL">
|
| 25289 |
<TextInput value={provider.baseUrl ?? ""} onChange={(v) => set("baseUrl", v || undefined)}
|
| 25290 |
placeholder="https://api.example.com/v1" mono />
|
|
|
|
| 25303 |
</Field>
|
| 25304 |
|
| 25305 |
<Field label="API">
|
| 25306 |
+
@@ -446,9 +438,6 @@ function ProviderDetail({ name, editingName, provider, onChange, onEditingNameCh
|
| 25307 |
headers={provider.headers}
|
| 25308 |
onChange={(headers) => set("headers", headers)}
|
| 25309 |
/>
|
|
|
|
| 25313 |
</Field>
|
| 25314 |
|
| 25315 |
<div style={{ borderTop: "1px solid var(--border)", paddingTop: 14, display: "flex", flexDirection: "column", gap: 10 }}>
|
| 25316 |
+
@@ -457,7 +446,7 @@ function ProviderDetail({ name, editingName, provider, onChange, onEditingNameCh
|
| 25317 |
onClick={handleDiscoverModels}
|
| 25318 |
disabled={discoveryState.phase === "loading"}
|
| 25319 |
style={{
|
|
|
|
| 25322 |
background: "var(--bg-panel)", color: discoveryState.phase === "loading" ? "var(--text-dim)" : "var(--text-muted)",
|
| 25323 |
cursor: discoveryState.phase === "loading" ? "not-allowed" : "pointer", fontSize: 11,
|
| 25324 |
}}
|
| 25325 |
+
@@ -467,7 +456,7 @@ function ProviderDetail({ name, editingName, provider, onChange, onEditingNameCh
|
| 25326 |
)}
|
| 25327 |
|
| 25328 |
{discoveryState.phase === "error" && (
|
|
|
|
| 25331 |
{discoveryState.message}
|
| 25332 |
</div>
|
| 25333 |
)}
|
| 25334 |
+
@@ -482,7 +471,7 @@ function ProviderDetail({ name, editingName, provider, onChange, onEditingNameCh
|
| 25335 |
style={{ ...inputStyle, width: "100%", minWidth: 0 }}
|
| 25336 |
/>
|
| 25337 |
|
|
|
|
| 25340 |
<label
|
| 25341 |
style={{
|
| 25342 |
minHeight: 32, padding: "5px 9px", display: "flex", alignItems: "center", gap: 8,
|
| 25343 |
+
@@ -541,7 +530,7 @@ function ProviderDetail({ name, editingName, provider, onChange, onEditingNameCh
|
| 25344 |
<button
|
| 25345 |
onClick={addSelectedModels}
|
| 25346 |
disabled={selectedCount === 0}
|
|
|
|
| 25349 |
>
|
| 25350 |
{selectedCount
|
| 25351 |
? t("models.discoveryAddSelectedCount", { count: selectedCount })
|
| 25352 |
+
@@ -563,10 +552,11 @@ type ThinkingLevel = typeof THINKING_LEVELS[number];
|
| 25353 |
|
| 25354 |
const LEVEL_COLORS: Record<ThinkingLevel, string> = {
|
| 25355 |
off: "var(--text-dim)",
|
|
|
|
| 25365 |
xhigh: "#fb923c",
|
| 25366 |
max: "#ef4444",
|
| 25367 |
};
|
| 25368 |
+
@@ -629,7 +619,7 @@ function ThinkingLevelMapEditor({
|
| 25369 |
alignItems: "center",
|
| 25370 |
gap: 8,
|
| 25371 |
padding: "5px 4px",
|
|
|
|
| 25374 |
background: "transparent",
|
| 25375 |
border: "1px solid transparent",
|
| 25376 |
}}
|
| 25377 |
+
@@ -646,7 +636,7 @@ function ThinkingLevelMapEditor({
|
| 25378 |
</span>
|
| 25379 |
</div>
|
| 25380 |
|
|
|
|
| 25383 |
<button
|
| 25384 |
onClick={() => setLevel(level, "omit")}
|
| 25385 |
style={{ ...btnBase, ...(state === "omit" ? btnActive : {}) }}
|
| 25386 |
+
@@ -661,7 +651,7 @@ function ThinkingLevelMapEditor({
|
| 25387 |
</button>
|
| 25388 |
</div>
|
| 25389 |
|
|
|
|
| 25392 |
<button
|
| 25393 |
onClick={() => setLevel(level, strVal || level)}
|
| 25394 |
style={{ ...btnBase, ...(state === "string" ? btnActive : {}), borderRight: "1px solid var(--border)", flexShrink: 0 }}
|
| 25395 |
+
@@ -749,7 +739,7 @@ function HeaderListEditor({ headers, onChange }: {
|
| 25396 |
padding: "6px 9px",
|
| 25397 |
background: "none",
|
| 25398 |
border: "1px solid rgba(239,68,68,0.3)",
|
|
|
|
| 25401 |
color: "#ef4444",
|
| 25402 |
cursor: "pointer",
|
| 25403 |
fontSize: 11,
|
| 25404 |
+
@@ -770,7 +760,7 @@ function HeaderListEditor({ headers, onChange }: {
|
| 25405 |
...current,
|
| 25406 |
{ id: nextRowIdRef.current++, name: "", value: "" },
|
| 25407 |
])}
|
|
|
|
| 25410 |
+ Add header
|
| 25411 |
</button>
|
| 25412 |
</div>
|
| 25413 |
+
@@ -842,6 +832,8 @@ function ModelDetail({
|
| 25414 |
const [costDraft, setCostDraft] = useState<ModelCostDraft>(() => modelCostToDraft(model.cost));
|
| 25415 |
const costDraftRef = useRef(costDraft);
|
| 25416 |
const [advancedOpen, setAdvancedOpen] = useState(false);
|
|
|
|
| 25419 |
const catalogRequestIdRef = useRef(0);
|
| 25420 |
const catalogUndoRef = useRef<ModelEntry | null>(null);
|
| 25421 |
const costTemplateRef = useRef(model.cost);
|
| 25422 |
+
@@ -885,17 +877,21 @@ function ModelDetail({
|
| 25423 |
})();
|
| 25424 |
|
| 25425 |
useEffect(() => {
|
|
|
|
| 25443 |
setTestState({ phase: "testing" });
|
| 25444 |
try {
|
| 25445 |
const res = await fetch("/api/models-config/test", {
|
| 25446 |
+
@@ -910,6 +906,7 @@ function ModelDetail({
|
| 25447 |
status?: number;
|
| 25448 |
responseText?: string;
|
| 25449 |
};
|
|
|
|
| 25451 |
if (!res.ok || !d.ok) {
|
| 25452 |
setTestState({
|
| 25453 |
phase: "error",
|
| 25454 |
+
@@ -926,6 +923,7 @@ function ModelDetail({
|
| 25455 |
responseText: d.responseText,
|
| 25456 |
});
|
| 25457 |
} catch (e) {
|
|
|
|
| 25459 |
setTestState({ phase: "error", message: e instanceof Error ? e.message : String(e) });
|
| 25460 |
}
|
| 25461 |
}, [model, provider, providerName, testState.phase]);
|
| 25462 |
+
@@ -937,7 +935,7 @@ function ModelDetail({
|
| 25463 |
setCatalogState({ phase: "loading" });
|
| 25464 |
try {
|
| 25465 |
const params = new URLSearchParams({ q: query, provider: providerName, limit: "50" });
|
|
|
|
| 25468 |
const res = await fetch(`/api/models-config/catalog?${params}`);
|
| 25469 |
const data = await res.json() as { recommendation?: ModelCatalogRecommendation; error?: string };
|
| 25470 |
if (requestId !== catalogRequestIdRef.current) return;
|
| 25471 |
+
@@ -960,7 +958,7 @@ function ModelDetail({
|
| 25472 |
if (requestId !== catalogRequestIdRef.current) return;
|
| 25473 |
setCatalogState({ phase: "error", message: error instanceof Error ? error.message : String(error) });
|
| 25474 |
}
|
|
|
|
| 25477 |
|
| 25478 |
const undoCatalogFill = () => {
|
| 25479 |
const previous = catalogUndoRef.current;
|
| 25480 |
+
@@ -1023,6 +1021,7 @@ function ModelDetail({
|
| 25481 |
}
|
| 25482 |
compatibilityOverrideCount += remainingCompatKeys.size;
|
| 25483 |
const advancedSummaryParts = [
|
|
|
|
| 25485 |
model.api ? `API: ${model.api}` : null,
|
| 25486 |
Object.keys(model.headers ?? {}).length
|
| 25487 |
? t("models.headersSummary", { count: Object.keys(model.headers ?? {}).length })
|
| 25488 |
+
@@ -1053,9 +1052,9 @@ function ModelDetail({
|
| 25489 |
height: 28,
|
| 25490 |
padding: "0 8px",
|
| 25491 |
border: `1px solid ${testState.phase === "error" ? "#fecaca" : testState.phase === "success" ? "#bbf7d0" : "var(--border)"}`,
|
|
|
|
| 25498 |
fontSize: 11,
|
| 25499 |
display: "inline-flex",
|
| 25500 |
alignItems: "center",
|
| 25501 |
+
@@ -1077,9 +1076,7 @@ function ModelDetail({
|
| 25502 |
className={testState.phase === "success" ? "is-success" : undefined}
|
| 25503 |
>
|
| 25504 |
{testState.phase === "success" && (
|
|
|
|
| 25509 |
)}
|
| 25510 |
{testState.phase === "testing" ? t("i18n.checking") : testState.phase === "success" ? t("common.ok") : t("i18n.test")}
|
| 25511 |
</ConfigButton>
|
| 25512 |
+
@@ -1098,7 +1095,7 @@ function ModelDetail({
|
| 25513 |
onClick={() => void handleCatalogFill()}
|
| 25514 |
disabled={!model.id.trim() || catalogState.phase === "loading"}
|
| 25515 |
style={{
|
|
|
|
| 25518 |
background: "var(--bg-panel)",
|
| 25519 |
color: !model.id.trim() || catalogState.phase === "loading" ? "var(--text-dim)" : "var(--text-muted)",
|
| 25520 |
cursor: !model.id.trim() || catalogState.phase === "loading" ? "not-allowed" : "pointer",
|
| 25521 |
+
@@ -1177,7 +1174,7 @@ function ModelDetail({
|
| 25522 |
</div>
|
| 25523 |
|
| 25524 |
<div style={{ marginTop: 16 }}>
|
|
|
|
| 25527 |
{t("models.costPerMillion")}
|
| 25528 |
</div>
|
| 25529 |
{costEditing ? (
|
| 25530 |
+
@@ -1229,24 +1226,16 @@ function ModelDetail({
|
| 25531 |
{advancedSummary}
|
| 25532 |
</span>
|
| 25533 |
</span>
|
|
|
|
| 25558 |
<Field label={t("models.apiOverride")}>
|
| 25559 |
<Select value={model.api ?? ""} onChange={(v) => set("api", v || undefined)} options={API_OPTIONS} />
|
| 25560 |
</Field>
|
| 25561 |
+
@@ -1274,6 +1263,18 @@ function ModelDetail({
|
| 25562 |
checked={effectiveCompat(provider, model)["supportsDeveloperRole"] !== false}
|
| 25563 |
onChange={(v) => onChange(setCompatBool(model, "supportsDeveloperRole", v))}
|
| 25564 |
/>
|
|
|
|
| 25577 |
<div style={{ marginTop: 4 }}>
|
| 25578 |
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, marginBottom: 8 }}>
|
| 25579 |
<SectionTitle>{t("models.thinkingLevelMap")}</SectionTitle>
|
| 25580 |
+
@@ -1495,7 +1496,7 @@ function OAuthDetail({ provider, onRefresh, enabledModels, contextSettings }: {
|
| 25581 |
<button
|
| 25582 |
key={option.id}
|
| 25583 |
onClick={() => submitSelection(loginState.token, option.id)}
|
|
|
|
| 25586 |
>
|
| 25587 |
{option.label}
|
| 25588 |
</button>
|
| 25589 |
+
@@ -1530,7 +1531,7 @@ function OAuthDetail({ provider, onRefresh, enabledModels, contextSettings }: {
|
| 25590 |
<p style={{ margin: 0, fontSize: 12, color: "var(--text-muted)", lineHeight: 1.5 }}>
|
| 25591 |
Open the verification page and enter this code:
|
| 25592 |
</p>
|
|
|
|
| 25595 |
{loginState.userCode}
|
| 25596 |
</div>
|
| 25597 |
<p style={{ margin: 0, fontSize: 11, color: "var(--text-dim)", lineHeight: 1.5 }}>
|
| 25598 |
+
@@ -1670,7 +1671,7 @@ function ApiKeyDetail({ provider, onRefresh, enabledModels, contextSettings }: {
|
| 25599 |
style={{
|
| 25600 |
padding: "6px 12px",
|
| 25601 |
background: savedOk ? "#16a34a" : apiKey.trim() ? "var(--accent)" : "var(--bg-panel)",
|
|
|
|
| 25604 |
color: savedOk ? "#fff" : apiKey.trim() ? "var(--accent-contrast)" : "var(--text-dim)",
|
| 25605 |
cursor: (saving || !apiKey.trim() || savedOk) ? "not-allowed" : "pointer",
|
| 25606 |
fontSize: 12, fontWeight: 600, flexShrink: 0,
|
| 25607 |
+
@@ -1678,9 +1679,7 @@ function ApiKeyDetail({ provider, onRefresh, enabledModels, contextSettings }: {
|
| 25608 |
}}
|
| 25609 |
>
|
| 25610 |
{savedOk && (
|
|
|
|
| 25615 |
)}
|
| 25616 |
{savedOk ? t("i18n.saved") : saving ? t("i18n.saving") : t("i18n.save")}
|
| 25617 |
</button>
|
| 25618 |
+
@@ -1718,8 +1717,8 @@ function AddProviderPicker({
|
| 25619 |
|
| 25620 |
const q = search.trim().toLowerCase();
|
| 25621 |
|
|
|
|
| 25626 |
const showCustom = !q || "custom".includes(q) || "openai-compatible".includes(q) || "anthropic-compatible".includes(q);
|
| 25627 |
|
| 25628 |
const totalCount = availableOAuth.length + availableApiKey.length + (showCustom ? 1 : 0);
|
| 25629 |
+
@@ -1729,7 +1728,7 @@ function AddProviderPicker({
|
| 25630 |
padding: "10px 12px",
|
| 25631 |
background: "var(--bg-panel)",
|
| 25632 |
border: "1px solid var(--border)",
|
|
|
|
| 25635 |
boxSizing: "border-box",
|
| 25636 |
cursor: "pointer",
|
| 25637 |
minWidth: 0,
|
| 25638 |
+
@@ -1751,12 +1750,10 @@ function AddProviderPicker({
|
| 25639 |
onClose();
|
| 25640 |
}}
|
| 25641 |
>
|
|
|
|
| 25650 |
<input
|
| 25651 |
ref={inputRef}
|
| 25652 |
value={search}
|
| 25653 |
+
@@ -1773,7 +1770,7 @@ function AddProviderPicker({
|
| 25654 |
) : (
|
| 25655 |
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(240px, 100%), 1fr))", gap: 8 }}>
|
| 25656 |
{showCustom && (
|
|
|
|
| 25659 |
)}
|
| 25660 |
{showCustom && (
|
| 25661 |
<button
|
| 25662 |
+
@@ -1786,16 +1783,14 @@ function AddProviderPicker({
|
| 25663 |
<div style={{ fontSize: 12, fontWeight: 600, color: "var(--text)", lineHeight: 1.3, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>OpenAI / Anthropic compatible</div>
|
| 25664 |
<div style={{ fontSize: 10, color: "var(--text-dim)", marginTop: 2 }}>{t("i18n.customEndpoint")}</div>
|
| 25665 |
</div>
|
|
|
|
| 25679 |
)}
|
| 25680 |
{availableOAuth.map((p) => (
|
| 25681 |
<button key={p.id} onClick={() => { onSelectOAuth(p.id); onClose(); }}
|
| 25682 |
+
@@ -1812,7 +1807,7 @@ function AddProviderPicker({
|
| 25683 |
))}
|
| 25684 |
|
| 25685 |
{availableApiKey.length > 0 && (
|
|
|
|
| 25688 |
)}
|
| 25689 |
{availableApiKey.map((p) => (
|
| 25690 |
<button key={p.id} onClick={() => { onSelectApiKey(p.id); onClose(); }}
|
| 25691 |
+
@@ -2216,13 +2211,7 @@ export function ModelsConfig({ onClose, embedded = false, cwd = null }: {
|
| 25692 |
onClick={() => setSelection({ type: "provider", name: pName })}
|
| 25693 |
active={isProviderSelected}
|
| 25694 |
>
|
|
|
|
| 25703 |
<ConfigSidebarText className="is-grow">
|
| 25704 |
{pName}
|
| 25705 |
</ConfigSidebarText>
|
| 25706 |
+
@@ -2243,7 +2232,7 @@ export function ModelsConfig({ onClose, embedded = false, cwd = null }: {
|
| 25707 |
{m.id || t("i18n.newModel")}
|
| 25708 |
</ConfigSidebarText>
|
| 25709 |
{m.reasoning && (
|
|
|
|
| 25712 |
)}
|
| 25713 |
</ConfigSidebarItem>
|
| 25714 |
);
|
| 25715 |
+
@@ -2276,11 +2265,11 @@ export function ModelsConfig({ onClose, embedded = false, cwd = null }: {
|
| 25716 |
</ConfigSplitView>
|
| 25717 |
|
| 25718 |
{/* Footer */}
|
|
|
|
| 25726 |
{!embedded && <ConfigButton onClick={onClose}>{t("i18n.cancel")}</ConfigButton>}
|
| 25727 |
<ConfigButton
|
| 25728 |
variant="primary"
|
| 25729 |
+
@@ -2289,10 +2278,7 @@ export function ModelsConfig({ onClose, embedded = false, cwd = null }: {
|
| 25730 |
className={savedOk ? "is-success" : undefined}
|
| 25731 |
>
|
| 25732 |
{savedOk && (
|
|
|
|
| 32721 |
+ else if (also) (also as { current: T | null }).current = node;
|
| 32722 |
+ }, [also]);
|
| 32723 |
+}
|
| 32724 |
+
diff --git a/components/ui/select.test.mjs b/components/ui/select.test.mjs
|
| 32725 |
+
new file mode 100644
|
| 32726 |
+
index 0000000..668e391
|
| 32727 |
+
--- /dev/null
|
| 32728 |
+
+++ b/components/ui/select.test.mjs
|
| 32729 |
+
@@ -0,0 +1,28 @@
|
| 32730 |
+
+import assert from "node:assert/strict";
|
| 32731 |
+
+import { readdirSync, readFileSync } from "node:fs";
|
| 32732 |
+
+import test from "node:test";
|
| 32733 |
+
+import { createJiti } from "jiti";
|
| 32734 |
+
+
|
| 32735 |
+
+const jiti = createJiti(import.meta.url, { jsx: { runtime: "automatic" }, tsconfigPaths: true });
|
| 32736 |
+
+const React = await jiti.import("react");
|
| 32737 |
+
+const { renderToStaticMarkup } = await jiti.import("react-dom/server");
|
| 32738 |
+
+const { NativeSelect } = await jiti.import("./select.tsx");
|
| 32739 |
+
+
|
| 32740 |
+
+test("every select in the app is a NativeSelect, with our chevron instead of the engine's arrow", () => {
|
| 32741 |
+
+ const dir = new URL("..", import.meta.url);
|
| 32742 |
+
+ const raw = readdirSync(dir).filter((name) => name.endsWith(".tsx"))
|
| 32743 |
+
+ .filter((name) => /<select\b/.test(readFileSync(new URL(name, dir), "utf8")));
|
| 32744 |
+
+ assert.deepEqual(raw, [], "use components/ui/select.tsx's NativeSelect");
|
| 32745 |
+
+});
|
| 32746 |
+
+
|
| 32747 |
+
+test("the chevron mirrors the text's padding and the select keeps its own attributes", () => {
|
| 32748 |
+
+ const html = renderToStaticMarkup(React.createElement(NativeSelect, { pad: 9, fill: true, "aria-label": "Thinking", defaultValue: "b", style: { padding: "0 9px" } },
|
| 32749 |
+
+ React.createElement("option", { value: "a" }, "A"), React.createElement("option", { value: "b" }, "B")));
|
| 32750 |
+
+ assert.match(html, /^<span class="ui-select is-fill" style="--select-pad:9px">/);
|
| 32751 |
+
+ assert.match(html, /<select aria-label="Thinking" style="padding:0 9px;padding-left:9px;padding-right:calc\(9px \+ 13.5px\)">/);
|
| 32752 |
+
+ assert.match(html, /<option value="b" selected="">B<\/option><\/select><svg[^>]*class="[^"]*ui-select-chevron/);
|
| 32753 |
+
+ const css = readFileSync(new URL("../../app/ui.css", import.meta.url), "utf8");
|
| 32754 |
+
+ assert.match(css, /\.ui-select > select \{[^}]*appearance: none;/);
|
| 32755 |
+
+ assert.match(css, /\.ui-select-chevron \{[^}]*right: calc\(var\(--select-pad, 10px\) \+ var\(--select-border, 0px\) - 1\.5px\);/);
|
| 32756 |
+
+ assert.match(renderToStaticMarkup(React.createElement(NativeSelect, { pad: 9, border: 1 })), /style="--select-pad:9px;--select-border:1px"/);
|
| 32757 |
+
+});
|
| 32758 |
+
diff --git a/components/ui/select.tsx b/components/ui/select.tsx
|
| 32759 |
+
new file mode 100644
|
| 32760 |
+
index 0000000..d1a6266
|
| 32761 |
+
--- /dev/null
|
| 32762 |
+
+++ b/components/ui/select.tsx
|
| 32763 |
+
@@ -0,0 +1,37 @@
|
| 32764 |
+
+import type { CSSProperties, SelectHTMLAttributes } from "react";
|
| 32765 |
+
+import { CarbonIcon } from "../CarbonIcon";
|
| 32766 |
+
+import * as Carbon from "../carbon/icons";
|
| 32767 |
+
+
|
| 32768 |
+
+/**
|
| 32769 |
+
+ * A native select with Carbon's chevron instead of the engine's arrow: each engine places
|
| 32770 |
+
+ * its own (Chromium 3px from the edge whatever the padding, after 4.5px of its own padding
|
| 32771 |
+
+ * before the text; Firefox and Safari elsewhere again). The chevron's ink sits as far from
|
| 32772 |
+
+ * the right edge as the text's from the left (`pad`, the text's padding, inside `border`),
|
| 32773 |
+
+ * and 6.25px after the text, as in the menu triggers. Styled in app/ui.css (.ui-select).
|
| 32774 |
+
+ */
|
| 32775 |
+
+export function NativeSelect({
|
| 32776 |
+
+ pad = 10,
|
| 32777 |
+
+ border = 0,
|
| 32778 |
+
+ fill = false,
|
| 32779 |
+
+ wrapperStyle,
|
| 32780 |
+
+ style,
|
| 32781 |
+
+ children,
|
| 32782 |
+
+ ...props
|
| 32783 |
+
+}: SelectHTMLAttributes<HTMLSelectElement> & {
|
| 32784 |
+
+ /** The text's padding from the left edge, in px; the chevron mirrors it. */
|
| 32785 |
+
+ pad?: number;
|
| 32786 |
+
+ /** The select's border width, in px, which the text sits inside too. */
|
| 32787 |
+
+ border?: number;
|
| 32788 |
+
+ /** Take the row's whole width, as a field does. */
|
| 32789 |
+
+ fill?: boolean;
|
| 32790 |
+
+ wrapperStyle?: CSSProperties;
|
| 32791 |
+
+}) {
|
| 32792 |
+
+ return (
|
| 32793 |
+
+ <span className={`ui-select${fill ? " is-fill" : ""}`} style={{ "--select-pad": `${pad}px`, ...(border ? { "--select-border": `${border}px` } : {}), ...wrapperStyle } as CSSProperties}>
|
| 32794 |
+
+ <select {...props} style={{ ...style, paddingLeft: pad, paddingRight: `calc(${pad}px + 13.5px)` }}>
|
| 32795 |
+
+ {children}
|
| 32796 |
+
+ </select>
|
| 32797 |
+
+ <CarbonIcon icon={Carbon.ChevronDown} size={12} className="ui-select-chevron" />
|
| 32798 |
+
+ </span>
|
| 32799 |
+
+ );
|
| 32800 |
+
+}
|
| 32801 |
diff --git a/components/ui/tooltip.tsx b/components/ui/tooltip.tsx
|
| 32802 |
new file mode 100644
|
| 32803 |
index 0000000..019f84d
|