@agent-native/toolkit 0.15.1 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/agent-native.eject.json +16 -0
- package/dist/composer/ComposerPlusMenu.d.ts.map +1 -1
- package/dist/composer/ComposerPlusMenu.js +29 -19
- package/dist/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/composer/PromptComposer.d.ts +11 -1
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +2 -2
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/TiptapComposer.d.ts +30 -1
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +255 -145
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/dist/composer/TiptapComposer.spec.js +33 -1
- package/dist/composer/TiptapComposer.spec.js.map +1 -1
- package/dist/composer/index.d.ts +1 -0
- package/dist/composer/index.d.ts.map +1 -1
- package/dist/composer/index.js +1 -0
- package/dist/composer/index.js.map +1 -1
- package/dist/composer/model-selection.d.ts +12 -0
- package/dist/composer/model-selection.d.ts.map +1 -0
- package/dist/composer/model-selection.js +31 -0
- package/dist/composer/model-selection.js.map +1 -0
- package/dist/composer/model-selection.spec.d.ts +2 -0
- package/dist/composer/model-selection.spec.d.ts.map +1 -0
- package/dist/composer/model-selection.spec.js +38 -0
- package/dist/composer/model-selection.spec.js.map +1 -0
- package/dist/data-grid/DataGrid.d.ts +92 -0
- package/dist/data-grid/DataGrid.d.ts.map +1 -0
- package/dist/data-grid/DataGrid.js +315 -0
- package/dist/data-grid/DataGrid.js.map +1 -0
- package/dist/data-grid/DataGrid.spec.d.ts +2 -0
- package/dist/data-grid/DataGrid.spec.d.ts.map +1 -0
- package/dist/data-grid/DataGrid.spec.js +134 -0
- package/dist/data-grid/DataGrid.spec.js.map +1 -0
- package/dist/data-grid/index.d.ts +2 -0
- package/dist/data-grid/index.d.ts.map +1 -0
- package/dist/data-grid/index.js +2 -0
- package/dist/data-grid/index.js.map +1 -0
- package/dist/sharing/SharePrimitives.d.ts +14 -3
- package/dist/sharing/SharePrimitives.d.ts.map +1 -1
- package/dist/sharing/SharePrimitives.js +11 -3
- package/dist/sharing/SharePrimitives.js.map +1 -1
- package/dist/sharing/index.d.ts +1 -1
- package/dist/sharing/index.d.ts.map +1 -1
- package/dist/sharing/index.js +1 -1
- package/dist/sharing/index.js.map +1 -1
- package/package.json +15 -1
- package/src/composer/ComposerPlusMenu.tsx +122 -65
- package/src/composer/PromptComposer.tsx +21 -0
- package/src/composer/TiptapComposer.spec.ts +46 -0
- package/src/composer/TiptapComposer.tsx +721 -349
- package/src/composer/index.ts +6 -0
- package/src/composer/model-selection.spec.ts +45 -0
- package/src/composer/model-selection.ts +51 -0
- package/src/data-grid/DataGrid.spec.tsx +243 -0
- package/src/data-grid/DataGrid.tsx +754 -0
- package/src/data-grid/index.ts +15 -0
- package/src/sharing/SharePrimitives.tsx +47 -8
- package/src/sharing/index.ts +4 -0
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
IconKey,
|
|
15
15
|
IconPencil,
|
|
16
16
|
IconPlugConnected,
|
|
17
|
+
IconHelpCircle,
|
|
17
18
|
} from "@tabler/icons-react";
|
|
18
19
|
import Placeholder from "@tiptap/extension-placeholder";
|
|
19
20
|
import type { EditorView } from "@tiptap/pm/view";
|
|
@@ -28,7 +29,12 @@ import React, {
|
|
|
28
29
|
useMemo,
|
|
29
30
|
} from "react";
|
|
30
31
|
|
|
31
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
Popover,
|
|
34
|
+
PopoverAnchor,
|
|
35
|
+
PopoverContent,
|
|
36
|
+
PopoverTrigger,
|
|
37
|
+
} from "../ui/popover.js";
|
|
32
38
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip.js";
|
|
33
39
|
import { ComposerPlusMenu } from "./ComposerPlusMenu.js";
|
|
34
40
|
import { getComposerDraftKey } from "./draft-key.js";
|
|
@@ -36,6 +42,11 @@ import { FileReference } from "./extensions/FileReference.js";
|
|
|
36
42
|
import { MentionReference } from "./extensions/MentionReference.js";
|
|
37
43
|
import { SkillReference } from "./extensions/SkillReference.js";
|
|
38
44
|
import { MentionPopover, type MentionPopoverRef } from "./MentionPopover.js";
|
|
45
|
+
import {
|
|
46
|
+
isClaudeCodeAgentId,
|
|
47
|
+
isLunaModel,
|
|
48
|
+
resolvePreferredAgentModel,
|
|
49
|
+
} from "./model-selection.js";
|
|
39
50
|
import {
|
|
40
51
|
createPastedAttachmentFile,
|
|
41
52
|
readClipboardPaste,
|
|
@@ -652,6 +663,19 @@ function ComposerModeChip({
|
|
|
652
663
|
|
|
653
664
|
type ExecMode = "build" | "plan";
|
|
654
665
|
|
|
666
|
+
export interface ComposerAgentOption {
|
|
667
|
+
/** Stable host-defined identifier for the agent runtime. */
|
|
668
|
+
id: string;
|
|
669
|
+
/** Human-readable runtime name shown in the picker. */
|
|
670
|
+
label: string;
|
|
671
|
+
/** Optional short detail shown below the runtime name. */
|
|
672
|
+
description?: string;
|
|
673
|
+
/** Whether this runtime can be selected right now. */
|
|
674
|
+
configured?: boolean;
|
|
675
|
+
/** Optional status text such as "Installed" or "Sign in". */
|
|
676
|
+
statusLabel?: string;
|
|
677
|
+
}
|
|
678
|
+
|
|
655
679
|
export interface TiptapComposerProps {
|
|
656
680
|
placeholder?: string;
|
|
657
681
|
disabled?: boolean;
|
|
@@ -729,6 +753,8 @@ export interface TiptapComposerProps {
|
|
|
729
753
|
selectedEffort?: ReasoningEffort;
|
|
730
754
|
/** Show the legacy provider-level Auto model option (default: true). */
|
|
731
755
|
showAutoModelOption?: boolean;
|
|
756
|
+
/** Controlled open state for hosts that resize around the model picker. */
|
|
757
|
+
modelSelectorOpen?: boolean;
|
|
732
758
|
/** Available models grouped by provider */
|
|
733
759
|
availableModels?: Array<{
|
|
734
760
|
engine: string;
|
|
@@ -744,6 +770,16 @@ export interface TiptapComposerProps {
|
|
|
744
770
|
onModelChange?: (model: string, engine: string) => void;
|
|
745
771
|
/** Callback when user picks an effort */
|
|
746
772
|
onEffortChange?: (effort: ReasoningEffort) => void;
|
|
773
|
+
/** Local or hosted agent runtimes shown above the model list. */
|
|
774
|
+
availableAgents?: ComposerAgentOption[];
|
|
775
|
+
/** Selected agent runtime identifier. Defaults to the built-in agent. */
|
|
776
|
+
selectedAgent?: string;
|
|
777
|
+
/** Mark the selected runtime as the hosted tools-only harness mode. */
|
|
778
|
+
hostedHarness?: boolean;
|
|
779
|
+
/** Callback when the user picks an agent runtime. */
|
|
780
|
+
onAgentChange?: (agent: string) => void;
|
|
781
|
+
/** Called when the shared model picker opens or closes. */
|
|
782
|
+
onModelSelectorOpenChange?: (open: boolean) => void;
|
|
747
783
|
/**
|
|
748
784
|
* Disable Builder/provider status polling for hosts that supply provider
|
|
749
785
|
* state through another channel, such as Electron IPC.
|
|
@@ -918,7 +954,7 @@ function ModeSelector({
|
|
|
918
954
|
</p>
|
|
919
955
|
</div>
|
|
920
956
|
{mode === "build" && (
|
|
921
|
-
<IconCheck className="
|
|
957
|
+
<IconCheck className="size-4 shrink-0 text-primary" />
|
|
922
958
|
)}
|
|
923
959
|
</button>
|
|
924
960
|
<button
|
|
@@ -950,7 +986,7 @@ function ModeSelector({
|
|
|
950
986
|
</p>
|
|
951
987
|
</div>
|
|
952
988
|
{mode === "plan" && !planModeDisabled && (
|
|
953
|
-
<IconCheck className="
|
|
989
|
+
<IconCheck className="size-4 shrink-0 text-primary" />
|
|
954
990
|
)}
|
|
955
991
|
</button>
|
|
956
992
|
</PopoverContent>
|
|
@@ -961,17 +997,61 @@ function ModeSelector({
|
|
|
961
997
|
const FRIENDLY_MODEL_NAMES: Record<string, string> = {
|
|
962
998
|
auto: "Default model",
|
|
963
999
|
"codex-cli": "Codex",
|
|
1000
|
+
"claude-cli": "Claude Code",
|
|
1001
|
+
"pi-cli": "Pi",
|
|
1002
|
+
"opencode-cli": "OpenCode",
|
|
964
1003
|
"claude-fable-5": "Fable 5",
|
|
965
1004
|
"kimi-k2-5": "Kimi K2.5",
|
|
966
1005
|
"deepseek-v3-1": "DeepSeek v3.1",
|
|
967
1006
|
"z-ai/glm-5.2": "GLM 5.2",
|
|
968
1007
|
};
|
|
969
1008
|
|
|
1009
|
+
const LOCAL_RUNTIME_ENGINES = new Set([
|
|
1010
|
+
"codex-cli",
|
|
1011
|
+
"claude-cli",
|
|
1012
|
+
"pi-cli",
|
|
1013
|
+
"opencode-cli",
|
|
1014
|
+
]);
|
|
1015
|
+
|
|
1016
|
+
function isOpenAiModelId(model: string): boolean {
|
|
1017
|
+
const normalizedModel = model.toLowerCase();
|
|
1018
|
+
return (
|
|
1019
|
+
normalizedModel.startsWith("gpt-") ||
|
|
1020
|
+
normalizedModel.startsWith("openai/gpt-")
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
export function isOpenAiModelProviderGroup(group: {
|
|
1025
|
+
engine: string;
|
|
1026
|
+
label: string;
|
|
1027
|
+
models: string[];
|
|
1028
|
+
}): boolean {
|
|
1029
|
+
const engine = group.engine.toLowerCase();
|
|
1030
|
+
const label = group.label.toLowerCase();
|
|
1031
|
+
if (engine === "codex-cli") {
|
|
1032
|
+
return group.models.some(isOpenAiModelId);
|
|
1033
|
+
}
|
|
1034
|
+
return (
|
|
1035
|
+
engine.includes("openai") ||
|
|
1036
|
+
label.includes("openai") ||
|
|
1037
|
+
group.models.some(isOpenAiModelId)
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const HARNESS_AGENTS_DOCS_URL =
|
|
1042
|
+
"https://www.agent-native.com/docs/harness-agents";
|
|
1043
|
+
|
|
970
1044
|
export const MODEL_SELECTOR_POPOVER_STYLE = {
|
|
971
1045
|
fontSize: 13,
|
|
972
1046
|
maxHeight: "min(500px, var(--radix-popover-content-available-height, 500px))",
|
|
973
1047
|
} satisfies React.CSSProperties;
|
|
974
1048
|
|
|
1049
|
+
const MODEL_SELECTOR_POPOVER_CLASS =
|
|
1050
|
+
"box-border w-64 rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-lg outline-none";
|
|
1051
|
+
const PICKER_HELP_BUTTON_CLASS =
|
|
1052
|
+
"flex size-3 shrink-0 items-center justify-center rounded text-muted-foreground/60 hover:text-foreground";
|
|
1053
|
+
const PICKER_HELP_ICON_CLASS = "size-2";
|
|
1054
|
+
|
|
975
1055
|
export function shouldShowModelSelectorSkeleton(
|
|
976
1056
|
isLoading: boolean,
|
|
977
1057
|
engineCount: number,
|
|
@@ -1182,17 +1262,7 @@ function ModelCostTier({ model }: { model: string }) {
|
|
|
1182
1262
|
: tier === 2
|
|
1183
1263
|
? t("agentChat.composer.costMedium", { defaultValue: "Medium cost" })
|
|
1184
1264
|
: t("agentChat.composer.costHigher", { defaultValue: "Higher cost" });
|
|
1185
|
-
return
|
|
1186
|
-
<>
|
|
1187
|
-
<span
|
|
1188
|
-
aria-hidden="true"
|
|
1189
|
-
className="shrink-0 text-[11px] tabular-nums text-muted-foreground/60"
|
|
1190
|
-
>
|
|
1191
|
-
{"$".repeat(tier)}
|
|
1192
|
-
</span>
|
|
1193
|
-
<span className="sr-only">{costLabel}</span>
|
|
1194
|
-
</>
|
|
1195
|
-
);
|
|
1265
|
+
return <span className="sr-only">{costLabel}</span>;
|
|
1196
1266
|
}
|
|
1197
1267
|
|
|
1198
1268
|
/**
|
|
@@ -1225,10 +1295,16 @@ function ModelSelector({
|
|
|
1225
1295
|
model,
|
|
1226
1296
|
effort,
|
|
1227
1297
|
engines,
|
|
1298
|
+
agents,
|
|
1299
|
+
selectedAgent,
|
|
1300
|
+
hostedHarness = false,
|
|
1228
1301
|
showAutoModelOption = true,
|
|
1229
1302
|
modelListLoading = false,
|
|
1303
|
+
open: controlledOpen,
|
|
1230
1304
|
onChange,
|
|
1231
1305
|
onEffortChange,
|
|
1306
|
+
onAgentChange,
|
|
1307
|
+
onModelSelectorOpenChange,
|
|
1232
1308
|
providerConnectStatusEnabled = true,
|
|
1233
1309
|
onConnectProvider,
|
|
1234
1310
|
onConnectLocalRuntime,
|
|
@@ -1236,6 +1312,9 @@ function ModelSelector({
|
|
|
1236
1312
|
}: {
|
|
1237
1313
|
model: string;
|
|
1238
1314
|
effort?: ReasoningEffort;
|
|
1315
|
+
agents?: ComposerAgentOption[];
|
|
1316
|
+
selectedAgent?: string;
|
|
1317
|
+
hostedHarness?: boolean;
|
|
1239
1318
|
engines: Array<{
|
|
1240
1319
|
engine: string;
|
|
1241
1320
|
label: string;
|
|
@@ -1248,18 +1327,25 @@ function ModelSelector({
|
|
|
1248
1327
|
modelListLoading?: boolean;
|
|
1249
1328
|
onChange: (model: string, engine: string) => void;
|
|
1250
1329
|
onEffortChange?: (effort: ReasoningEffort) => void;
|
|
1330
|
+
onAgentChange?: (agent: string) => void;
|
|
1251
1331
|
providerConnectStatusEnabled?: boolean;
|
|
1252
1332
|
onConnectProvider?: () => void;
|
|
1253
1333
|
onConnectLocalRuntime?: (engine: string) => void;
|
|
1334
|
+
onModelSelectorOpenChange?: (open: boolean) => void;
|
|
1254
1335
|
imageModel?: ComposerImageModelMenu;
|
|
1336
|
+
open?: boolean;
|
|
1255
1337
|
}) {
|
|
1256
1338
|
const adapters = useComposerRuntimeAdapters();
|
|
1257
1339
|
const t = adapters.translate!;
|
|
1258
1340
|
const reasoning = adapters.models?.reasoning;
|
|
1259
1341
|
const defaultEffort = reasoning?.defaultEffort ?? DEFAULT_REASONING_EFFORT;
|
|
1260
|
-
const [
|
|
1342
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
1343
|
+
const open = controlledOpen ?? internalOpen;
|
|
1344
|
+
const isClaudeCodeAgent = isClaudeCodeAgentId(selectedAgent);
|
|
1261
1345
|
const autoModelGroup = showAutoModelOption
|
|
1262
|
-
?
|
|
1346
|
+
? isClaudeCodeAgent
|
|
1347
|
+
? undefined
|
|
1348
|
+
: engines.find((group) => group.models.includes("auto"))
|
|
1263
1349
|
: undefined;
|
|
1264
1350
|
const providerGroups = useMemo(
|
|
1265
1351
|
() =>
|
|
@@ -1271,6 +1357,60 @@ function ModelSelector({
|
|
|
1271
1357
|
.filter((group) => group.models.length > 0),
|
|
1272
1358
|
[engines],
|
|
1273
1359
|
);
|
|
1360
|
+
const isCodexAgent =
|
|
1361
|
+
selectedAgent === "codex" || selectedAgent === "codex-cli";
|
|
1362
|
+
const modelProviderGroups = useMemo(() => {
|
|
1363
|
+
const hasCodexLocalGroup = providerGroups.some(
|
|
1364
|
+
(group) => group.engine === "codex-cli",
|
|
1365
|
+
);
|
|
1366
|
+
const groups = providerGroups.flatMap((group) => {
|
|
1367
|
+
if (group.engine === "codex-cli") {
|
|
1368
|
+
return isCodexAgent && isOpenAiModelProviderGroup(group) ? [group] : [];
|
|
1369
|
+
}
|
|
1370
|
+
if (
|
|
1371
|
+
isCodexAgent &&
|
|
1372
|
+
hasCodexLocalGroup &&
|
|
1373
|
+
group.engine === "ai-sdk:openai"
|
|
1374
|
+
) {
|
|
1375
|
+
return [];
|
|
1376
|
+
}
|
|
1377
|
+
if (LOCAL_RUNTIME_ENGINES.has(group.engine)) return [];
|
|
1378
|
+
if (!isCodexAgent) return [group];
|
|
1379
|
+
if (!isOpenAiModelProviderGroup(group)) return [];
|
|
1380
|
+
|
|
1381
|
+
const isExplicitOpenAiProvider =
|
|
1382
|
+
group.engine.toLowerCase().includes("openai") ||
|
|
1383
|
+
group.label.toLowerCase().includes("openai");
|
|
1384
|
+
const models = isExplicitOpenAiProvider
|
|
1385
|
+
? group.models
|
|
1386
|
+
: group.models.filter(isOpenAiModelId);
|
|
1387
|
+
return models.length > 0 ? [{ ...group, models }] : [];
|
|
1388
|
+
});
|
|
1389
|
+
if (!isClaudeCodeAgent) return groups;
|
|
1390
|
+
return groups
|
|
1391
|
+
.map((group) => ({
|
|
1392
|
+
...group,
|
|
1393
|
+
models: group.models.filter((candidate) => !isLunaModel(candidate)),
|
|
1394
|
+
}))
|
|
1395
|
+
.filter((group) => group.models.length > 0);
|
|
1396
|
+
}, [isClaudeCodeAgent, isCodexAgent, providerGroups]);
|
|
1397
|
+
const preferredAgentModel = useMemo(
|
|
1398
|
+
() => resolvePreferredAgentModel(selectedAgent, providerGroups),
|
|
1399
|
+
[providerGroups, selectedAgent],
|
|
1400
|
+
);
|
|
1401
|
+
useEffect(() => {
|
|
1402
|
+
if (!isClaudeCodeAgent || !isLunaModel(model) || !preferredAgentModel) {
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
if (
|
|
1406
|
+
preferredAgentModel.model === model &&
|
|
1407
|
+
preferredAgentModel.engine ===
|
|
1408
|
+
engines.find((group) => group.models.includes(model))?.engine
|
|
1409
|
+
) {
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
onChange(preferredAgentModel.model, preferredAgentModel.engine);
|
|
1413
|
+
}, [engines, isClaudeCodeAgent, model, onChange, preferredAgentModel]);
|
|
1274
1414
|
const effortOptions =
|
|
1275
1415
|
reasoning?.getOptionsForModel?.(model) ??
|
|
1276
1416
|
getComposerReasoningEffortOptions(model);
|
|
@@ -1286,61 +1426,35 @@ function ModelSelector({
|
|
|
1286
1426
|
),
|
|
1287
1427
|
[reasoning?.label, t],
|
|
1288
1428
|
);
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
// currently-selected model stays expanded so the user sees their pick at
|
|
1292
|
-
// a glance; clicking another family's header expands it inline.
|
|
1293
|
-
const selectedGroupKey = useMemo(() => {
|
|
1294
|
-
const found = providerGroups.find((g) => g.models.includes(model));
|
|
1295
|
-
return found ? `${found.engine}:${found.label}` : null;
|
|
1296
|
-
}, [model, providerGroups]);
|
|
1297
|
-
|
|
1298
|
-
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(
|
|
1299
|
-
() => new Set(selectedGroupKey ? [selectedGroupKey] : []),
|
|
1429
|
+
const selectedAgentOption = agents?.find(
|
|
1430
|
+
(agent) => agent.id === (selectedAgent ?? "default"),
|
|
1300
1431
|
);
|
|
1432
|
+
const selectedAgentLabel = selectedAgentOption?.label ?? "Default";
|
|
1433
|
+
const selectedModelLabel = friendlyModelName(model, t).replace(/^GPT-/, "");
|
|
1301
1434
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
setExpandedGroups(new Set(selectedGroupKey ? [selectedGroupKey] : []));
|
|
1307
|
-
}
|
|
1308
|
-
}, [open, selectedGroupKey]);
|
|
1309
|
-
|
|
1310
|
-
const toggleGroup = useCallback((key: string) => {
|
|
1311
|
-
setExpandedGroups((prev) => {
|
|
1312
|
-
const next = new Set(prev);
|
|
1313
|
-
if (next.has(key)) next.delete(key);
|
|
1314
|
-
else next.add(key);
|
|
1315
|
-
return next;
|
|
1316
|
-
});
|
|
1317
|
-
}, []);
|
|
1435
|
+
const [detailSection, setDetailSection] = useState<
|
|
1436
|
+
"agent" | "model" | "effort" | null
|
|
1437
|
+
>(null);
|
|
1438
|
+
const resolvedSection = detailSection ?? "model";
|
|
1318
1439
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1440
|
+
const setPickerOpen = useCallback(
|
|
1441
|
+
(nextOpen: boolean) => {
|
|
1442
|
+
if (controlledOpen === undefined) setInternalOpen(nextOpen);
|
|
1443
|
+
if (nextOpen) setDetailSection(null);
|
|
1444
|
+
onModelSelectorOpenChange?.(nextOpen);
|
|
1445
|
+
},
|
|
1446
|
+
[controlledOpen, onModelSelectorOpenChange],
|
|
1447
|
+
);
|
|
1326
1448
|
|
|
1327
|
-
|
|
1328
|
-
// pattern; the current selection shows next to the header.
|
|
1329
|
-
const [imageExpanded, setImageExpanded] = useState(false);
|
|
1330
|
-
useEffect(() => {
|
|
1331
|
-
if (open) setImageExpanded(false);
|
|
1332
|
-
}, [open]);
|
|
1333
|
-
const imageModelLabel =
|
|
1334
|
-
imageModel?.options.find((option) => option.value === imageModel.value)
|
|
1335
|
-
?.label ?? imageModel?.value;
|
|
1449
|
+
const visibleProviderGroups = modelProviderGroups;
|
|
1336
1450
|
const showModelListSkeleton = shouldShowModelSelectorSkeleton(
|
|
1337
1451
|
modelListLoading,
|
|
1338
1452
|
engines.length,
|
|
1339
1453
|
);
|
|
1340
1454
|
|
|
1341
|
-
//
|
|
1342
|
-
//
|
|
1343
|
-
//
|
|
1455
|
+
// Keep setup actions beside model choices while any visible provider still
|
|
1456
|
+
// needs configuration. The model rows remain visible so the user can see
|
|
1457
|
+
// what becomes available after connecting or adding a key.
|
|
1344
1458
|
const builderFlow = adapters.builder!.useConnectFlow!({
|
|
1345
1459
|
enabled: providerConnectStatusEnabled,
|
|
1346
1460
|
trackingSource: "composer_builder_cta",
|
|
@@ -1351,34 +1465,39 @@ function ModelSelector({
|
|
|
1351
1465
|
const hasConnectedSubscription = providerGroups.some(
|
|
1352
1466
|
(group) => group.configured && group.isSubscription,
|
|
1353
1467
|
);
|
|
1354
|
-
const
|
|
1355
|
-
(
|
|
1356
|
-
(!providerConnectStatusEnabled && !!onConnectProvider)) &&
|
|
1357
|
-
!builderFlow.configured &&
|
|
1358
|
-
!builderFlow.envManaged &&
|
|
1359
|
-
!hasConfiguredBuilderModels &&
|
|
1360
|
-
!hasConnectedSubscription;
|
|
1361
|
-
const onlyConnectPathAvailable = shouldShowOnlyConnectPath(
|
|
1362
|
-
showBuilderCta,
|
|
1363
|
-
providerGroups,
|
|
1468
|
+
const hasUnconfiguredVisibleModels = modelProviderGroups.some(
|
|
1469
|
+
(group) => !group.configured,
|
|
1364
1470
|
);
|
|
1471
|
+
const showBuilderAction =
|
|
1472
|
+
hasUnconfiguredVisibleModels &&
|
|
1473
|
+
(Boolean(onConnectProvider) ||
|
|
1474
|
+
(providerConnectStatusEnabled &&
|
|
1475
|
+
!builderFlow.configured &&
|
|
1476
|
+
!builderFlow.envManaged &&
|
|
1477
|
+
!hasConfiguredBuilderModels &&
|
|
1478
|
+
!hasConnectedSubscription));
|
|
1479
|
+
const showAddKeysAction = hasUnconfiguredVisibleModels;
|
|
1480
|
+
const showProviderActions = showBuilderAction || showAddKeysAction;
|
|
1481
|
+
const onlyConnectPathAvailable =
|
|
1482
|
+
shouldShowOnlyConnectPath(showBuilderAction, providerGroups) &&
|
|
1483
|
+
modelProviderGroups.length === 0;
|
|
1365
1484
|
const openLlmSettings = useCallback(() => {
|
|
1366
1485
|
try {
|
|
1367
1486
|
window.location.hash = "llm";
|
|
1368
1487
|
} catch {}
|
|
1369
1488
|
window.dispatchEvent(new CustomEvent("agent-panel:open-settings"));
|
|
1370
|
-
|
|
1371
|
-
}, []);
|
|
1489
|
+
setPickerOpen(false);
|
|
1490
|
+
}, [setPickerOpen]);
|
|
1372
1491
|
const connectLocalRuntime = useCallback(
|
|
1373
1492
|
(engine: string) => {
|
|
1374
1493
|
onConnectLocalRuntime?.(engine);
|
|
1375
|
-
|
|
1494
|
+
setPickerOpen(false);
|
|
1376
1495
|
},
|
|
1377
|
-
[onConnectLocalRuntime],
|
|
1496
|
+
[onConnectLocalRuntime, setPickerOpen],
|
|
1378
1497
|
);
|
|
1379
1498
|
|
|
1380
1499
|
return (
|
|
1381
|
-
<Popover open={open} onOpenChange={
|
|
1500
|
+
<Popover open={open} onOpenChange={setPickerOpen}>
|
|
1382
1501
|
<PopoverTrigger asChild>
|
|
1383
1502
|
<button
|
|
1384
1503
|
type="button"
|
|
@@ -1387,15 +1506,21 @@ function ModelSelector({
|
|
|
1387
1506
|
defaultValue: "Model",
|
|
1388
1507
|
})}: ${friendlyModelName(model, t)}${
|
|
1389
1508
|
effortOptions.length > 0
|
|
1390
|
-
? `. ${t("agentChat.composer.
|
|
1391
|
-
defaultValue: "
|
|
1509
|
+
? `. ${t("agentChat.composer.effort", {
|
|
1510
|
+
defaultValue: "Effort",
|
|
1392
1511
|
})}: ${effortLabel(selectedEffort)}`
|
|
1393
1512
|
: ""
|
|
1513
|
+
}${
|
|
1514
|
+
selectedAgentOption && selectedAgentOption.id !== "default"
|
|
1515
|
+
? `. Agent: ${selectedAgentLabel}`
|
|
1516
|
+
: ""
|
|
1394
1517
|
}`}
|
|
1395
1518
|
className="agent-composer-model-button flex min-w-0 max-w-[10.5rem] shrink items-center gap-1 rounded-md px-2 py-1 text-[12px] font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground"
|
|
1396
1519
|
>
|
|
1397
1520
|
<span className="min-w-0 truncate">
|
|
1398
|
-
{
|
|
1521
|
+
{selectedAgentOption && selectedAgentOption.id !== "default"
|
|
1522
|
+
? selectedAgentLabel
|
|
1523
|
+
: compactComposerModelName(model, t)}
|
|
1399
1524
|
</span>
|
|
1400
1525
|
{effortOptions.length > 0 && (
|
|
1401
1526
|
<span className="agent-composer-model-effort min-w-0 shrink truncate text-muted-foreground/70">
|
|
@@ -1411,292 +1536,527 @@ function ModelSelector({
|
|
|
1411
1536
|
sideOffset={6}
|
|
1412
1537
|
collisionPadding={8}
|
|
1413
1538
|
data-agent-native-composer-popover="true"
|
|
1414
|
-
className=
|
|
1539
|
+
className={`z-[260] overflow-visible ${MODEL_SELECTOR_POPOVER_CLASS}`}
|
|
1415
1540
|
style={MODEL_SELECTOR_POPOVER_STYLE}
|
|
1416
1541
|
>
|
|
1417
|
-
{
|
|
1418
|
-
|
|
1419
|
-
<
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
builderFlow.start();
|
|
1426
|
-
}
|
|
1427
|
-
}}
|
|
1428
|
-
disabled={!onConnectProvider && builderFlow.connecting}
|
|
1429
|
-
className="flex w-full items-start gap-2 px-3 py-2 text-start hover:bg-accent/50 disabled:opacity-60"
|
|
1430
|
-
>
|
|
1431
|
-
<IconPlugConnected className="h-4 w-4 shrink-0 mt-0.5 text-blue-500" />
|
|
1432
|
-
<span className="flex-1 min-w-0">
|
|
1433
|
-
<span className="block text-[12px] font-medium text-foreground">
|
|
1434
|
-
{!onConnectProvider && builderFlow.connecting
|
|
1435
|
-
? t("agentPanel.connectingBuilder", {
|
|
1436
|
-
defaultValue: "Connecting Builder.io…",
|
|
1437
|
-
})
|
|
1438
|
-
: t("agentPanel.connectBuilderIo", {
|
|
1439
|
-
defaultValue: "Connect Builder.io",
|
|
1440
|
-
})}
|
|
1441
|
-
</span>
|
|
1442
|
-
<span className="block text-[11px] text-muted-foreground">
|
|
1443
|
-
{t("agentPanel.builderModelCredits", {
|
|
1444
|
-
defaultValue: "Free credits for Claude, OpenAI & Gemini",
|
|
1445
|
-
})}
|
|
1446
|
-
</span>
|
|
1447
|
-
</span>
|
|
1448
|
-
</button>
|
|
1449
|
-
{!onConnectProvider && builderFlow.error && (
|
|
1450
|
-
<p
|
|
1451
|
-
role="alert"
|
|
1452
|
-
className="px-3 pb-2 ps-9 text-[11px] text-destructive"
|
|
1453
|
-
>
|
|
1454
|
-
{builderFlow.error}
|
|
1455
|
-
</p>
|
|
1456
|
-
)}
|
|
1457
|
-
<button
|
|
1458
|
-
type="button"
|
|
1459
|
-
onClick={openLlmSettings}
|
|
1460
|
-
className="flex w-full items-start gap-2 px-3 py-2 text-start hover:bg-accent/50"
|
|
1542
|
+
<Popover open={detailSection !== null} onOpenChange={() => undefined}>
|
|
1543
|
+
<PopoverAnchor asChild>
|
|
1544
|
+
<div
|
|
1545
|
+
className="flex flex-col"
|
|
1546
|
+
role="tablist"
|
|
1547
|
+
aria-label={t("agentChat.composer.pickerSections", {
|
|
1548
|
+
defaultValue: "Picker sections",
|
|
1549
|
+
})}
|
|
1461
1550
|
>
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1551
|
+
{agents && agents.length > 0 && (
|
|
1552
|
+
<button
|
|
1553
|
+
type="button"
|
|
1554
|
+
role="tab"
|
|
1555
|
+
aria-selected={resolvedSection === "agent"}
|
|
1556
|
+
onClick={() => setDetailSection("agent")}
|
|
1557
|
+
onMouseEnter={() => setDetailSection("agent")}
|
|
1558
|
+
className={`flex w-full min-w-0 items-center gap-1 rounded-md px-2 py-2 text-start transition-colors ${
|
|
1559
|
+
resolvedSection === "agent"
|
|
1560
|
+
? "bg-accent text-foreground"
|
|
1561
|
+
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground"
|
|
1562
|
+
}`}
|
|
1563
|
+
>
|
|
1564
|
+
<span className="flex min-w-0 items-center">
|
|
1565
|
+
<span className="shrink-0 text-[12px] font-medium">
|
|
1566
|
+
Agent
|
|
1567
|
+
</span>
|
|
1568
|
+
<Tooltip>
|
|
1569
|
+
<TooltipTrigger asChild>
|
|
1570
|
+
<span
|
|
1571
|
+
role="img"
|
|
1572
|
+
aria-label={t("agentChat.composer.harnessAgentHelp", {
|
|
1573
|
+
defaultValue: "Learn about harness agents",
|
|
1574
|
+
})}
|
|
1575
|
+
onClick={(event) => event.stopPropagation()}
|
|
1576
|
+
className={`ms-1.5 ${PICKER_HELP_BUTTON_CLASS}`}
|
|
1577
|
+
>
|
|
1578
|
+
<IconHelpCircle
|
|
1579
|
+
aria-hidden="true"
|
|
1580
|
+
className={PICKER_HELP_ICON_CLASS}
|
|
1581
|
+
strokeWidth={1.8}
|
|
1582
|
+
/>
|
|
1583
|
+
</span>
|
|
1584
|
+
</TooltipTrigger>
|
|
1585
|
+
<TooltipContent side="right" className="z-[400] max-w-xs">
|
|
1586
|
+
<span className="block">
|
|
1587
|
+
{hostedHarness
|
|
1588
|
+
? t("agentChat.composer.hostedHarnessDescription", {
|
|
1589
|
+
defaultValue:
|
|
1590
|
+
"Hosted mode uses app tools only. For full coding with a repository and shell, use Agent Native Desktop.",
|
|
1591
|
+
})
|
|
1592
|
+
: t("agentChat.composer.harnessAgentDescription", {
|
|
1593
|
+
defaultValue:
|
|
1594
|
+
"Harnesses run their own coding loop and local tools.",
|
|
1595
|
+
})}
|
|
1596
|
+
</span>
|
|
1597
|
+
<a
|
|
1598
|
+
href={HARNESS_AGENTS_DOCS_URL}
|
|
1599
|
+
target="_blank"
|
|
1600
|
+
rel="noreferrer"
|
|
1601
|
+
className="mt-1 block underline underline-offset-2 hover:no-underline"
|
|
1602
|
+
>
|
|
1603
|
+
{t("agentChat.composer.learnMoreHarnessAgents", {
|
|
1604
|
+
defaultValue: "Learn more about harness agents",
|
|
1605
|
+
})}
|
|
1606
|
+
</a>
|
|
1607
|
+
</TooltipContent>
|
|
1608
|
+
</Tooltip>
|
|
1609
|
+
</span>
|
|
1610
|
+
<span className="ms-auto min-w-0 max-w-[6rem] truncate text-end text-[11px] text-muted-foreground/80">
|
|
1611
|
+
{selectedAgentLabel}
|
|
1612
|
+
</span>
|
|
1613
|
+
<IconChevronRight className="h-3 w-3 shrink-0 opacity-60 rtl:-scale-x-100" />
|
|
1614
|
+
</button>
|
|
1615
|
+
)}
|
|
1484
1616
|
<button
|
|
1485
1617
|
type="button"
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1618
|
+
role="tab"
|
|
1619
|
+
aria-selected={resolvedSection === "model"}
|
|
1620
|
+
onClick={() => setDetailSection("model")}
|
|
1621
|
+
onMouseEnter={() => setDetailSection("model")}
|
|
1622
|
+
className={`flex w-full min-w-0 items-center gap-1 rounded-md px-2 py-2 text-start transition-colors ${
|
|
1623
|
+
resolvedSection === "model"
|
|
1624
|
+
? "bg-accent text-foreground"
|
|
1625
|
+
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground"
|
|
1626
|
+
}`}
|
|
1489
1627
|
>
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
<IconChevronRight className="h-3 w-3 shrink-0 text-muted-foreground rtl:-scale-x-100" />
|
|
1494
|
-
)}
|
|
1495
|
-
<span className="text-[11px] font-medium text-muted-foreground uppercase tracking-wide shrink-0">
|
|
1496
|
-
{imageModel.label ??
|
|
1497
|
-
t("agentChat.composer.imageModel", {
|
|
1498
|
-
defaultValue: "Image model",
|
|
1499
|
-
})}
|
|
1628
|
+
<span className="shrink-0 text-[12px] font-medium">Model</span>
|
|
1629
|
+
<span className="ms-auto min-w-0 max-w-[6rem] truncate text-end text-[11px] text-muted-foreground/80">
|
|
1630
|
+
{selectedModelLabel}
|
|
1500
1631
|
</span>
|
|
1501
|
-
|
|
1502
|
-
<span className="text-[11px] text-muted-foreground/80 truncate">
|
|
1503
|
-
{imageModelLabel}
|
|
1504
|
-
</span>
|
|
1505
|
-
)}
|
|
1632
|
+
<IconChevronRight className="h-3 w-3 shrink-0 opacity-60 rtl:-scale-x-100" />
|
|
1506
1633
|
</button>
|
|
1507
|
-
|
|
1508
|
-
{imageExpanded &&
|
|
1509
|
-
imageModel.options.map((option) => (
|
|
1634
|
+
{effortOptions.length > 0 && (
|
|
1510
1635
|
<button
|
|
1511
|
-
key={option.value}
|
|
1512
1636
|
type="button"
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
}
|
|
1517
|
-
className=
|
|
1637
|
+
role="tab"
|
|
1638
|
+
aria-selected={resolvedSection === "effort"}
|
|
1639
|
+
onClick={() => setDetailSection("effort")}
|
|
1640
|
+
onMouseEnter={() => setDetailSection("effort")}
|
|
1641
|
+
className={`flex w-full min-w-0 items-center gap-1 rounded-md px-2 py-2 text-start transition-colors ${
|
|
1642
|
+
resolvedSection === "effort"
|
|
1643
|
+
? "bg-accent text-foreground"
|
|
1644
|
+
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground"
|
|
1645
|
+
}`}
|
|
1518
1646
|
>
|
|
1519
|
-
<span className="
|
|
1520
|
-
{
|
|
1647
|
+
<span className="shrink-0 text-[12px] font-medium">
|
|
1648
|
+
{t("agentChat.composer.effort", {
|
|
1649
|
+
defaultValue: "Effort",
|
|
1650
|
+
})}
|
|
1651
|
+
</span>
|
|
1652
|
+
<span className="ms-auto min-w-0 max-w-[6rem] truncate text-end text-[11px] text-muted-foreground/80">
|
|
1653
|
+
{effortLabel(selectedEffort)}
|
|
1521
1654
|
</span>
|
|
1522
|
-
|
|
1523
|
-
<IconCheck className="h-3.5 w-3.5 shrink-0 text-blue-500" />
|
|
1524
|
-
)}
|
|
1655
|
+
<IconChevronRight className="h-3 w-3 shrink-0 opacity-60 rtl:-scale-x-100" />
|
|
1525
1656
|
</button>
|
|
1526
|
-
)
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
}}
|
|
1538
|
-
className="flex w-full items-center gap-3 px-3 py-1.5 text-start hover:bg-accent/50"
|
|
1657
|
+
)}
|
|
1658
|
+
</div>
|
|
1659
|
+
</PopoverAnchor>
|
|
1660
|
+
<PopoverContent
|
|
1661
|
+
side="right"
|
|
1662
|
+
align="start"
|
|
1663
|
+
sideOffset={4}
|
|
1664
|
+
alignOffset={-8}
|
|
1665
|
+
collisionPadding={8}
|
|
1666
|
+
className={`z-[320] overflow-visible ${MODEL_SELECTOR_POPOVER_CLASS}`}
|
|
1667
|
+
style={MODEL_SELECTOR_POPOVER_STYLE}
|
|
1539
1668
|
>
|
|
1540
|
-
<
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1669
|
+
<div className="max-h-[min(500px,var(--radix-popover-content-available-height,500px))] overflow-y-auto">
|
|
1670
|
+
<div
|
|
1671
|
+
className="min-h-0 min-w-0"
|
|
1672
|
+
role="tabpanel"
|
|
1673
|
+
aria-label={resolvedSection}
|
|
1674
|
+
>
|
|
1675
|
+
{resolvedSection === "agent" && agents && (
|
|
1676
|
+
<div className="flex flex-col">
|
|
1677
|
+
{agents.map((agent) => {
|
|
1678
|
+
const isSelected =
|
|
1679
|
+
agent.id === (selectedAgent ?? "default");
|
|
1680
|
+
const isConfigured = agent.configured !== false;
|
|
1681
|
+
const canConnect =
|
|
1682
|
+
!isConfigured && Boolean(onConnectLocalRuntime);
|
|
1683
|
+
const statusLabel =
|
|
1684
|
+
agent.statusLabel ??
|
|
1685
|
+
(!isConfigured ? "Unavailable" : undefined);
|
|
1686
|
+
return (
|
|
1687
|
+
<div
|
|
1688
|
+
key={agent.id}
|
|
1689
|
+
className="group flex items-center rounded-md hover:bg-accent/30"
|
|
1690
|
+
>
|
|
1691
|
+
<button
|
|
1692
|
+
type="button"
|
|
1693
|
+
disabled={!isConfigured || !onAgentChange}
|
|
1694
|
+
aria-current={isSelected ? "true" : undefined}
|
|
1695
|
+
onClick={() => {
|
|
1696
|
+
if (!isConfigured) return;
|
|
1697
|
+
onAgentChange?.(agent.id);
|
|
1698
|
+
setPickerOpen(false);
|
|
1699
|
+
}}
|
|
1700
|
+
className={`flex min-w-0 flex-1 items-center gap-0.5 px-2 py-2 text-start ${
|
|
1701
|
+
isConfigured
|
|
1702
|
+
? "hover:bg-accent/50"
|
|
1703
|
+
: "cursor-default opacity-50"
|
|
1704
|
+
}`}
|
|
1705
|
+
>
|
|
1706
|
+
<span
|
|
1707
|
+
className={`min-w-0 truncate text-[12px] ${
|
|
1708
|
+
isSelected
|
|
1709
|
+
? "text-foreground"
|
|
1710
|
+
: "text-muted-foreground"
|
|
1711
|
+
}`}
|
|
1712
|
+
>
|
|
1713
|
+
{agent.label}
|
|
1714
|
+
</span>
|
|
1715
|
+
{agent.description && (
|
|
1716
|
+
<Tooltip>
|
|
1717
|
+
<TooltipTrigger asChild>
|
|
1718
|
+
<span
|
|
1719
|
+
role="img"
|
|
1720
|
+
aria-label={`${agent.label} details`}
|
|
1721
|
+
onClick={(event) => event.stopPropagation()}
|
|
1722
|
+
className={PICKER_HELP_BUTTON_CLASS}
|
|
1723
|
+
>
|
|
1724
|
+
<IconHelpCircle
|
|
1725
|
+
aria-hidden="true"
|
|
1726
|
+
className={PICKER_HELP_ICON_CLASS}
|
|
1727
|
+
strokeWidth={1.8}
|
|
1728
|
+
/>
|
|
1729
|
+
</span>
|
|
1730
|
+
</TooltipTrigger>
|
|
1731
|
+
<TooltipContent
|
|
1732
|
+
side="left"
|
|
1733
|
+
className="z-[400] max-w-xs"
|
|
1734
|
+
>
|
|
1735
|
+
{agent.description}
|
|
1736
|
+
</TooltipContent>
|
|
1737
|
+
</Tooltip>
|
|
1738
|
+
)}
|
|
1739
|
+
<span
|
|
1740
|
+
className="min-w-0 flex-1"
|
|
1741
|
+
aria-hidden="true"
|
|
1742
|
+
/>
|
|
1743
|
+
{isSelected && isConfigured && (
|
|
1744
|
+
<IconCheck className="size-4 shrink-0 text-primary" />
|
|
1745
|
+
)}
|
|
1746
|
+
</button>
|
|
1747
|
+
{!isConfigured && statusLabel && (
|
|
1748
|
+
<button
|
|
1749
|
+
type="button"
|
|
1750
|
+
disabled={!canConnect}
|
|
1751
|
+
className="ms-auto max-w-[6rem] shrink-0 truncate px-2 py-2 text-end text-[10px] text-muted-foreground/70 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 hover:text-foreground focus-visible:opacity-100 disabled:cursor-default"
|
|
1752
|
+
onClick={() => {
|
|
1753
|
+
if (canConnect) connectLocalRuntime(agent.id);
|
|
1754
|
+
}}
|
|
1755
|
+
>
|
|
1756
|
+
{statusLabel}
|
|
1757
|
+
</button>
|
|
1758
|
+
)}
|
|
1759
|
+
</div>
|
|
1760
|
+
);
|
|
1761
|
+
})}
|
|
1762
|
+
</div>
|
|
1763
|
+
)}
|
|
1764
|
+
{resolvedSection === "model" && (
|
|
1765
|
+
<>
|
|
1766
|
+
{showProviderActions && (
|
|
1767
|
+
<>
|
|
1768
|
+
{showBuilderAction && (
|
|
1769
|
+
<>
|
|
1770
|
+
<button
|
|
1771
|
+
type="button"
|
|
1772
|
+
onClick={() => {
|
|
1773
|
+
if (onConnectProvider) {
|
|
1774
|
+
onConnectProvider();
|
|
1775
|
+
} else {
|
|
1776
|
+
builderFlow.start();
|
|
1777
|
+
}
|
|
1778
|
+
}}
|
|
1779
|
+
disabled={
|
|
1780
|
+
!onConnectProvider && builderFlow.connecting
|
|
1781
|
+
}
|
|
1782
|
+
className="flex w-full items-start gap-2 rounded-md px-2 py-2 text-start hover:bg-accent/50 disabled:opacity-60"
|
|
1783
|
+
>
|
|
1784
|
+
<IconPlugConnected className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
|
|
1785
|
+
<span className="min-w-0 flex-1">
|
|
1786
|
+
<span className="block text-[12px] font-medium text-foreground">
|
|
1787
|
+
{!onConnectProvider && builderFlow.connecting
|
|
1788
|
+
? t("agentPanel.connectingBuilder", {
|
|
1789
|
+
defaultValue: "Connecting Builder.io…",
|
|
1790
|
+
})
|
|
1791
|
+
: t("agentPanel.connectBuilderIo", {
|
|
1792
|
+
defaultValue: "Connect Builder.io",
|
|
1793
|
+
})}
|
|
1794
|
+
</span>
|
|
1795
|
+
<span className="block text-[11px] text-muted-foreground">
|
|
1796
|
+
{t("agentPanel.builderModelCredits", {
|
|
1797
|
+
defaultValue:
|
|
1798
|
+
"Free credits for Claude, OpenAI & Gemini",
|
|
1799
|
+
})}
|
|
1800
|
+
</span>
|
|
1801
|
+
</span>
|
|
1802
|
+
</button>
|
|
1803
|
+
{!onConnectProvider && builderFlow.error && (
|
|
1804
|
+
<p
|
|
1805
|
+
role="alert"
|
|
1806
|
+
className="px-2 pb-2 ps-8 text-[11px] text-destructive"
|
|
1807
|
+
>
|
|
1808
|
+
{builderFlow.error}
|
|
1809
|
+
</p>
|
|
1810
|
+
)}
|
|
1811
|
+
</>
|
|
1812
|
+
)}
|
|
1813
|
+
{showAddKeysAction && (
|
|
1814
|
+
<button
|
|
1815
|
+
type="button"
|
|
1816
|
+
onClick={openLlmSettings}
|
|
1817
|
+
className="flex w-full items-start gap-2 rounded-md px-2 py-2 text-start hover:bg-accent/50"
|
|
1818
|
+
>
|
|
1819
|
+
<IconKey className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
|
|
1820
|
+
<span className="min-w-0 flex-1">
|
|
1821
|
+
<span className="block text-[12px] font-medium text-foreground">
|
|
1822
|
+
{t("agentPanel.addOwnKeys", {
|
|
1823
|
+
defaultValue: "Add your own keys",
|
|
1824
|
+
})}
|
|
1825
|
+
</span>
|
|
1826
|
+
<span className="block text-[11px] text-muted-foreground">
|
|
1827
|
+
{t("agentPanel.configureProviderKeys", {
|
|
1828
|
+
defaultValue:
|
|
1829
|
+
"Choose a cloud, gateway, or local provider",
|
|
1830
|
+
})}
|
|
1831
|
+
</span>
|
|
1832
|
+
</span>
|
|
1833
|
+
</button>
|
|
1834
|
+
)}
|
|
1835
|
+
</>
|
|
1589
1836
|
)}
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
</span>
|
|
1644
|
-
<ModelCostTier model={m} />
|
|
1645
|
-
{m === model && group.configured && (
|
|
1646
|
-
<IconCheck className="h-3.5 w-3.5 shrink-0 text-blue-500" />
|
|
1837
|
+
{imageModel && imageModel.options.length > 0 && (
|
|
1838
|
+
<div className="mt-2 pt-1">
|
|
1839
|
+
<div className="px-2 py-1 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
1840
|
+
{imageModel.label ??
|
|
1841
|
+
t("agentChat.composer.imageModel", {
|
|
1842
|
+
defaultValue: "Image model",
|
|
1843
|
+
})}
|
|
1844
|
+
</div>
|
|
1845
|
+
{imageModel.options.map((option) => (
|
|
1846
|
+
<button
|
|
1847
|
+
key={option.value}
|
|
1848
|
+
type="button"
|
|
1849
|
+
onClick={() => {
|
|
1850
|
+
imageModel.onChange(option.value);
|
|
1851
|
+
setPickerOpen(false);
|
|
1852
|
+
}}
|
|
1853
|
+
className="flex w-full items-center gap-3 rounded-md px-2 py-2 text-start hover:bg-accent/50"
|
|
1854
|
+
>
|
|
1855
|
+
<span
|
|
1856
|
+
className={`min-w-0 flex-1 truncate text-[12px] ${
|
|
1857
|
+
option.value === imageModel.value
|
|
1858
|
+
? "text-foreground"
|
|
1859
|
+
: "text-muted-foreground"
|
|
1860
|
+
}`}
|
|
1861
|
+
>
|
|
1862
|
+
{option.label}
|
|
1863
|
+
</span>
|
|
1864
|
+
{option.value === imageModel.value && (
|
|
1865
|
+
<IconCheck className="size-4 shrink-0 text-primary" />
|
|
1866
|
+
)}
|
|
1867
|
+
</button>
|
|
1868
|
+
))}
|
|
1869
|
+
</div>
|
|
1870
|
+
)}
|
|
1871
|
+
{showModelListSkeleton && <ModelSelectorSkeleton />}
|
|
1872
|
+
{isCodexAgent &&
|
|
1873
|
+
!showModelListSkeleton &&
|
|
1874
|
+
!onlyConnectPathAvailable &&
|
|
1875
|
+
modelProviderGroups.length === 0 && (
|
|
1876
|
+
<button
|
|
1877
|
+
type="button"
|
|
1878
|
+
onClick={openLlmSettings}
|
|
1879
|
+
className="flex w-full items-center rounded-md px-2 py-2 text-start hover:bg-accent/50"
|
|
1880
|
+
>
|
|
1881
|
+
<span className="min-w-0 flex-1 truncate text-[12px] text-muted-foreground">
|
|
1882
|
+
{t("agentChat.composer.noOpenAiModels", {
|
|
1883
|
+
defaultValue: "No OpenAI models configured",
|
|
1884
|
+
})}
|
|
1885
|
+
</span>
|
|
1886
|
+
<span className="shrink-0 text-[11px] text-muted-foreground/70">
|
|
1887
|
+
Configure
|
|
1888
|
+
</span>
|
|
1889
|
+
</button>
|
|
1647
1890
|
)}
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
defaultValue: "Reasoning",
|
|
1673
|
-
})}
|
|
1674
|
-
</span>
|
|
1675
|
-
{!effortExpanded && (
|
|
1676
|
-
<span className="text-[11px] text-muted-foreground/80 truncate">
|
|
1677
|
-
{effortLabel(selectedEffort)}
|
|
1678
|
-
</span>
|
|
1679
|
-
)}
|
|
1680
|
-
</button>
|
|
1681
|
-
</div>
|
|
1682
|
-
{effortExpanded &&
|
|
1683
|
-
effortOptions.map((option) => (
|
|
1684
|
-
<button
|
|
1685
|
-
key={option}
|
|
1686
|
-
type="button"
|
|
1687
|
-
onClick={() => onEffortChange?.(option)}
|
|
1688
|
-
className="flex w-full items-center gap-3 ps-7 pe-3 py-1.5 text-start hover:bg-accent/50"
|
|
1689
|
-
>
|
|
1690
|
-
<span className="flex-1 min-w-0 text-[13px] text-foreground truncate">
|
|
1691
|
-
{effortLabel(option)}
|
|
1692
|
-
</span>
|
|
1693
|
-
{option === selectedEffort && (
|
|
1694
|
-
<IconCheck className="h-3.5 w-3.5 shrink-0 text-blue-500" />
|
|
1891
|
+
{autoModelGroup && !onlyConnectPathAvailable && (
|
|
1892
|
+
<button
|
|
1893
|
+
type="button"
|
|
1894
|
+
onClick={() => {
|
|
1895
|
+
onChange("auto", autoModelGroup.engine);
|
|
1896
|
+
setPickerOpen(false);
|
|
1897
|
+
}}
|
|
1898
|
+
className="mt-1 flex w-full items-center gap-3 rounded-md px-2 py-1.5 text-start hover:bg-accent/50"
|
|
1899
|
+
>
|
|
1900
|
+
<span
|
|
1901
|
+
className={`min-w-0 flex-1 truncate text-[13px] ${
|
|
1902
|
+
model === "auto"
|
|
1903
|
+
? "text-foreground"
|
|
1904
|
+
: "text-muted-foreground"
|
|
1905
|
+
}`}
|
|
1906
|
+
>
|
|
1907
|
+
{t("agentChat.composer.auto", {
|
|
1908
|
+
defaultValue: "Auto",
|
|
1909
|
+
})}
|
|
1910
|
+
</span>
|
|
1911
|
+
{model === "auto" && (
|
|
1912
|
+
<IconCheck className="size-4 shrink-0 text-primary" />
|
|
1913
|
+
)}
|
|
1914
|
+
</button>
|
|
1695
1915
|
)}
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1916
|
+
{!onlyConnectPathAvailable &&
|
|
1917
|
+
visibleProviderGroups.map((group, groupIndex) => {
|
|
1918
|
+
const models = latestModelsOnly(group.models);
|
|
1919
|
+
const showProviderLabels =
|
|
1920
|
+
visibleProviderGroups.length > 1;
|
|
1921
|
+
const isLocalRuntime =
|
|
1922
|
+
group.engine === "codex-cli" ||
|
|
1923
|
+
group.engine === "claude-cli";
|
|
1924
|
+
const canConnectLocalRuntime =
|
|
1925
|
+
isLocalRuntime && Boolean(onConnectLocalRuntime);
|
|
1926
|
+
const statusLabel =
|
|
1927
|
+
group.statusLabel ??
|
|
1928
|
+
(!group.configured
|
|
1929
|
+
? canConnectLocalRuntime
|
|
1930
|
+
? t("agentChat.auth.logIn", {
|
|
1931
|
+
defaultValue: "Sign in",
|
|
1932
|
+
})
|
|
1933
|
+
: t("agentChat.composer.needsApiKey", {
|
|
1934
|
+
defaultValue: "needs API key",
|
|
1935
|
+
})
|
|
1936
|
+
: undefined);
|
|
1937
|
+
return (
|
|
1938
|
+
<div
|
|
1939
|
+
key={`${group.engine}:${group.label}`}
|
|
1940
|
+
className={
|
|
1941
|
+
showProviderLabels && groupIndex > 0
|
|
1942
|
+
? "mt-2 pt-1"
|
|
1943
|
+
: ""
|
|
1944
|
+
}
|
|
1945
|
+
>
|
|
1946
|
+
{showProviderLabels && (
|
|
1947
|
+
<div className="group flex items-center px-2 py-1">
|
|
1948
|
+
<span className="shrink-0 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
1949
|
+
{group.label}
|
|
1950
|
+
</span>
|
|
1951
|
+
{!group.configured && statusLabel && (
|
|
1952
|
+
<button
|
|
1953
|
+
type="button"
|
|
1954
|
+
aria-label={statusLabel}
|
|
1955
|
+
className="ms-auto max-w-[9rem] shrink-0 cursor-pointer truncate px-0 py-1 text-end text-[10px] text-muted-foreground/60 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 hover:text-foreground focus-visible:opacity-100"
|
|
1956
|
+
onClick={() =>
|
|
1957
|
+
canConnectLocalRuntime
|
|
1958
|
+
? connectLocalRuntime(group.engine)
|
|
1959
|
+
: openLlmSettings()
|
|
1960
|
+
}
|
|
1961
|
+
>
|
|
1962
|
+
{statusLabel}
|
|
1963
|
+
</button>
|
|
1964
|
+
)}
|
|
1965
|
+
{group.configured && statusLabel && (
|
|
1966
|
+
<span className="ms-auto max-w-[9rem] shrink-0 truncate py-1 text-end text-[10px] text-muted-foreground/70 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">
|
|
1967
|
+
{statusLabel}
|
|
1968
|
+
</span>
|
|
1969
|
+
)}
|
|
1970
|
+
</div>
|
|
1971
|
+
)}
|
|
1972
|
+
{models.map((m) => {
|
|
1973
|
+
const isSelected =
|
|
1974
|
+
m === model && group.configured;
|
|
1975
|
+
return (
|
|
1976
|
+
<button
|
|
1977
|
+
key={m}
|
|
1978
|
+
type="button"
|
|
1979
|
+
onClick={() => {
|
|
1980
|
+
if (!group.configured) {
|
|
1981
|
+
if (canConnectLocalRuntime) {
|
|
1982
|
+
connectLocalRuntime(group.engine);
|
|
1983
|
+
} else {
|
|
1984
|
+
openLlmSettings();
|
|
1985
|
+
}
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
onChange(m, group.engine);
|
|
1989
|
+
const nextOptions =
|
|
1990
|
+
getReasoningEffortOptionsForModel(m);
|
|
1991
|
+
if (
|
|
1992
|
+
nextOptions.length > 0 &&
|
|
1993
|
+
!nextOptions.includes(selectedEffort)
|
|
1994
|
+
) {
|
|
1995
|
+
onEffortChange?.(defaultEffort);
|
|
1996
|
+
}
|
|
1997
|
+
setPickerOpen(false);
|
|
1998
|
+
}}
|
|
1999
|
+
className={`group flex w-full items-center gap-3 rounded-md px-2 py-2 text-start ${
|
|
2000
|
+
group.configured
|
|
2001
|
+
? "hover:bg-accent/50"
|
|
2002
|
+
: "cursor-default opacity-40"
|
|
2003
|
+
}`}
|
|
2004
|
+
>
|
|
2005
|
+
<span
|
|
2006
|
+
className={`min-w-0 flex-1 truncate text-[12px] ${
|
|
2007
|
+
isSelected
|
|
2008
|
+
? "text-foreground"
|
|
2009
|
+
: "text-muted-foreground"
|
|
2010
|
+
}`}
|
|
2011
|
+
>
|
|
2012
|
+
{friendlyModelName(m, t)}
|
|
2013
|
+
</span>
|
|
2014
|
+
<ModelCostTier model={m} />
|
|
2015
|
+
{!showProviderLabels && statusLabel && (
|
|
2016
|
+
<span className="hidden max-w-[9rem] shrink-0 truncate text-[10px] text-muted-foreground/70 group-hover:inline">
|
|
2017
|
+
{statusLabel}
|
|
2018
|
+
</span>
|
|
2019
|
+
)}
|
|
2020
|
+
{isSelected && (
|
|
2021
|
+
<IconCheck className="size-4 shrink-0 text-primary" />
|
|
2022
|
+
)}
|
|
2023
|
+
</button>
|
|
2024
|
+
);
|
|
2025
|
+
})}
|
|
2026
|
+
</div>
|
|
2027
|
+
);
|
|
2028
|
+
})}
|
|
2029
|
+
</>
|
|
2030
|
+
)}
|
|
2031
|
+
{resolvedSection === "effort" && effortOptions.length > 0 && (
|
|
2032
|
+
<div className="flex flex-col">
|
|
2033
|
+
{effortOptions.map((option) => (
|
|
2034
|
+
<button
|
|
2035
|
+
key={option}
|
|
2036
|
+
type="button"
|
|
2037
|
+
onClick={() => onEffortChange?.(option)}
|
|
2038
|
+
className="flex w-full items-center gap-3 rounded-md px-2 py-2 text-start hover:bg-accent/50"
|
|
2039
|
+
>
|
|
2040
|
+
<span
|
|
2041
|
+
className={`min-w-0 flex-1 truncate text-[12px] ${
|
|
2042
|
+
option === selectedEffort
|
|
2043
|
+
? "text-foreground"
|
|
2044
|
+
: "text-muted-foreground"
|
|
2045
|
+
}`}
|
|
2046
|
+
>
|
|
2047
|
+
{effortLabel(option)}
|
|
2048
|
+
</span>
|
|
2049
|
+
{option === selectedEffort && (
|
|
2050
|
+
<IconCheck className="size-4 shrink-0 text-primary" />
|
|
2051
|
+
)}
|
|
2052
|
+
</button>
|
|
2053
|
+
))}
|
|
2054
|
+
</div>
|
|
2055
|
+
)}
|
|
2056
|
+
</div>
|
|
2057
|
+
</div>
|
|
2058
|
+
</PopoverContent>
|
|
2059
|
+
</Popover>
|
|
1700
2060
|
</PopoverContent>
|
|
1701
2061
|
</Popover>
|
|
1702
2062
|
);
|
|
@@ -1767,10 +2127,16 @@ export function TiptapComposer({
|
|
|
1767
2127
|
selectedModel,
|
|
1768
2128
|
selectedEffort,
|
|
1769
2129
|
showAutoModelOption = true,
|
|
2130
|
+
modelSelectorOpen,
|
|
1770
2131
|
availableModels,
|
|
1771
2132
|
modelListLoading,
|
|
1772
2133
|
onModelChange,
|
|
1773
2134
|
onEffortChange,
|
|
2135
|
+
availableAgents,
|
|
2136
|
+
selectedAgent,
|
|
2137
|
+
hostedHarness,
|
|
2138
|
+
onAgentChange,
|
|
2139
|
+
onModelSelectorOpenChange,
|
|
1774
2140
|
providerConnectStatusEnabled,
|
|
1775
2141
|
onConnectProvider,
|
|
1776
2142
|
onConnectLocalRuntime,
|
|
@@ -3274,12 +3640,18 @@ export function TiptapComposer({
|
|
|
3274
3640
|
{selectedModel && availableModels && onModelChange && (
|
|
3275
3641
|
<ModelSelector
|
|
3276
3642
|
model={selectedModel}
|
|
3643
|
+
open={modelSelectorOpen}
|
|
3277
3644
|
effort={selectedEffort}
|
|
3278
3645
|
engines={availableModels}
|
|
3646
|
+
agents={availableAgents}
|
|
3647
|
+
selectedAgent={selectedAgent}
|
|
3648
|
+
hostedHarness={hostedHarness}
|
|
3279
3649
|
showAutoModelOption={showAutoModelOption}
|
|
3280
3650
|
modelListLoading={modelListLoading}
|
|
3281
3651
|
onChange={onModelChange}
|
|
3282
3652
|
onEffortChange={onEffortChange}
|
|
3653
|
+
onAgentChange={onAgentChange}
|
|
3654
|
+
onModelSelectorOpenChange={onModelSelectorOpenChange}
|
|
3283
3655
|
providerConnectStatusEnabled={providerConnectStatusEnabled}
|
|
3284
3656
|
onConnectProvider={onConnectProvider}
|
|
3285
3657
|
onConnectLocalRuntime={onConnectLocalRuntime}
|