@agent-native/toolkit 0.16.0 → 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/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 +4 -0
- 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 +12 -1
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +249 -162
- 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/package.json +1 -1
- package/src/composer/ComposerPlusMenu.tsx +122 -65
- package/src/composer/PromptComposer.tsx +8 -0
- package/src/composer/TiptapComposer.spec.ts +46 -0
- package/src/composer/TiptapComposer.tsx +678 -412
- package/src/composer/index.ts +6 -0
- package/src/composer/model-selection.spec.ts +45 -0
- package/src/composer/model-selection.ts +51 -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,
|
|
@@ -742,6 +753,8 @@ export interface TiptapComposerProps {
|
|
|
742
753
|
selectedEffort?: ReasoningEffort;
|
|
743
754
|
/** Show the legacy provider-level Auto model option (default: true). */
|
|
744
755
|
showAutoModelOption?: boolean;
|
|
756
|
+
/** Controlled open state for hosts that resize around the model picker. */
|
|
757
|
+
modelSelectorOpen?: boolean;
|
|
745
758
|
/** Available models grouped by provider */
|
|
746
759
|
availableModels?: Array<{
|
|
747
760
|
engine: string;
|
|
@@ -761,8 +774,12 @@ export interface TiptapComposerProps {
|
|
|
761
774
|
availableAgents?: ComposerAgentOption[];
|
|
762
775
|
/** Selected agent runtime identifier. Defaults to the built-in agent. */
|
|
763
776
|
selectedAgent?: string;
|
|
777
|
+
/** Mark the selected runtime as the hosted tools-only harness mode. */
|
|
778
|
+
hostedHarness?: boolean;
|
|
764
779
|
/** Callback when the user picks an agent runtime. */
|
|
765
780
|
onAgentChange?: (agent: string) => void;
|
|
781
|
+
/** Called when the shared model picker opens or closes. */
|
|
782
|
+
onModelSelectorOpenChange?: (open: boolean) => void;
|
|
766
783
|
/**
|
|
767
784
|
* Disable Builder/provider status polling for hosts that supply provider
|
|
768
785
|
* state through another channel, such as Electron IPC.
|
|
@@ -937,7 +954,7 @@ function ModeSelector({
|
|
|
937
954
|
</p>
|
|
938
955
|
</div>
|
|
939
956
|
{mode === "build" && (
|
|
940
|
-
<IconCheck className="
|
|
957
|
+
<IconCheck className="size-4 shrink-0 text-primary" />
|
|
941
958
|
)}
|
|
942
959
|
</button>
|
|
943
960
|
<button
|
|
@@ -969,7 +986,7 @@ function ModeSelector({
|
|
|
969
986
|
</p>
|
|
970
987
|
</div>
|
|
971
988
|
{mode === "plan" && !planModeDisabled && (
|
|
972
|
-
<IconCheck className="
|
|
989
|
+
<IconCheck className="size-4 shrink-0 text-primary" />
|
|
973
990
|
)}
|
|
974
991
|
</button>
|
|
975
992
|
</PopoverContent>
|
|
@@ -980,17 +997,61 @@ function ModeSelector({
|
|
|
980
997
|
const FRIENDLY_MODEL_NAMES: Record<string, string> = {
|
|
981
998
|
auto: "Default model",
|
|
982
999
|
"codex-cli": "Codex",
|
|
1000
|
+
"claude-cli": "Claude Code",
|
|
1001
|
+
"pi-cli": "Pi",
|
|
1002
|
+
"opencode-cli": "OpenCode",
|
|
983
1003
|
"claude-fable-5": "Fable 5",
|
|
984
1004
|
"kimi-k2-5": "Kimi K2.5",
|
|
985
1005
|
"deepseek-v3-1": "DeepSeek v3.1",
|
|
986
1006
|
"z-ai/glm-5.2": "GLM 5.2",
|
|
987
1007
|
};
|
|
988
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
|
+
|
|
989
1044
|
export const MODEL_SELECTOR_POPOVER_STYLE = {
|
|
990
1045
|
fontSize: 13,
|
|
991
1046
|
maxHeight: "min(500px, var(--radix-popover-content-available-height, 500px))",
|
|
992
1047
|
} satisfies React.CSSProperties;
|
|
993
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
|
+
|
|
994
1055
|
export function shouldShowModelSelectorSkeleton(
|
|
995
1056
|
isLoading: boolean,
|
|
996
1057
|
engineCount: number,
|
|
@@ -1201,17 +1262,7 @@ function ModelCostTier({ model }: { model: string }) {
|
|
|
1201
1262
|
: tier === 2
|
|
1202
1263
|
? t("agentChat.composer.costMedium", { defaultValue: "Medium cost" })
|
|
1203
1264
|
: t("agentChat.composer.costHigher", { defaultValue: "Higher cost" });
|
|
1204
|
-
return
|
|
1205
|
-
<>
|
|
1206
|
-
<span
|
|
1207
|
-
aria-hidden="true"
|
|
1208
|
-
className="shrink-0 text-[11px] tabular-nums text-muted-foreground/60"
|
|
1209
|
-
>
|
|
1210
|
-
{"$".repeat(tier)}
|
|
1211
|
-
</span>
|
|
1212
|
-
<span className="sr-only">{costLabel}</span>
|
|
1213
|
-
</>
|
|
1214
|
-
);
|
|
1265
|
+
return <span className="sr-only">{costLabel}</span>;
|
|
1215
1266
|
}
|
|
1216
1267
|
|
|
1217
1268
|
/**
|
|
@@ -1246,11 +1297,14 @@ function ModelSelector({
|
|
|
1246
1297
|
engines,
|
|
1247
1298
|
agents,
|
|
1248
1299
|
selectedAgent,
|
|
1300
|
+
hostedHarness = false,
|
|
1249
1301
|
showAutoModelOption = true,
|
|
1250
1302
|
modelListLoading = false,
|
|
1303
|
+
open: controlledOpen,
|
|
1251
1304
|
onChange,
|
|
1252
1305
|
onEffortChange,
|
|
1253
1306
|
onAgentChange,
|
|
1307
|
+
onModelSelectorOpenChange,
|
|
1254
1308
|
providerConnectStatusEnabled = true,
|
|
1255
1309
|
onConnectProvider,
|
|
1256
1310
|
onConnectLocalRuntime,
|
|
@@ -1260,6 +1314,7 @@ function ModelSelector({
|
|
|
1260
1314
|
effort?: ReasoningEffort;
|
|
1261
1315
|
agents?: ComposerAgentOption[];
|
|
1262
1316
|
selectedAgent?: string;
|
|
1317
|
+
hostedHarness?: boolean;
|
|
1263
1318
|
engines: Array<{
|
|
1264
1319
|
engine: string;
|
|
1265
1320
|
label: string;
|
|
@@ -1276,15 +1331,21 @@ function ModelSelector({
|
|
|
1276
1331
|
providerConnectStatusEnabled?: boolean;
|
|
1277
1332
|
onConnectProvider?: () => void;
|
|
1278
1333
|
onConnectLocalRuntime?: (engine: string) => void;
|
|
1334
|
+
onModelSelectorOpenChange?: (open: boolean) => void;
|
|
1279
1335
|
imageModel?: ComposerImageModelMenu;
|
|
1336
|
+
open?: boolean;
|
|
1280
1337
|
}) {
|
|
1281
1338
|
const adapters = useComposerRuntimeAdapters();
|
|
1282
1339
|
const t = adapters.translate!;
|
|
1283
1340
|
const reasoning = adapters.models?.reasoning;
|
|
1284
1341
|
const defaultEffort = reasoning?.defaultEffort ?? DEFAULT_REASONING_EFFORT;
|
|
1285
|
-
const [
|
|
1342
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
1343
|
+
const open = controlledOpen ?? internalOpen;
|
|
1344
|
+
const isClaudeCodeAgent = isClaudeCodeAgentId(selectedAgent);
|
|
1286
1345
|
const autoModelGroup = showAutoModelOption
|
|
1287
|
-
?
|
|
1346
|
+
? isClaudeCodeAgent
|
|
1347
|
+
? undefined
|
|
1348
|
+
: engines.find((group) => group.models.includes("auto"))
|
|
1288
1349
|
: undefined;
|
|
1289
1350
|
const providerGroups = useMemo(
|
|
1290
1351
|
() =>
|
|
@@ -1296,6 +1357,60 @@ function ModelSelector({
|
|
|
1296
1357
|
.filter((group) => group.models.length > 0),
|
|
1297
1358
|
[engines],
|
|
1298
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]);
|
|
1299
1414
|
const effortOptions =
|
|
1300
1415
|
reasoning?.getOptionsForModel?.(model) ??
|
|
1301
1416
|
getComposerReasoningEffortOptions(model);
|
|
@@ -1315,61 +1430,31 @@ function ModelSelector({
|
|
|
1315
1430
|
(agent) => agent.id === (selectedAgent ?? "default"),
|
|
1316
1431
|
);
|
|
1317
1432
|
const selectedAgentLabel = selectedAgentOption?.label ?? "Default";
|
|
1433
|
+
const selectedModelLabel = friendlyModelName(model, t).replace(/^GPT-/, "");
|
|
1318
1434
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
const
|
|
1323
|
-
const found = providerGroups.find((g) => g.models.includes(model));
|
|
1324
|
-
return found ? `${found.engine}:${found.label}` : null;
|
|
1325
|
-
}, [model, providerGroups]);
|
|
1435
|
+
const [detailSection, setDetailSection] = useState<
|
|
1436
|
+
"agent" | "model" | "effort" | null
|
|
1437
|
+
>(null);
|
|
1438
|
+
const resolvedSection = detailSection ?? "model";
|
|
1326
1439
|
|
|
1327
|
-
const
|
|
1328
|
-
() =>
|
|
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],
|
|
1329
1447
|
);
|
|
1330
1448
|
|
|
1331
|
-
|
|
1332
|
-
// on the "selected family expanded, others collapsed" view.
|
|
1333
|
-
useEffect(() => {
|
|
1334
|
-
if (open) {
|
|
1335
|
-
setExpandedGroups(new Set(selectedGroupKey ? [selectedGroupKey] : []));
|
|
1336
|
-
}
|
|
1337
|
-
}, [open, selectedGroupKey]);
|
|
1338
|
-
|
|
1339
|
-
const toggleGroup = useCallback((key: string) => {
|
|
1340
|
-
setExpandedGroups((prev) => {
|
|
1341
|
-
const next = new Set(prev);
|
|
1342
|
-
if (next.has(key)) next.delete(key);
|
|
1343
|
-
else next.add(key);
|
|
1344
|
-
return next;
|
|
1345
|
-
});
|
|
1346
|
-
}, []);
|
|
1347
|
-
|
|
1348
|
-
// The effort list is collapsed by default — it's a secondary
|
|
1349
|
-
// control most users don't touch, so it stays tucked behind a header that
|
|
1350
|
-
// reveals the current effort at a glance. Reset to collapsed on each open.
|
|
1351
|
-
const [effortExpanded, setEffortExpanded] = useState(false);
|
|
1352
|
-
useEffect(() => {
|
|
1353
|
-
if (open) setEffortExpanded(false);
|
|
1354
|
-
}, [open]);
|
|
1355
|
-
|
|
1356
|
-
// The optional image-model section follows the same collapsed-by-default
|
|
1357
|
-
// pattern; the current selection shows next to the header.
|
|
1358
|
-
const [imageExpanded, setImageExpanded] = useState(false);
|
|
1359
|
-
useEffect(() => {
|
|
1360
|
-
if (open) setImageExpanded(false);
|
|
1361
|
-
}, [open]);
|
|
1362
|
-
const imageModelLabel =
|
|
1363
|
-
imageModel?.options.find((option) => option.value === imageModel.value)
|
|
1364
|
-
?.label ?? imageModel?.value;
|
|
1449
|
+
const visibleProviderGroups = modelProviderGroups;
|
|
1365
1450
|
const showModelListSkeleton = shouldShowModelSelectorSkeleton(
|
|
1366
1451
|
modelListLoading,
|
|
1367
1452
|
engines.length,
|
|
1368
1453
|
);
|
|
1369
1454
|
|
|
1370
|
-
//
|
|
1371
|
-
//
|
|
1372
|
-
//
|
|
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.
|
|
1373
1458
|
const builderFlow = adapters.builder!.useConnectFlow!({
|
|
1374
1459
|
enabled: providerConnectStatusEnabled,
|
|
1375
1460
|
trackingSource: "composer_builder_cta",
|
|
@@ -1380,34 +1465,39 @@ function ModelSelector({
|
|
|
1380
1465
|
const hasConnectedSubscription = providerGroups.some(
|
|
1381
1466
|
(group) => group.configured && group.isSubscription,
|
|
1382
1467
|
);
|
|
1383
|
-
const
|
|
1384
|
-
(
|
|
1385
|
-
(!providerConnectStatusEnabled && !!onConnectProvider)) &&
|
|
1386
|
-
!builderFlow.configured &&
|
|
1387
|
-
!builderFlow.envManaged &&
|
|
1388
|
-
!hasConfiguredBuilderModels &&
|
|
1389
|
-
!hasConnectedSubscription;
|
|
1390
|
-
const onlyConnectPathAvailable = shouldShowOnlyConnectPath(
|
|
1391
|
-
showBuilderCta,
|
|
1392
|
-
providerGroups,
|
|
1468
|
+
const hasUnconfiguredVisibleModels = modelProviderGroups.some(
|
|
1469
|
+
(group) => !group.configured,
|
|
1393
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;
|
|
1394
1484
|
const openLlmSettings = useCallback(() => {
|
|
1395
1485
|
try {
|
|
1396
1486
|
window.location.hash = "llm";
|
|
1397
1487
|
} catch {}
|
|
1398
1488
|
window.dispatchEvent(new CustomEvent("agent-panel:open-settings"));
|
|
1399
|
-
|
|
1400
|
-
}, []);
|
|
1489
|
+
setPickerOpen(false);
|
|
1490
|
+
}, [setPickerOpen]);
|
|
1401
1491
|
const connectLocalRuntime = useCallback(
|
|
1402
1492
|
(engine: string) => {
|
|
1403
1493
|
onConnectLocalRuntime?.(engine);
|
|
1404
|
-
|
|
1494
|
+
setPickerOpen(false);
|
|
1405
1495
|
},
|
|
1406
|
-
[onConnectLocalRuntime],
|
|
1496
|
+
[onConnectLocalRuntime, setPickerOpen],
|
|
1407
1497
|
);
|
|
1408
1498
|
|
|
1409
1499
|
return (
|
|
1410
|
-
<Popover open={open} onOpenChange={
|
|
1500
|
+
<Popover open={open} onOpenChange={setPickerOpen}>
|
|
1411
1501
|
<PopoverTrigger asChild>
|
|
1412
1502
|
<button
|
|
1413
1503
|
type="button"
|
|
@@ -1416,8 +1506,8 @@ function ModelSelector({
|
|
|
1416
1506
|
defaultValue: "Model",
|
|
1417
1507
|
})}: ${friendlyModelName(model, t)}${
|
|
1418
1508
|
effortOptions.length > 0
|
|
1419
|
-
? `. ${t("agentChat.composer.
|
|
1420
|
-
defaultValue: "
|
|
1509
|
+
? `. ${t("agentChat.composer.effort", {
|
|
1510
|
+
defaultValue: "Effort",
|
|
1421
1511
|
})}: ${effortLabel(selectedEffort)}`
|
|
1422
1512
|
: ""
|
|
1423
1513
|
}${
|
|
@@ -1446,357 +1536,527 @@ function ModelSelector({
|
|
|
1446
1536
|
sideOffset={6}
|
|
1447
1537
|
collisionPadding={8}
|
|
1448
1538
|
data-agent-native-composer-popover="true"
|
|
1449
|
-
className=
|
|
1539
|
+
className={`z-[260] overflow-visible ${MODEL_SELECTOR_POPOVER_CLASS}`}
|
|
1450
1540
|
style={MODEL_SELECTOR_POPOVER_STYLE}
|
|
1451
1541
|
>
|
|
1452
|
-
{
|
|
1453
|
-
|
|
1454
|
-
<div
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
className=
|
|
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
|
+
})}
|
|
1550
|
+
>
|
|
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
|
+
}`}
|
|
1469
1563
|
>
|
|
1470
|
-
<
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
aria-current={isSelected ? "true" : undefined}
|
|
1474
|
-
onClick={() => {
|
|
1475
|
-
if (!isConfigured) return;
|
|
1476
|
-
onAgentChange?.(agent.id);
|
|
1477
|
-
setOpen(false);
|
|
1478
|
-
}}
|
|
1479
|
-
className={`flex min-w-0 flex-1 items-center gap-3 px-3 py-1.5 text-start ${
|
|
1480
|
-
isConfigured
|
|
1481
|
-
? "hover:bg-accent/50"
|
|
1482
|
-
: "cursor-default opacity-50"
|
|
1483
|
-
}`}
|
|
1484
|
-
>
|
|
1485
|
-
<span className="min-w-0 flex-1">
|
|
1486
|
-
<span className="block truncate text-[13px] text-foreground">
|
|
1487
|
-
{agent.label}
|
|
1488
|
-
</span>
|
|
1489
|
-
{agent.description && (
|
|
1490
|
-
<span className="block truncate text-[11px] text-muted-foreground">
|
|
1491
|
-
{agent.description}
|
|
1492
|
-
</span>
|
|
1493
|
-
)}
|
|
1564
|
+
<span className="flex min-w-0 items-center">
|
|
1565
|
+
<span className="shrink-0 text-[12px] font-medium">
|
|
1566
|
+
Agent
|
|
1494
1567
|
</span>
|
|
1495
|
-
|
|
1496
|
-
<
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
</
|
|
1542
|
-
|
|
1543
|
-
{t("agentPanel.builderModelCredits", {
|
|
1544
|
-
defaultValue: "Free credits for Claude, OpenAI & Gemini",
|
|
1545
|
-
})}
|
|
1546
|
-
</span>
|
|
1547
|
-
</span>
|
|
1548
|
-
</button>
|
|
1549
|
-
{!onConnectProvider && builderFlow.error && (
|
|
1550
|
-
<p
|
|
1551
|
-
role="alert"
|
|
1552
|
-
className="px-3 pb-2 ps-9 text-[11px] text-destructive"
|
|
1553
|
-
>
|
|
1554
|
-
{builderFlow.error}
|
|
1555
|
-
</p>
|
|
1556
|
-
)}
|
|
1557
|
-
<button
|
|
1558
|
-
type="button"
|
|
1559
|
-
onClick={openLlmSettings}
|
|
1560
|
-
className="flex w-full items-start gap-2 px-3 py-2 text-start hover:bg-accent/50"
|
|
1561
|
-
>
|
|
1562
|
-
<IconKey className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
|
|
1563
|
-
<span className="min-w-0 flex-1">
|
|
1564
|
-
<span className="block text-[12px] font-medium text-foreground">
|
|
1565
|
-
{t("agentPanel.addOwnKeys", {
|
|
1566
|
-
defaultValue: "Add your own keys",
|
|
1567
|
-
})}
|
|
1568
|
-
</span>
|
|
1569
|
-
<span className="block text-[11px] text-muted-foreground">
|
|
1570
|
-
{t("agentPanel.configureProviderKeys", {
|
|
1571
|
-
defaultValue: "Choose a cloud, gateway, or local provider",
|
|
1572
|
-
})}
|
|
1573
|
-
</span>
|
|
1574
|
-
</span>
|
|
1575
|
-
</button>
|
|
1576
|
-
{!onlyConnectPathAvailable && (
|
|
1577
|
-
<div className="my-1 border-t border-border" />
|
|
1578
|
-
)}
|
|
1579
|
-
</>
|
|
1580
|
-
)}
|
|
1581
|
-
{imageModel && imageModel.options.length > 0 && (
|
|
1582
|
-
<>
|
|
1583
|
-
<div className="flex items-center hover:bg-accent/30">
|
|
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
|
+
)}
|
|
1584
1616
|
<button
|
|
1585
1617
|
type="button"
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
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
|
+
}`}
|
|
1589
1627
|
>
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
<IconChevronRight className="h-3 w-3 shrink-0 text-muted-foreground rtl:-scale-x-100" />
|
|
1594
|
-
)}
|
|
1595
|
-
<span className="text-[11px] font-medium text-muted-foreground uppercase tracking-wide shrink-0">
|
|
1596
|
-
{imageModel.label ??
|
|
1597
|
-
t("agentChat.composer.imageModel", {
|
|
1598
|
-
defaultValue: "Image model",
|
|
1599
|
-
})}
|
|
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}
|
|
1600
1631
|
</span>
|
|
1601
|
-
|
|
1602
|
-
<span className="text-[11px] text-muted-foreground/80 truncate">
|
|
1603
|
-
{imageModelLabel}
|
|
1604
|
-
</span>
|
|
1605
|
-
)}
|
|
1632
|
+
<IconChevronRight className="h-3 w-3 shrink-0 opacity-60 rtl:-scale-x-100" />
|
|
1606
1633
|
</button>
|
|
1607
|
-
|
|
1608
|
-
{imageExpanded &&
|
|
1609
|
-
imageModel.options.map((option) => (
|
|
1634
|
+
{effortOptions.length > 0 && (
|
|
1610
1635
|
<button
|
|
1611
|
-
key={option.value}
|
|
1612
1636
|
type="button"
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
}
|
|
1617
|
-
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
|
+
}`}
|
|
1618
1646
|
>
|
|
1619
|
-
<span className="
|
|
1620
|
-
{
|
|
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)}
|
|
1621
1654
|
</span>
|
|
1622
|
-
|
|
1623
|
-
<IconCheck className="h-3.5 w-3.5 shrink-0 text-blue-500" />
|
|
1624
|
-
)}
|
|
1655
|
+
<IconChevronRight className="h-3 w-3 shrink-0 opacity-60 rtl:-scale-x-100" />
|
|
1625
1656
|
</button>
|
|
1626
|
-
)
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
}}
|
|
1638
|
-
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}
|
|
1639
1668
|
>
|
|
1640
|
-
<
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
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
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
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
|
+
</>
|
|
1689
1836
|
)}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
</span>
|
|
1744
|
-
<ModelCostTier model={m} />
|
|
1745
|
-
{m === model && group.configured && (
|
|
1746
|
-
<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>
|
|
1747
1890
|
)}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
defaultValue: "Reasoning",
|
|
1773
|
-
})}
|
|
1774
|
-
</span>
|
|
1775
|
-
{!effortExpanded && (
|
|
1776
|
-
<span className="text-[11px] text-muted-foreground/80 truncate">
|
|
1777
|
-
{effortLabel(selectedEffort)}
|
|
1778
|
-
</span>
|
|
1779
|
-
)}
|
|
1780
|
-
</button>
|
|
1781
|
-
</div>
|
|
1782
|
-
{effortExpanded &&
|
|
1783
|
-
effortOptions.map((option) => (
|
|
1784
|
-
<button
|
|
1785
|
-
key={option}
|
|
1786
|
-
type="button"
|
|
1787
|
-
onClick={() => onEffortChange?.(option)}
|
|
1788
|
-
className="flex w-full items-center gap-3 ps-7 pe-3 py-1.5 text-start hover:bg-accent/50"
|
|
1789
|
-
>
|
|
1790
|
-
<span className="flex-1 min-w-0 text-[13px] text-foreground truncate">
|
|
1791
|
-
{effortLabel(option)}
|
|
1792
|
-
</span>
|
|
1793
|
-
{option === selectedEffort && (
|
|
1794
|
-
<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>
|
|
1795
1915
|
)}
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
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>
|
|
1800
2060
|
</PopoverContent>
|
|
1801
2061
|
</Popover>
|
|
1802
2062
|
);
|
|
@@ -1867,13 +2127,16 @@ export function TiptapComposer({
|
|
|
1867
2127
|
selectedModel,
|
|
1868
2128
|
selectedEffort,
|
|
1869
2129
|
showAutoModelOption = true,
|
|
2130
|
+
modelSelectorOpen,
|
|
1870
2131
|
availableModels,
|
|
1871
2132
|
modelListLoading,
|
|
1872
2133
|
onModelChange,
|
|
1873
2134
|
onEffortChange,
|
|
1874
2135
|
availableAgents,
|
|
1875
2136
|
selectedAgent,
|
|
2137
|
+
hostedHarness,
|
|
1876
2138
|
onAgentChange,
|
|
2139
|
+
onModelSelectorOpenChange,
|
|
1877
2140
|
providerConnectStatusEnabled,
|
|
1878
2141
|
onConnectProvider,
|
|
1879
2142
|
onConnectLocalRuntime,
|
|
@@ -3377,15 +3640,18 @@ export function TiptapComposer({
|
|
|
3377
3640
|
{selectedModel && availableModels && onModelChange && (
|
|
3378
3641
|
<ModelSelector
|
|
3379
3642
|
model={selectedModel}
|
|
3643
|
+
open={modelSelectorOpen}
|
|
3380
3644
|
effort={selectedEffort}
|
|
3381
3645
|
engines={availableModels}
|
|
3382
3646
|
agents={availableAgents}
|
|
3383
3647
|
selectedAgent={selectedAgent}
|
|
3648
|
+
hostedHarness={hostedHarness}
|
|
3384
3649
|
showAutoModelOption={showAutoModelOption}
|
|
3385
3650
|
modelListLoading={modelListLoading}
|
|
3386
3651
|
onChange={onModelChange}
|
|
3387
3652
|
onEffortChange={onEffortChange}
|
|
3388
3653
|
onAgentChange={onAgentChange}
|
|
3654
|
+
onModelSelectorOpenChange={onModelSelectorOpenChange}
|
|
3389
3655
|
providerConnectStatusEnabled={providerConnectStatusEnabled}
|
|
3390
3656
|
onConnectProvider={onConnectProvider}
|
|
3391
3657
|
onConnectLocalRuntime={onConnectLocalRuntime}
|