@agent-native/toolkit 0.22.0 → 0.22.1-nightly-20260926172134
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 +2 -1
- package/dist/composer/ComposerPlusMenu.d.ts.map +1 -1
- package/dist/composer/ComposerPlusMenu.js +24 -18
- package/dist/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/composer/PromptComposer.context.spec.js +40 -25
- package/dist/composer/PromptComposer.context.spec.js.map +1 -1
- package/dist/composer/PromptComposer.d.ts +15 -7
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +59 -30
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/PromptComposer.spec.js +29 -18
- package/dist/composer/PromptComposer.spec.js.map +1 -1
- package/dist/composer/TiptapComposer.d.ts +5 -2
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +35 -5
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/dist/composer/TiptapComposer.spec.js +96 -0
- package/dist/composer/TiptapComposer.spec.js.map +1 -1
- package/dist/composer/index.d.ts +1 -1
- package/dist/composer/index.d.ts.map +1 -1
- package/dist/composer/index.js +1 -1
- package/dist/composer/index.js.map +1 -1
- package/dist/composer/runtime-adapters.d.ts +3 -2
- package/dist/composer/runtime-adapters.d.ts.map +1 -1
- package/dist/composer/runtime-adapters.js +4 -1
- package/dist/composer/runtime-adapters.js.map +1 -1
- package/dist/icons/ResourceIconPicker.d.ts.map +1 -1
- package/dist/icons/ResourceIconPicker.js +6 -1
- package/dist/icons/ResourceIconPicker.js.map +1 -1
- package/package.json +1 -1
- package/src/composer/ComposerPlusMenu.tsx +25 -14
- package/src/composer/PromptComposer.context.spec.tsx +60 -34
- package/src/composer/PromptComposer.spec.ts +39 -20
- package/src/composer/PromptComposer.tsx +106 -46
- package/src/composer/TiptapComposer.spec.ts +130 -0
- package/src/composer/TiptapComposer.tsx +61 -26
- package/src/composer/index.ts +1 -0
- package/src/composer/runtime-adapters.tsx +13 -4
- package/src/icons/ResourceIconPicker.tsx +13 -2
|
@@ -45,6 +45,7 @@ export interface ComposerTerminalModeControl {
|
|
|
45
45
|
interface ComposerPlusMenuProps {
|
|
46
46
|
onSelectMode?: (mode: ComposerMode) => void;
|
|
47
47
|
addAttachment?: (file: File) => Promise<unknown>;
|
|
48
|
+
attachmentsEnabled?: boolean;
|
|
48
49
|
onAttachmentError?: (message: string) => void;
|
|
49
50
|
attachmentAccept?: string;
|
|
50
51
|
/**
|
|
@@ -309,6 +310,7 @@ function UploadOnlyAttachButton({
|
|
|
309
310
|
export function ComposerPlusMenu({
|
|
310
311
|
onSelectMode,
|
|
311
312
|
addAttachment,
|
|
313
|
+
attachmentsEnabled = true,
|
|
312
314
|
onAttachmentError,
|
|
313
315
|
attachmentAccept,
|
|
314
316
|
extensionTools = false,
|
|
@@ -316,6 +318,7 @@ export function ComposerPlusMenu({
|
|
|
316
318
|
terminalModeControl,
|
|
317
319
|
}: ComposerPlusMenuProps) {
|
|
318
320
|
if (mode === "upload-only") {
|
|
321
|
+
if (!attachmentsEnabled) return null;
|
|
319
322
|
return (
|
|
320
323
|
<UploadOnlyAttachButton
|
|
321
324
|
addAttachment={addAttachment}
|
|
@@ -333,6 +336,7 @@ export function ComposerPlusMenu({
|
|
|
333
336
|
<ComposerPlusMenuFull
|
|
334
337
|
onSelectMode={onSelectMode}
|
|
335
338
|
addAttachment={addAttachment}
|
|
339
|
+
attachmentsEnabled={attachmentsEnabled}
|
|
336
340
|
onAttachmentError={onAttachmentError}
|
|
337
341
|
attachmentAccept={attachmentAccept}
|
|
338
342
|
extensionTools={extensionTools}
|
|
@@ -410,12 +414,14 @@ function ComposerPlusMenuTerminal({
|
|
|
410
414
|
function ComposerPlusMenuFull({
|
|
411
415
|
onSelectMode,
|
|
412
416
|
addAttachment,
|
|
417
|
+
attachmentsEnabled,
|
|
413
418
|
onAttachmentError,
|
|
414
419
|
attachmentAccept,
|
|
415
420
|
extensionTools,
|
|
416
421
|
}: Pick<
|
|
417
422
|
ComposerPlusMenuProps,
|
|
418
423
|
| "addAttachment"
|
|
424
|
+
| "attachmentsEnabled"
|
|
419
425
|
| "onSelectMode"
|
|
420
426
|
| "onAttachmentError"
|
|
421
427
|
| "attachmentAccept"
|
|
@@ -510,7 +516,7 @@ function ComposerPlusMenuFull({
|
|
|
510
516
|
};
|
|
511
517
|
|
|
512
518
|
const handleFilesSelected = async (files: FileList | null) => {
|
|
513
|
-
if (!files || files.length === 0) return;
|
|
519
|
+
if (!attachmentsEnabled || !files || files.length === 0) return;
|
|
514
520
|
try {
|
|
515
521
|
await Promise.all(
|
|
516
522
|
Array.from(files).map((file) =>
|
|
@@ -589,19 +595,23 @@ function ComposerPlusMenuFull({
|
|
|
589
595
|
hoverAction?: () => void;
|
|
590
596
|
isSkill?: boolean;
|
|
591
597
|
}[] = [
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
598
|
+
...(attachmentsEnabled
|
|
599
|
+
? [
|
|
600
|
+
{
|
|
601
|
+
icon: <IconUpload className="h-3.5 w-3.5" />,
|
|
602
|
+
label: t("agentChat.composer.menu.uploadFile", {
|
|
603
|
+
defaultValue: "Upload File",
|
|
604
|
+
}),
|
|
605
|
+
desc: t("agentChat.composer.menu.uploadFileDescription", {
|
|
606
|
+
defaultValue: "Images, PDFs, text/code, JSON, CSV",
|
|
607
|
+
}),
|
|
608
|
+
action: () => {
|
|
609
|
+
setOpen(false);
|
|
610
|
+
setTimeout(() => fileUploadRef.current?.click(), 0);
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
]
|
|
614
|
+
: []),
|
|
605
615
|
{
|
|
606
616
|
icon: <IconPhotoPlus className="h-3.5 w-3.5" />,
|
|
607
617
|
label: t("agentChat.composer.menu.generateImage", {
|
|
@@ -698,6 +708,7 @@ function ComposerPlusMenuFull({
|
|
|
698
708
|
multiple
|
|
699
709
|
accept={attachmentAccept}
|
|
700
710
|
className="hidden"
|
|
711
|
+
disabled={!attachmentsEnabled}
|
|
701
712
|
onChange={(event) => {
|
|
702
713
|
void handleFilesSelected(event.target.files);
|
|
703
714
|
event.target.value = "";
|
|
@@ -94,7 +94,7 @@ describe("controlled composer context", () => {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
it.each(["host", "provider"] as const)(
|
|
97
|
-
"%s submission gating
|
|
97
|
+
"%s submission gating prevents sends until ready",
|
|
98
98
|
async (gate) => {
|
|
99
99
|
const composerRef = React.createRef<TiptapComposerHandle>();
|
|
100
100
|
const onSubmit = vi.fn();
|
|
@@ -152,6 +152,29 @@ describe("controlled composer context", () => {
|
|
|
152
152
|
});
|
|
153
153
|
};
|
|
154
154
|
await render();
|
|
155
|
+
if (gate === "provider") {
|
|
156
|
+
expect(
|
|
157
|
+
container.querySelector('[data-testid="provider-setup"]'),
|
|
158
|
+
).not.toBeNull();
|
|
159
|
+
expect(container.querySelector('[contenteditable="true"]')).toBeNull();
|
|
160
|
+
expect(
|
|
161
|
+
container.querySelector('button[aria-label="Add context"]'),
|
|
162
|
+
).toBeNull();
|
|
163
|
+
expect(
|
|
164
|
+
container.querySelector<HTMLButtonElement>(
|
|
165
|
+
'button[aria-label="Send message"]',
|
|
166
|
+
)?.disabled,
|
|
167
|
+
).toBe(true);
|
|
168
|
+
await act(async () =>
|
|
169
|
+
expect(
|
|
170
|
+
await composerRef.current!.submitWithText("Blocked prompt"),
|
|
171
|
+
).toBe(false),
|
|
172
|
+
);
|
|
173
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
174
|
+
expect(onDisabledClick).not.toHaveBeenCalled();
|
|
175
|
+
blocked = false;
|
|
176
|
+
await render();
|
|
177
|
+
}
|
|
155
178
|
const editor = container.querySelector<HTMLElement>(
|
|
156
179
|
'[contenteditable="true"]',
|
|
157
180
|
)!;
|
|
@@ -196,33 +219,37 @@ describe("controlled composer context", () => {
|
|
|
196
219
|
const send = container.querySelector<HTMLButtonElement>(
|
|
197
220
|
'button[aria-label="Send message"]',
|
|
198
221
|
)!;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
if (gate === "host") {
|
|
223
|
+
expect(send.disabled).toBe(true);
|
|
224
|
+
await act(async () => {
|
|
225
|
+
send.click();
|
|
226
|
+
editor.dispatchEvent(
|
|
227
|
+
new KeyboardEvent("keydown", { key: "Enter", bubbles: true }),
|
|
228
|
+
);
|
|
229
|
+
editor.dispatchEvent(
|
|
230
|
+
new KeyboardEvent("keydown", {
|
|
231
|
+
key: "Enter",
|
|
232
|
+
metaKey: true,
|
|
233
|
+
bubbles: true,
|
|
234
|
+
}),
|
|
235
|
+
);
|
|
236
|
+
editor.dispatchEvent(
|
|
237
|
+
new KeyboardEvent("keydown", {
|
|
238
|
+
key: "Enter",
|
|
239
|
+
ctrlKey: true,
|
|
240
|
+
bubbles: true,
|
|
241
|
+
}),
|
|
242
|
+
);
|
|
243
|
+
expect(await composerRef.current!.submitWithText("Quick start")).toBe(
|
|
244
|
+
false,
|
|
245
|
+
);
|
|
246
|
+
});
|
|
247
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
248
|
+
expect(editor.textContent).toBe("Staged draft");
|
|
249
|
+
expect(files).toEqual([file]);
|
|
250
|
+
} else {
|
|
251
|
+
expect(send.disabled).toBe(false);
|
|
252
|
+
}
|
|
226
253
|
blocked = false;
|
|
227
254
|
await render();
|
|
228
255
|
await act(async () => {
|
|
@@ -378,10 +405,9 @@ describe("controlled composer context", () => {
|
|
|
378
405
|
).toBe("Keep the editable draft");
|
|
379
406
|
});
|
|
380
407
|
|
|
381
|
-
it("
|
|
408
|
+
it("keeps chat locked while provider status is unresolved", async () => {
|
|
382
409
|
const composerRef = React.createRef<TiptapComposerHandle>();
|
|
383
410
|
const onSubmit = vi.fn();
|
|
384
|
-
const preflight = vi.fn().mockResolvedValue("missing");
|
|
385
411
|
await act(async () =>
|
|
386
412
|
root.render(
|
|
387
413
|
<ComposerRuntimeAdaptersProvider
|
|
@@ -391,7 +417,6 @@ describe("controlled composer context", () => {
|
|
|
391
417
|
state: "unknown",
|
|
392
418
|
missing: false,
|
|
393
419
|
}),
|
|
394
|
-
fetchAgentEngineConfiguredState: preflight,
|
|
395
420
|
},
|
|
396
421
|
}}
|
|
397
422
|
>
|
|
@@ -411,11 +436,12 @@ describe("controlled composer context", () => {
|
|
|
411
436
|
false,
|
|
412
437
|
),
|
|
413
438
|
);
|
|
414
|
-
expect(preflight).toHaveBeenCalledOnce();
|
|
415
439
|
expect(onSubmit).not.toHaveBeenCalled();
|
|
440
|
+
expect(container.querySelector('[contenteditable="true"]')).toBeNull();
|
|
441
|
+
expect(container.querySelector('[role="status"]')).not.toBeNull();
|
|
416
442
|
expect(
|
|
417
|
-
container.querySelector('[contenteditable="
|
|
418
|
-
).
|
|
443
|
+
container.querySelector('[contenteditable="false"]')?.textContent,
|
|
444
|
+
).toContain("Keep my draft");
|
|
419
445
|
});
|
|
420
446
|
|
|
421
447
|
it.each(["click", "enter"])(
|
|
@@ -7,7 +7,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
7
7
|
import {
|
|
8
8
|
buildPromptComposerSubmission,
|
|
9
9
|
PromptComposer,
|
|
10
|
-
|
|
10
|
+
resolveComposerModelStatusChecksEnabled,
|
|
11
|
+
shouldGateComposerForEngine,
|
|
12
|
+
shouldCheckModelStatus,
|
|
11
13
|
type PromptComposerFile,
|
|
12
14
|
} from "./PromptComposer.js";
|
|
13
15
|
|
|
@@ -25,35 +27,52 @@ afterEach(() => {
|
|
|
25
27
|
container.remove();
|
|
26
28
|
});
|
|
27
29
|
|
|
28
|
-
describe("
|
|
29
|
-
it("
|
|
30
|
-
for (const state of ["unknown", "unavailable"]) {
|
|
31
|
-
expect(
|
|
32
|
-
shouldGateComposerForMissingEngine({ state, hasSetupComponent: true }),
|
|
33
|
-
).toBe(false);
|
|
30
|
+
describe("shouldGateComposerForEngine", () => {
|
|
31
|
+
it("blocks typing until provider status confirms the engine is configured", () => {
|
|
32
|
+
for (const state of ["unknown", "unavailable", "missing"] as const) {
|
|
33
|
+
expect(shouldGateComposerForEngine(state)).toBe(true);
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
it("
|
|
37
|
+
it("leaves the composer usable once an engine is configured", () => {
|
|
38
|
+
expect(shouldGateComposerForEngine("configured")).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("shouldCheckModelStatus", () => {
|
|
43
|
+
it("checks hosted engines and skips local runtimes", () => {
|
|
44
|
+
expect(shouldCheckModelStatus({ selectedEngine: "openai" })).toBe(true);
|
|
45
|
+
expect(shouldCheckModelStatus({ selectedEngine: "codex-cli" })).toBe(false);
|
|
38
46
|
expect(
|
|
39
|
-
|
|
40
|
-
state: "missing",
|
|
41
|
-
hasSetupComponent: true,
|
|
42
|
-
}),
|
|
47
|
+
shouldCheckModelStatus({ enabled: true, selectedEngine: "codex-cli" }),
|
|
43
48
|
).toBe(true);
|
|
44
49
|
expect(
|
|
45
|
-
|
|
46
|
-
state: "missing",
|
|
47
|
-
hasSetupComponent: false,
|
|
48
|
-
}),
|
|
50
|
+
shouldCheckModelStatus({ enabled: false, selectedEngine: "openai" }),
|
|
49
51
|
).toBe(false);
|
|
50
52
|
});
|
|
53
|
+
});
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
describe("resolveComposerModelStatusChecksEnabled", () => {
|
|
56
|
+
it("uses the persisted engine when no picker selection is provided", () => {
|
|
57
|
+
expect(
|
|
58
|
+
resolveComposerModelStatusChecksEnabled({ defaultEngine: "codex-cli" }),
|
|
59
|
+
).toBe(false);
|
|
60
|
+
expect(
|
|
61
|
+
resolveComposerModelStatusChecksEnabled({ defaultEngine: "openai" }),
|
|
62
|
+
).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("prefers an explicit engine and honors an explicit host override", () => {
|
|
66
|
+
expect(
|
|
67
|
+
resolveComposerModelStatusChecksEnabled({
|
|
68
|
+
selectedEngine: "codex-cli",
|
|
69
|
+
defaultEngine: "openai",
|
|
70
|
+
}),
|
|
71
|
+
).toBe(false);
|
|
53
72
|
expect(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
73
|
+
resolveComposerModelStatusChecksEnabled({
|
|
74
|
+
enabled: false,
|
|
75
|
+
selectedEngine: "openai",
|
|
57
76
|
}),
|
|
58
77
|
).toBe(false);
|
|
59
78
|
});
|
|
@@ -44,12 +44,14 @@ import { PastedTextChip } from "./PastedTextChip.js";
|
|
|
44
44
|
import { escapePromptAttachmentAttribute } from "./prompt-attachments.js";
|
|
45
45
|
import {
|
|
46
46
|
type EngineModelGroup,
|
|
47
|
+
type ComposerAgentEngineState,
|
|
47
48
|
type AgentChatContextItem,
|
|
48
49
|
type ReasoningEffort,
|
|
49
50
|
useComposerRuntimeAdapters,
|
|
50
51
|
} from "./runtime-adapters.js";
|
|
51
52
|
import {
|
|
52
53
|
DEFAULT_VOICE_DICTATION_ENABLED,
|
|
54
|
+
isLocalRuntimeEngine,
|
|
53
55
|
TiptapComposer,
|
|
54
56
|
type ComposerAgentOption,
|
|
55
57
|
type ComposerSubmitIntent,
|
|
@@ -193,10 +195,7 @@ export interface PromptComposerProps {
|
|
|
193
195
|
onAgentChange?: (agent: string) => void;
|
|
194
196
|
/** Called when the shared model picker opens or closes. */
|
|
195
197
|
onModelSelectorOpenChange?: (open: boolean) => void;
|
|
196
|
-
/**
|
|
197
|
-
* Enable server-backed model/provider status checks. Defaults off when the
|
|
198
|
-
* host supplies model state and callbacks, otherwise on.
|
|
199
|
-
*/
|
|
198
|
+
/** Enable server-backed model/provider status checks. Defaults on, except for a selected local runtime. */
|
|
200
199
|
modelStatusChecksEnabled?: boolean;
|
|
201
200
|
/** Called whenever the plain editor text changes. */
|
|
202
201
|
onTextChange?: (text: string) => void;
|
|
@@ -294,9 +293,16 @@ function formatInlineTextFile(name: string, text: string): string {
|
|
|
294
293
|
.join("\n");
|
|
295
294
|
}
|
|
296
295
|
|
|
296
|
+
/** Chat stays closed until the provider check confirms it can run. */
|
|
297
|
+
export function shouldGateComposerForEngine(
|
|
298
|
+
state: ComposerAgentEngineState,
|
|
299
|
+
): boolean {
|
|
300
|
+
return state !== "configured";
|
|
301
|
+
}
|
|
302
|
+
|
|
297
303
|
/**
|
|
298
|
-
* Show
|
|
299
|
-
*
|
|
304
|
+
* Show setup treatment only for a confirmed-missing engine with a setup
|
|
305
|
+
* component. Unresolved status uses the retry treatment instead.
|
|
300
306
|
*/
|
|
301
307
|
export function shouldGateComposerForMissingEngine(input: {
|
|
302
308
|
state: string;
|
|
@@ -305,6 +311,24 @@ export function shouldGateComposerForMissingEngine(input: {
|
|
|
305
311
|
return input.state === "missing" && input.hasSetupComponent;
|
|
306
312
|
}
|
|
307
313
|
|
|
314
|
+
export function shouldCheckModelStatus(input: {
|
|
315
|
+
enabled?: boolean;
|
|
316
|
+
selectedEngine?: string;
|
|
317
|
+
}): boolean {
|
|
318
|
+
return input.enabled ?? !isLocalRuntimeEngine(input.selectedEngine);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function resolveComposerModelStatusChecksEnabled(input: {
|
|
322
|
+
enabled?: boolean;
|
|
323
|
+
selectedEngine?: string;
|
|
324
|
+
defaultEngine?: string;
|
|
325
|
+
}): boolean {
|
|
326
|
+
return shouldCheckModelStatus({
|
|
327
|
+
enabled: input.enabled,
|
|
328
|
+
selectedEngine: input.selectedEngine ?? input.defaultEngine,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
308
332
|
export async function buildPromptComposerSubmission(options: {
|
|
309
333
|
text: string;
|
|
310
334
|
attachments?: ReadonlyArray<unknown>;
|
|
@@ -602,6 +626,7 @@ function PromptComposerInner({
|
|
|
602
626
|
composerRef,
|
|
603
627
|
}: PromptComposerProps) {
|
|
604
628
|
const adapters = useComposerRuntimeAdapters();
|
|
629
|
+
const t = adapters.translate!;
|
|
605
630
|
const modelsAdapter = adapters.models!;
|
|
606
631
|
const BuilderSetupCard = modelsAdapter.BuilderSetupCard;
|
|
607
632
|
const BuilderSetupContent = modelsAdapter.BuilderSetupContent;
|
|
@@ -624,13 +649,12 @@ function PromptComposerInner({
|
|
|
624
649
|
useEffect(() => {
|
|
625
650
|
onAttachmentsChangeRef.current?.(attachmentFiles);
|
|
626
651
|
}, [attachmentFiles]);
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
modelStatusChecksEnabled ?? !hostManagedModels;
|
|
652
|
+
const requestedModelStatusChecksEnabled = shouldCheckModelStatus({
|
|
653
|
+
enabled: modelStatusChecksEnabled,
|
|
654
|
+
selectedEngine,
|
|
655
|
+
});
|
|
632
656
|
const models = modelsAdapter.useChatModels!({
|
|
633
|
-
enabled: showModelSelector &&
|
|
657
|
+
enabled: showModelSelector && requestedModelStatusChecksEnabled,
|
|
634
658
|
});
|
|
635
659
|
const composerModel = showModelSelector
|
|
636
660
|
? (selectedModel ?? models.selectedModel)
|
|
@@ -638,6 +662,12 @@ function PromptComposerInner({
|
|
|
638
662
|
const composerEngine = showModelSelector
|
|
639
663
|
? (selectedEngine ?? models.selectedEngine)
|
|
640
664
|
: undefined;
|
|
665
|
+
const resolvedModelStatusChecksEnabled =
|
|
666
|
+
resolveComposerModelStatusChecksEnabled({
|
|
667
|
+
enabled: modelStatusChecksEnabled,
|
|
668
|
+
selectedEngine,
|
|
669
|
+
defaultEngine: models.selectedEngine,
|
|
670
|
+
});
|
|
641
671
|
const composerEffort = showModelSelector
|
|
642
672
|
? (selectedEffort ?? models.selectedEffort)
|
|
643
673
|
: undefined;
|
|
@@ -668,7 +698,14 @@ function PromptComposerInner({
|
|
|
668
698
|
const agentEngineConfigured = modelsAdapter.useAgentEngineConfigured!(
|
|
669
699
|
resolvedModelStatusChecksEnabled,
|
|
670
700
|
);
|
|
671
|
-
const
|
|
701
|
+
const engineState = resolvedModelStatusChecksEnabled
|
|
702
|
+
? agentEngineConfigured.state
|
|
703
|
+
: "configured";
|
|
704
|
+
const missingApiKey =
|
|
705
|
+
resolvedModelStatusChecksEnabled && engineState === "missing";
|
|
706
|
+
const engineStatusUnresolved =
|
|
707
|
+
resolvedModelStatusChecksEnabled &&
|
|
708
|
+
(engineState === "unknown" || engineState === "unavailable");
|
|
672
709
|
const [missingKeyBouncePulse, setMissingKeyBouncePulse] = useState(0);
|
|
673
710
|
const bounceMissingKeySetup = useCallback(() => {
|
|
674
711
|
setMissingKeyBouncePulse((pulse) => pulse + 1);
|
|
@@ -682,30 +719,14 @@ function PromptComposerInner({
|
|
|
682
719
|
}
|
|
683
720
|
}, []);
|
|
684
721
|
const useInlineMissingKeySetup = layoutVariant === "compact";
|
|
685
|
-
const gateComposer =
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
),
|
|
690
|
-
});
|
|
691
|
-
const ensureEngineReadyBeforeSubmit = useCallback(async () => {
|
|
692
|
-
if (agentEngineConfigured.state === "missing") {
|
|
693
|
-
bounceMissingKeySetup();
|
|
694
|
-
return false;
|
|
695
|
-
}
|
|
696
|
-
if (agentEngineConfigured.state !== "unknown") return true;
|
|
697
|
-
const state = await modelsAdapter.fetchAgentEngineConfiguredState?.(true, {
|
|
698
|
-
timeoutMs: 5_000,
|
|
699
|
-
});
|
|
700
|
-
if (state === "missing") {
|
|
701
|
-
bounceMissingKeySetup();
|
|
702
|
-
return false;
|
|
722
|
+
const gateComposer = shouldGateComposerForEngine(engineState);
|
|
723
|
+
const retryEngineStatus = useCallback(() => {
|
|
724
|
+
if (typeof window !== "undefined") {
|
|
725
|
+
window.dispatchEvent(new Event("agent-engine:configured-changed"));
|
|
703
726
|
}
|
|
704
|
-
|
|
705
|
-
}, [agentEngineConfigured.state, bounceMissingKeySetup, modelsAdapter]);
|
|
706
|
-
|
|
727
|
+
}, []);
|
|
707
728
|
useEffect(() => {
|
|
708
|
-
if (!autoFocus || disabled) return;
|
|
729
|
+
if (!autoFocus || disabled || gateComposer) return;
|
|
709
730
|
const id = window.setTimeout(() => {
|
|
710
731
|
const target =
|
|
711
732
|
typeof handleRef === "object" && handleRef && "current" in handleRef
|
|
@@ -714,7 +735,7 @@ function PromptComposerInner({
|
|
|
714
735
|
target?.focus();
|
|
715
736
|
}, 50);
|
|
716
737
|
return () => window.clearTimeout(id);
|
|
717
|
-
}, [autoFocus, disabled, handleRef]);
|
|
738
|
+
}, [autoFocus, disabled, gateComposer, handleRef]);
|
|
718
739
|
|
|
719
740
|
const handleSubmit = useCallback(
|
|
720
741
|
async (
|
|
@@ -772,6 +793,27 @@ function PromptComposerInner({
|
|
|
772
793
|
/>
|
|
773
794
|
</div>
|
|
774
795
|
) : null}
|
|
796
|
+
{engineStatusUnresolved ? (
|
|
797
|
+
<div
|
|
798
|
+
className="mb-2 flex items-center justify-between gap-3 rounded-md border border-border bg-muted/50 px-3 py-2 text-sm text-muted-foreground"
|
|
799
|
+
role="status"
|
|
800
|
+
>
|
|
801
|
+
<span>
|
|
802
|
+
{engineState === "unknown"
|
|
803
|
+
? t("agentChat.setup.checkingProvider")
|
|
804
|
+
: t("agentChat.setup.providerStatusUnavailable")}
|
|
805
|
+
</span>
|
|
806
|
+
{engineState === "unavailable" ? (
|
|
807
|
+
<button
|
|
808
|
+
type="button"
|
|
809
|
+
className="shrink-0 font-medium text-foreground underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
810
|
+
onClick={retryEngineStatus}
|
|
811
|
+
>
|
|
812
|
+
{t("agentChat.common.retry")}
|
|
813
|
+
</button>
|
|
814
|
+
) : null}
|
|
815
|
+
</div>
|
|
816
|
+
) : null}
|
|
775
817
|
<AgentComposerFrame
|
|
776
818
|
className={cn(
|
|
777
819
|
"text-start",
|
|
@@ -783,38 +825,56 @@ function PromptComposerInner({
|
|
|
783
825
|
style={style}
|
|
784
826
|
rootStyle={rootStyle}
|
|
785
827
|
layoutVariant={layoutVariant}
|
|
786
|
-
onClick={
|
|
828
|
+
onClick={
|
|
829
|
+
gateComposer
|
|
830
|
+
? missingApiKey
|
|
831
|
+
? bounceMissingKeySetup
|
|
832
|
+
: retryEngineStatus
|
|
833
|
+
: disabled && onDisabledClick
|
|
834
|
+
? () => onDisabledClick()
|
|
835
|
+
: undefined
|
|
836
|
+
}
|
|
787
837
|
>
|
|
788
838
|
<PromptAttachmentStrip />
|
|
789
839
|
<TiptapComposer
|
|
790
840
|
contextItems={contextItems}
|
|
791
|
-
contextMenuItems={contextMenuItems}
|
|
841
|
+
contextMenuItems={gateComposer ? undefined : contextMenuItems}
|
|
792
842
|
onRemoveContextItem={onRemoveContextItem}
|
|
793
843
|
onInspectContextItem={onInspectContextItem}
|
|
794
844
|
onRetryContextItem={onRetryContextItem}
|
|
795
845
|
attachmentsEnabled={attachmentsEnabled}
|
|
796
846
|
ariaLabel={ariaLabel}
|
|
797
847
|
focusRef={handleRef}
|
|
798
|
-
disabled={disabled}
|
|
799
|
-
submissionDisabled={
|
|
800
|
-
submissionDisabled || agentEngineConfigured.state === "missing"
|
|
801
|
-
}
|
|
848
|
+
disabled={disabled || gateComposer}
|
|
849
|
+
submissionDisabled={submissionDisabled || gateComposer}
|
|
802
850
|
submitting={submitting}
|
|
803
851
|
willQueue={willQueue}
|
|
804
852
|
maxDocumentAttachmentBytes={maxDocumentAttachmentBytes}
|
|
805
853
|
documentAttachmentLimitLabel={documentAttachmentLimitLabel}
|
|
806
|
-
placeholder={
|
|
854
|
+
placeholder={
|
|
855
|
+
gateComposer
|
|
856
|
+
? engineStatusUnresolved
|
|
857
|
+
? t("agentChat.setup.checkingProvider")
|
|
858
|
+
: t("agentChat.composer.connectAbove", {
|
|
859
|
+
defaultValue: "Connect AI above to continue...",
|
|
860
|
+
})
|
|
861
|
+
: placeholder
|
|
862
|
+
}
|
|
807
863
|
initialText={initialText}
|
|
808
864
|
initialTextKey={initialTextKey}
|
|
809
865
|
onSubmit={handleSubmit}
|
|
810
|
-
onBeforeSubmit={ensureEngineReadyBeforeSubmit}
|
|
811
866
|
clearOnSubmit={!preserveDraftOnSubmit}
|
|
812
867
|
plusMenuMode={
|
|
813
|
-
|
|
868
|
+
gateComposer
|
|
869
|
+
? "hidden"
|
|
870
|
+
: (plusMenuMode ??
|
|
871
|
+
(attachmentsEnabled ? "upload-only" : "hidden"))
|
|
814
872
|
}
|
|
815
873
|
terminalModeControl={terminalModeControl}
|
|
816
874
|
extensionTools={extensionTools}
|
|
817
|
-
attachButton={
|
|
875
|
+
attachButton={
|
|
876
|
+
gateComposer || !attachmentsEnabled ? null : attachButton
|
|
877
|
+
}
|
|
818
878
|
modeControl={modeControl}
|
|
819
879
|
execMode={execMode}
|
|
820
880
|
onExecModeChange={onExecModeChange}
|