@alpaca-editor/core 1.0.4187 → 1.0.4190
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/agents-view/AgentCard.js +1 -1
- package/dist/agents-view/AgentCard.js.map +1 -1
- package/dist/agents-view/AgentsView.js +7 -5
- package/dist/agents-view/AgentsView.js.map +1 -1
- package/dist/components/ui/PlaceholderInput.d.ts +41 -0
- package/dist/components/ui/PlaceholderInput.js +160 -0
- package/dist/components/ui/PlaceholderInput.js.map +1 -0
- package/dist/components/ui/PlaceholderInputTypes.d.ts +41 -0
- package/dist/components/ui/PlaceholderInputTypes.js +48 -0
- package/dist/components/ui/PlaceholderInputTypes.js.map +1 -0
- package/dist/components/ui/PlaceholderItemSelector.d.ts +7 -0
- package/dist/components/ui/PlaceholderItemSelector.js +154 -0
- package/dist/components/ui/PlaceholderItemSelector.js.map +1 -0
- package/dist/config/config.js +7 -14
- package/dist/config/config.js.map +1 -1
- package/dist/editor/ItemInfo.js +3 -3
- package/dist/editor/ItemInfo.js.map +1 -1
- package/dist/editor/QuickItemSwitcher.js +1 -1
- package/dist/editor/QuickItemSwitcher.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.d.ts +7 -1
- package/dist/editor/ai/AgentTerminal.js +256 -382
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/Agents.js +198 -84
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.d.ts +3 -1
- package/dist/editor/ai/AiResponseMessage.js +63 -12
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/ToolCallDisplay.d.ts +2 -1
- package/dist/editor/ai/ToolCallDisplay.js +13 -5
- package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
- package/dist/editor/client/EditorShell.js +6 -5
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/hooks/useSocketMessageHandler.js +6 -4
- package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
- package/dist/editor/commands/componentCommands.js +2 -0
- package/dist/editor/commands/componentCommands.js.map +1 -1
- package/dist/editor/control-center/About.js +1 -1
- package/dist/editor/control-center/About.js.map +1 -1
- package/dist/editor/control-center/AllAgentsPanel.js +1 -1
- package/dist/editor/field-types/MultiLineText.js +1 -1
- package/dist/editor/field-types/MultiLineText.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +1 -0
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/sidebar/Validation.js +1 -1
- package/dist/editor/sidebar/Validation.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/page-wizard/PageWizard.js +3 -3
- package/dist/page-wizard/PageWizard.js.map +1 -1
- package/dist/page-wizard/WizardSteps.js +1 -1
- package/dist/page-wizard/WizardSteps.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/ModernSplashScreen.d.ts +8 -0
- package/dist/splash-screen/ModernSplashScreen.js +36 -0
- package/dist/splash-screen/ModernSplashScreen.js.map +1 -0
- package/dist/splash-screen/OpenPage.js +10 -6
- package/dist/splash-screen/OpenPage.js.map +1 -1
- package/dist/splash-screen/ParheliaAssistantChat.d.ts +8 -0
- package/dist/splash-screen/ParheliaAssistantChat.js +155 -0
- package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -0
- package/dist/splash-screen/RecentAgents.d.ts +7 -0
- package/dist/splash-screen/RecentAgents.js +76 -0
- package/dist/splash-screen/RecentAgents.js.map +1 -0
- package/dist/splash-screen/RecentPages.js +2 -2
- package/dist/splash-screen/RecentPages.js.map +1 -1
- package/dist/splash-screen/SplashScreen.js +1 -1
- package/dist/splash-screen/SplashScreen.js.map +1 -1
- package/dist/styles.css +241 -12
- package/package.json +1 -1
- package/src/agents-view/AgentCard.tsx +1 -6
- package/src/agents-view/AgentsView.tsx +18 -30
- package/src/components/ui/PlaceholderInput.tsx +290 -0
- package/src/components/ui/PlaceholderInputTypes.tsx +97 -0
- package/src/components/ui/PlaceholderItemSelector.tsx +253 -0
- package/src/config/config.tsx +8 -17
- package/src/editor/ItemInfo.tsx +3 -2
- package/src/editor/QuickItemSwitcher.tsx +1 -1
- package/src/editor/ai/AgentTerminal.tsx +544 -649
- package/src/editor/ai/Agents.tsx +464 -250
- package/src/editor/ai/AiResponseMessage.tsx +154 -29
- package/src/editor/ai/ToolCallDisplay.tsx +18 -4
- package/src/editor/client/EditorShell.tsx +9 -6
- package/src/editor/client/hooks/useSocketMessageHandler.ts +6 -7
- package/src/editor/commands/componentCommands.tsx +1 -0
- package/src/editor/control-center/About.tsx +2 -2
- package/src/editor/control-center/AllAgentsPanel.tsx +1 -1
- package/src/editor/field-types/MultiLineText.tsx +1 -1
- package/src/editor/services/aiService.ts +2 -0
- package/src/editor/sidebar/Validation.tsx +1 -1
- package/src/index.ts +5 -0
- package/src/page-wizard/PageWizard.tsx +3 -3
- package/src/page-wizard/WizardSteps.tsx +1 -1
- package/src/revision.ts +2 -2
- package/src/splash-screen/ModernSplashScreen.tsx +158 -0
- package/src/splash-screen/OpenPage.tsx +12 -4
- package/src/splash-screen/ParheliaAssistantChat.tsx +273 -0
- package/src/splash-screen/RecentAgents.tsx +151 -0
- package/src/splash-screen/RecentPages.tsx +58 -61
- package/src/splash-screen/SplashScreen.tsx +1 -1
- package/styles.css +20 -0
|
@@ -5,6 +5,7 @@ import React, {
|
|
|
5
5
|
useCallback,
|
|
6
6
|
useLayoutEffect,
|
|
7
7
|
useMemo,
|
|
8
|
+
ViewTransition,
|
|
8
9
|
} from "react";
|
|
9
10
|
import {
|
|
10
11
|
Send,
|
|
@@ -28,7 +29,6 @@ import {
|
|
|
28
29
|
StartAgentRequest,
|
|
29
30
|
Agent,
|
|
30
31
|
AgentDetails,
|
|
31
|
-
updateAgentContext,
|
|
32
32
|
AgentMetadata,
|
|
33
33
|
updateAgentSettings,
|
|
34
34
|
updateAgentCostLimit,
|
|
@@ -38,21 +38,22 @@ import {
|
|
|
38
38
|
import { useEditContext, useFieldsEditContext } from "../client/editContext";
|
|
39
39
|
import { Textarea } from "../../components/ui/textarea";
|
|
40
40
|
import { Button } from "../../components/ui/button";
|
|
41
|
+
import { PlaceholderInput } from "../../components/ui/PlaceholderInput";
|
|
41
42
|
import { AiResponseMessage } from "./AiResponseMessage";
|
|
42
43
|
import { AgentCostDisplay } from "./AgentCostDisplay";
|
|
43
44
|
import { Message } from "./types";
|
|
44
45
|
import { ContextInfoBar } from "./ContextInfoBar";
|
|
45
46
|
import { getComponentById } from "../componentTreeHelper";
|
|
46
|
-
|
|
47
|
+
|
|
47
48
|
import { AiProfile } from "../services/aiService";
|
|
48
|
-
|
|
49
|
+
|
|
49
50
|
import {
|
|
50
51
|
Popover,
|
|
51
52
|
PopoverContent,
|
|
52
53
|
PopoverTrigger,
|
|
53
54
|
} from "../../components/ui/popover";
|
|
54
55
|
import { SecretAgentIcon } from "../ui/Icons";
|
|
55
|
-
import { formatTime
|
|
56
|
+
import { formatTime } from "../utils";
|
|
56
57
|
import {
|
|
57
58
|
Tooltip,
|
|
58
59
|
TooltipTrigger,
|
|
@@ -662,6 +663,12 @@ const convertAgentMessagesToAiFormat = (
|
|
|
662
663
|
id: agentMessage.id,
|
|
663
664
|
content: agentMessage.content,
|
|
664
665
|
formattedContent: agentMessage.content
|
|
666
|
+
?.replace(/^######\s+(.+)$/gm, "<h6>$1</h6>")
|
|
667
|
+
?.replace(/^#####\s+(.+)$/gm, "<h5>$1</h5>")
|
|
668
|
+
?.replace(/^####\s+(.+)$/gm, "<h4>$1</h4>")
|
|
669
|
+
?.replace(/^###\s+(.+)$/gm, "<h3>$1</h3>")
|
|
670
|
+
?.replace(/^##\s+(.+)$/gm, "<h2>$1</h2>")
|
|
671
|
+
?.replace(/^#\s+(.+)$/gm, "<h1>$1</h1>")
|
|
665
672
|
?.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
|
|
666
673
|
?.replace(/\n/g, "<br/>"),
|
|
667
674
|
name: agentMessage.name,
|
|
@@ -700,11 +707,23 @@ export function AgentTerminal({
|
|
|
700
707
|
initialMetadata,
|
|
701
708
|
profiles,
|
|
702
709
|
isActive = true,
|
|
710
|
+
compact = false,
|
|
711
|
+
hideContext = false,
|
|
712
|
+
hideBottomControls = false,
|
|
713
|
+
hideGreeting = false,
|
|
714
|
+
className,
|
|
715
|
+
initialPrompt,
|
|
703
716
|
}: {
|
|
704
717
|
agentStub: Agent;
|
|
705
718
|
initialMetadata?: AgentMetadata;
|
|
706
719
|
profiles: AiProfile[];
|
|
707
720
|
isActive?: boolean;
|
|
721
|
+
compact?: boolean;
|
|
722
|
+
hideContext?: boolean;
|
|
723
|
+
hideBottomControls?: boolean;
|
|
724
|
+
hideGreeting?: boolean;
|
|
725
|
+
className?: string;
|
|
726
|
+
initialPrompt?: string;
|
|
708
727
|
}) {
|
|
709
728
|
const editContext = useEditContext();
|
|
710
729
|
const fieldsContext = useFieldsEditContext();
|
|
@@ -717,6 +736,10 @@ export function AgentTerminal({
|
|
|
717
736
|
const [isLoading, setIsLoading] = useState(false);
|
|
718
737
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
719
738
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
739
|
+
const [activePlaceholderInput, setActivePlaceholderInput] = useState<{
|
|
740
|
+
text: string;
|
|
741
|
+
behavior?: "compose" | "submit";
|
|
742
|
+
} | null>(null);
|
|
720
743
|
const [agentMetadata, setAgentMetadata] = useState<AgentMetadata | null>(
|
|
721
744
|
null,
|
|
722
745
|
);
|
|
@@ -945,6 +968,13 @@ export function AgentTerminal({
|
|
|
945
968
|
}
|
|
946
969
|
}, []);
|
|
947
970
|
|
|
971
|
+
// Auto-focus terminal input on mount
|
|
972
|
+
useEffect(() => {
|
|
973
|
+
if (textareaRef.current) {
|
|
974
|
+
textareaRef.current.focus();
|
|
975
|
+
}
|
|
976
|
+
}, []);
|
|
977
|
+
|
|
948
978
|
// Start voice recognition
|
|
949
979
|
const startVoice = useCallback(() => {
|
|
950
980
|
try {
|
|
@@ -1570,9 +1600,6 @@ export function AgentTerminal({
|
|
|
1570
1600
|
const loadAgent = useCallback(async () => {
|
|
1571
1601
|
try {
|
|
1572
1602
|
if (agentStub.status === "new") {
|
|
1573
|
-
// Set agent ID immediately for new agents
|
|
1574
|
-
(window as any).currentAgentId = agentStub.id;
|
|
1575
|
-
// Set currentAgentId for new agent
|
|
1576
1603
|
// Derive initial profile from provided metadata if present
|
|
1577
1604
|
const initialProfileIdFromMeta = (() => {
|
|
1578
1605
|
try {
|
|
@@ -1790,9 +1817,6 @@ export function AgentTerminal({
|
|
|
1790
1817
|
return merged;
|
|
1791
1818
|
});
|
|
1792
1819
|
|
|
1793
|
-
// Set agent ID for existing agents too
|
|
1794
|
-
(window as any).currentAgentId = agentData.id;
|
|
1795
|
-
|
|
1796
1820
|
// Check if cost limit was exceeded (detect from existing messages)
|
|
1797
1821
|
try {
|
|
1798
1822
|
const costLimitMessage = (agentData.messages || []).find(
|
|
@@ -2390,17 +2414,20 @@ export function AgentTerminal({
|
|
|
2390
2414
|
|
|
2391
2415
|
// Profiles are provided by parent component (Agents). No local loading here.
|
|
2392
2416
|
|
|
2393
|
-
// Select active profile based on agent.profileId or
|
|
2417
|
+
// Select active profile based on agent.profileId or agentStub.profileId
|
|
2394
2418
|
useEffect(() => {
|
|
2395
2419
|
if (!profiles || profiles.length === 0) return;
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2420
|
+
// For new agents, use agentStub.profileId; for loaded agents, use agent.profileId
|
|
2421
|
+
const profileIdToUse = agent?.profileId || agentStub.profileId;
|
|
2422
|
+
|
|
2423
|
+
const candidate = profileIdToUse
|
|
2424
|
+
? (profiles.find((p) => p.id === profileIdToUse) ?? profiles[0])
|
|
2399
2425
|
: profiles[0];
|
|
2426
|
+
|
|
2400
2427
|
if (candidate && (!activeProfile || activeProfile.id !== candidate.id)) {
|
|
2401
2428
|
setActiveProfile(candidate);
|
|
2402
2429
|
}
|
|
2403
|
-
}, [profiles, agent?.profileId]);
|
|
2430
|
+
}, [profiles, agent?.profileId, agentStub.profileId]);
|
|
2404
2431
|
|
|
2405
2432
|
// Update selected model when the active profile or agent model changes
|
|
2406
2433
|
useEffect(() => {
|
|
@@ -2486,12 +2513,13 @@ export function AgentTerminal({
|
|
|
2486
2513
|
}, [agent?.id]);
|
|
2487
2514
|
|
|
2488
2515
|
const handleSubmit = async () => {
|
|
2489
|
-
if (
|
|
2516
|
+
if (isSubmitting || !editContext) return;
|
|
2490
2517
|
|
|
2491
2518
|
try {
|
|
2492
2519
|
setIsSubmitting(true);
|
|
2493
2520
|
setError(null);
|
|
2494
|
-
|
|
2521
|
+
// For new agents, use agentStub.id; for existing agents, use agent.id
|
|
2522
|
+
const agentId = agent?.id || agentStub.id;
|
|
2495
2523
|
|
|
2496
2524
|
if (!agentId) return;
|
|
2497
2525
|
|
|
@@ -2520,7 +2548,7 @@ export function AgentTerminal({
|
|
|
2520
2548
|
// This ensures all tabs (including the sending tab) have the same messageId from the database
|
|
2521
2549
|
|
|
2522
2550
|
const request: StartAgentRequest = {
|
|
2523
|
-
agentId:
|
|
2551
|
+
agentId: agentId,
|
|
2524
2552
|
message: prompt.trim(),
|
|
2525
2553
|
sessionId: editContext.sessionId,
|
|
2526
2554
|
profileId: activeProfile?.id || profiles[0]?.id || "",
|
|
@@ -2698,276 +2726,37 @@ export function AgentTerminal({
|
|
|
2698
2726
|
}
|
|
2699
2727
|
};
|
|
2700
2728
|
|
|
2701
|
-
//
|
|
2702
|
-
const
|
|
2703
|
-
|
|
2704
|
-
key: "items" | "componentIds" | "field" | "comment",
|
|
2705
|
-
index?: number,
|
|
2706
|
-
) => {
|
|
2707
|
-
if (!agent?.id) return;
|
|
2708
|
-
const current = agentMetadata || {};
|
|
2709
|
-
// Exclude top-level context to avoid duplicate keys when spreading
|
|
2710
|
-
const currentWithoutContext = { ...current };
|
|
2711
|
-
delete (currentWithoutContext as any).context;
|
|
2712
|
-
const next: AgentMetadata = {
|
|
2713
|
-
...currentWithoutContext,
|
|
2714
|
-
additionalData: {
|
|
2715
|
-
...(current.additionalData || {}),
|
|
2716
|
-
context: {
|
|
2717
|
-
...((current.additionalData &&
|
|
2718
|
-
(current.additionalData as any).context) ||
|
|
2719
|
-
{}),
|
|
2720
|
-
},
|
|
2721
|
-
},
|
|
2722
|
-
} as AgentMetadata;
|
|
2723
|
-
|
|
2724
|
-
if (next.additionalData && (next.additionalData as any).context) {
|
|
2725
|
-
const ctx = (next.additionalData as any).context;
|
|
2726
|
-
if (key === "items" && typeof index === "number" && ctx.items) {
|
|
2727
|
-
ctx.items.splice(index, 1);
|
|
2728
|
-
if (ctx.items.length === 0) delete ctx.items;
|
|
2729
|
-
} else if (
|
|
2730
|
-
key === "componentIds" &&
|
|
2731
|
-
typeof index === "number" &&
|
|
2732
|
-
ctx.componentIds
|
|
2733
|
-
) {
|
|
2734
|
-
ctx.componentIds.splice(index, 1);
|
|
2735
|
-
if (ctx.componentIds.length === 0) delete ctx.componentIds;
|
|
2736
|
-
} else if (key === "field") {
|
|
2737
|
-
delete ctx.field;
|
|
2738
|
-
} else if (key === "comment") {
|
|
2739
|
-
delete ctx.comment;
|
|
2740
|
-
}
|
|
2741
|
-
}
|
|
2742
|
-
|
|
2743
|
-
try {
|
|
2744
|
-
// For new (not yet persisted) agents, only update local state
|
|
2745
|
-
if (agent.status === "new") {
|
|
2746
|
-
setAgentMetadata(next);
|
|
2747
|
-
return;
|
|
2748
|
-
}
|
|
2749
|
-
|
|
2750
|
-
// Persisted agents: update server and local cache
|
|
2751
|
-
await updateAgentContext(agent.id, next);
|
|
2752
|
-
setAgentMetadata(next);
|
|
2753
|
-
setAgent((prev) =>
|
|
2754
|
-
prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
|
|
2755
|
-
);
|
|
2756
|
-
} catch (e) {
|
|
2757
|
-
console.error("Failed to update agent metadata", e);
|
|
2758
|
-
}
|
|
2759
|
-
},
|
|
2760
|
-
[agent?.id, agentMetadata],
|
|
2761
|
-
);
|
|
2762
|
-
|
|
2763
|
-
const addPagesToContext = async () => {
|
|
2764
|
-
if (!agent?.id || !editContext?.currentItemDescriptor) return;
|
|
2765
|
-
|
|
2766
|
-
// Add the current page (currentItemDescriptor represents the current page)
|
|
2767
|
-
// Note: Currently only supports adding the current page. To support multiple pages,
|
|
2768
|
-
// we'd need a page selection mechanism in the UI (e.g., a page picker dialog)
|
|
2769
|
-
const item = editContext.currentItemDescriptor;
|
|
2770
|
-
const pageToAdd = {
|
|
2771
|
-
id: item.id,
|
|
2772
|
-
language: item.language,
|
|
2773
|
-
version: item.version,
|
|
2774
|
-
name: editContext.contentEditorItem?.name,
|
|
2775
|
-
path: undefined,
|
|
2776
|
-
};
|
|
2777
|
-
|
|
2778
|
-
const current = agentMetadata || {};
|
|
2779
|
-
const currentPages =
|
|
2780
|
-
((current.additionalData as any)?.context?.items as Array<any>) || [];
|
|
2781
|
-
|
|
2782
|
-
// Check if this page is already in context
|
|
2783
|
-
const existingPageIds = new Set(
|
|
2784
|
-
currentPages.map((p: any) => `${p.id}-${p.language}-${p.version}`),
|
|
2785
|
-
);
|
|
2786
|
-
|
|
2729
|
+
// Auto-send initial prompt if provided and agent has no messages yet
|
|
2730
|
+
const initialPromptSentRef = useRef(false);
|
|
2731
|
+
useEffect(() => {
|
|
2787
2732
|
if (
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2733
|
+
initialPrompt !== undefined &&
|
|
2734
|
+
!isLoading &&
|
|
2735
|
+
!initialPromptSentRef.current &&
|
|
2736
|
+
messages.length === 0 &&
|
|
2737
|
+
agentStub.id &&
|
|
2738
|
+
editContext &&
|
|
2739
|
+
activeProfile && // MUST have activeProfile set, not just profiles.length
|
|
2740
|
+
profiles.length > 0
|
|
2791
2741
|
) {
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
...currentWithoutContext,
|
|
2800
|
-
additionalData: {
|
|
2801
|
-
...(current.additionalData || {}),
|
|
2802
|
-
context: {
|
|
2803
|
-
...((current.additionalData &&
|
|
2804
|
-
(current.additionalData as any).context) ||
|
|
2805
|
-
{}),
|
|
2806
|
-
items: [...currentPages, pageToAdd],
|
|
2807
|
-
},
|
|
2808
|
-
},
|
|
2809
|
-
} as AgentMetadata;
|
|
2810
|
-
|
|
2811
|
-
try {
|
|
2812
|
-
if (agent.status === "new") {
|
|
2813
|
-
setAgentMetadata(next);
|
|
2814
|
-
return;
|
|
2815
|
-
}
|
|
2816
|
-
await updateAgentContext(agent.id, next);
|
|
2817
|
-
setAgentMetadata(next);
|
|
2818
|
-
setAgent((prev) =>
|
|
2819
|
-
prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
|
|
2820
|
-
);
|
|
2821
|
-
} catch (e) {
|
|
2822
|
-
console.error("Failed to update agent metadata (add pages)", e);
|
|
2823
|
-
}
|
|
2824
|
-
};
|
|
2825
|
-
|
|
2826
|
-
const addSelectedComponentsToContext = async () => {
|
|
2827
|
-
if (!agent?.id || !editContext?.selection?.length) return;
|
|
2828
|
-
|
|
2829
|
-
const current = agentMetadata || {};
|
|
2830
|
-
const currentComponentIds =
|
|
2831
|
-
((current.additionalData as any)?.context?.componentIds as string[]) ||
|
|
2832
|
-
[];
|
|
2833
|
-
|
|
2834
|
-
// Merge with existing components, avoiding duplicates
|
|
2835
|
-
const existingIds = new Set(currentComponentIds);
|
|
2836
|
-
const newComponentIds = editContext.selection.filter(
|
|
2837
|
-
(id) => !existingIds.has(id),
|
|
2838
|
-
);
|
|
2839
|
-
|
|
2840
|
-
if (newComponentIds.length === 0) return; // No new components to add
|
|
2841
|
-
|
|
2842
|
-
// Exclude top-level context to avoid duplicate keys when spreading
|
|
2843
|
-
const currentWithoutContext = { ...current };
|
|
2844
|
-
delete (currentWithoutContext as any).context;
|
|
2845
|
-
const next: AgentMetadata = {
|
|
2846
|
-
...currentWithoutContext,
|
|
2847
|
-
additionalData: {
|
|
2848
|
-
...(current.additionalData || {}),
|
|
2849
|
-
context: {
|
|
2850
|
-
...((current.additionalData &&
|
|
2851
|
-
(current.additionalData as any).context) ||
|
|
2852
|
-
{}),
|
|
2853
|
-
componentIds: [...currentComponentIds, ...newComponentIds],
|
|
2854
|
-
},
|
|
2855
|
-
},
|
|
2856
|
-
} as AgentMetadata;
|
|
2857
|
-
|
|
2858
|
-
try {
|
|
2859
|
-
if (agent.status === "new") {
|
|
2860
|
-
setAgentMetadata(next);
|
|
2861
|
-
return;
|
|
2862
|
-
}
|
|
2863
|
-
await updateAgentContext(agent.id, next);
|
|
2864
|
-
setAgentMetadata(next);
|
|
2865
|
-
setAgent((prev) =>
|
|
2866
|
-
prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
|
|
2867
|
-
);
|
|
2868
|
-
} catch (e) {
|
|
2869
|
-
console.error("Failed to update agent metadata (add components)", e);
|
|
2870
|
-
}
|
|
2871
|
-
};
|
|
2872
|
-
|
|
2873
|
-
const addComponentIdsToContext = async (ids: string[]) => {
|
|
2874
|
-
if (!agent?.id || !ids?.length) return;
|
|
2875
|
-
|
|
2876
|
-
const current = agentMetadata || {};
|
|
2877
|
-
const currentComponentIds =
|
|
2878
|
-
((current.additionalData as any)?.context?.componentIds as string[]) ||
|
|
2879
|
-
[];
|
|
2880
|
-
|
|
2881
|
-
// Merge with existing components, avoiding duplicates
|
|
2882
|
-
const existingIds = new Set(currentComponentIds);
|
|
2883
|
-
const newComponentIds = ids.filter((id) => !!id && !existingIds.has(id));
|
|
2884
|
-
if (newComponentIds.length === 0) return;
|
|
2885
|
-
|
|
2886
|
-
// Exclude top-level context to avoid duplicate keys when spreading
|
|
2887
|
-
const currentWithoutContext = { ...current };
|
|
2888
|
-
delete (currentWithoutContext as any).context;
|
|
2889
|
-
const next: AgentMetadata = {
|
|
2890
|
-
...currentWithoutContext,
|
|
2891
|
-
additionalData: {
|
|
2892
|
-
...(current.additionalData || {}),
|
|
2893
|
-
context: {
|
|
2894
|
-
...((current.additionalData &&
|
|
2895
|
-
(current.additionalData as any).context) ||
|
|
2896
|
-
{}),
|
|
2897
|
-
componentIds: [...currentComponentIds, ...newComponentIds],
|
|
2898
|
-
},
|
|
2899
|
-
},
|
|
2900
|
-
} as AgentMetadata;
|
|
2901
|
-
|
|
2902
|
-
try {
|
|
2903
|
-
if (agent.status === "new") {
|
|
2904
|
-
setAgentMetadata(next);
|
|
2905
|
-
return;
|
|
2906
|
-
}
|
|
2907
|
-
await updateAgentContext(agent.id, next);
|
|
2908
|
-
setAgentMetadata(next);
|
|
2909
|
-
setAgent((prev) =>
|
|
2910
|
-
prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
|
|
2911
|
-
);
|
|
2912
|
-
} catch (e) {
|
|
2913
|
-
console.error("Failed to update agent metadata (add components)", e);
|
|
2742
|
+
initialPromptSentRef.current = true;
|
|
2743
|
+
// Delay slightly to ensure all state is ready
|
|
2744
|
+
setTimeout(() => {
|
|
2745
|
+
setPrompt(initialPrompt);
|
|
2746
|
+
// Trigger submit programmatically
|
|
2747
|
+
handleSubmit();
|
|
2748
|
+
}, 200);
|
|
2914
2749
|
}
|
|
2915
|
-
}
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
currentPages.map((p: any) => `${p.id}-${p.language}-${p.version}`),
|
|
2926
|
-
);
|
|
2927
|
-
|
|
2928
|
-
const pagesToAdd = items
|
|
2929
|
-
.filter((it) => !!it?.id)
|
|
2930
|
-
.map((it) => ({
|
|
2931
|
-
id: it.id,
|
|
2932
|
-
language: it.language,
|
|
2933
|
-
version: it.version,
|
|
2934
|
-
}))
|
|
2935
|
-
.filter(
|
|
2936
|
-
(p) => !existingPageIds.has(`${p.id}-${p.language}-${p.version}`),
|
|
2937
|
-
);
|
|
2938
|
-
|
|
2939
|
-
if (pagesToAdd.length === 0) return;
|
|
2940
|
-
|
|
2941
|
-
// Exclude top-level context to avoid duplicate keys when spreading
|
|
2942
|
-
const currentWithoutContext = { ...current };
|
|
2943
|
-
delete (currentWithoutContext as any).context;
|
|
2944
|
-
const next: AgentMetadata = {
|
|
2945
|
-
...currentWithoutContext,
|
|
2946
|
-
additionalData: {
|
|
2947
|
-
...(current.additionalData || {}),
|
|
2948
|
-
context: {
|
|
2949
|
-
...((current.additionalData &&
|
|
2950
|
-
(current.additionalData as any).context) ||
|
|
2951
|
-
{}),
|
|
2952
|
-
items: [...currentPages, ...pagesToAdd],
|
|
2953
|
-
},
|
|
2954
|
-
},
|
|
2955
|
-
} as AgentMetadata;
|
|
2956
|
-
|
|
2957
|
-
try {
|
|
2958
|
-
if (agent.status === "new") {
|
|
2959
|
-
setAgentMetadata(next);
|
|
2960
|
-
return;
|
|
2961
|
-
}
|
|
2962
|
-
await updateAgentContext(agent.id, next);
|
|
2963
|
-
setAgentMetadata(next);
|
|
2964
|
-
setAgent((prev) =>
|
|
2965
|
-
prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
|
|
2966
|
-
);
|
|
2967
|
-
} catch (e) {
|
|
2968
|
-
console.error("Failed to update agent metadata (add items/pages)", e);
|
|
2969
|
-
}
|
|
2970
|
-
};
|
|
2750
|
+
}, [
|
|
2751
|
+
initialPrompt,
|
|
2752
|
+
isLoading,
|
|
2753
|
+
messages.length,
|
|
2754
|
+
agentStub.id,
|
|
2755
|
+
editContext,
|
|
2756
|
+
activeProfile,
|
|
2757
|
+
profiles.length,
|
|
2758
|
+
handleSubmit,
|
|
2759
|
+
]);
|
|
2971
2760
|
|
|
2972
2761
|
// Resolve display names when metadata or editor state changes
|
|
2973
2762
|
useEffect(() => {
|
|
@@ -3215,9 +3004,12 @@ export function AgentTerminal({
|
|
|
3215
3004
|
);
|
|
3216
3005
|
};
|
|
3217
3006
|
|
|
3007
|
+
|
|
3008
|
+
|
|
3218
3009
|
return (
|
|
3219
|
-
<div className=
|
|
3010
|
+
<div className={`flex h-full flex-col ${className || ""}`}>
|
|
3220
3011
|
{/* Messages */}
|
|
3012
|
+
|
|
3221
3013
|
<div
|
|
3222
3014
|
ref={messagesContainerRef}
|
|
3223
3015
|
className="flex-1 overflow-y-auto"
|
|
@@ -3238,42 +3030,50 @@ export function AgentTerminal({
|
|
|
3238
3030
|
</div>
|
|
3239
3031
|
)}
|
|
3240
3032
|
|
|
3241
|
-
{messages.length === 0 && !error && (
|
|
3033
|
+
{messages.length === 0 && !error && !hideGreeting && (
|
|
3242
3034
|
<div className="flex h-full items-center justify-center p-8">
|
|
3243
3035
|
<div className="max-w-prose text-center">
|
|
3244
|
-
{activeProfile
|
|
3245
|
-
<
|
|
3246
|
-
className="mx-auto mb-4 flex h-24 w-24 items-center justify-center text-gray-400 [&>svg]:h-full [&>svg]:w-full"
|
|
3247
|
-
dangerouslySetInnerHTML={{
|
|
3248
|
-
__html: activeProfile.svgIcon,
|
|
3249
|
-
}}
|
|
3250
|
-
/>
|
|
3251
|
-
) : (
|
|
3252
|
-
<SecretAgentIcon
|
|
3253
|
-
size={96}
|
|
3254
|
-
strokeWidth={1}
|
|
3255
|
-
className="mx-auto mb-4 text-gray-400"
|
|
3256
|
-
/>
|
|
3257
|
-
)}
|
|
3258
|
-
{activeProfile?.greetingMessage ? (
|
|
3259
|
-
<div
|
|
3260
|
-
className="prose prose-sm mx-auto text-center"
|
|
3261
|
-
dangerouslySetInnerHTML={{
|
|
3262
|
-
__html: activeProfile.greetingMessage,
|
|
3263
|
-
}}
|
|
3264
|
-
/>
|
|
3036
|
+
{!activeProfile ? (
|
|
3037
|
+
<Loader2 className="h-8 w-8 animate-spin text-gray-400 mx-auto" />
|
|
3265
3038
|
) : (
|
|
3266
3039
|
<>
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3040
|
+
{activeProfile.svgIcon ? (
|
|
3041
|
+
<div
|
|
3042
|
+
className="mx-auto mb-4 flex h-24 w-24 items-center justify-center text-gray-400 [&>svg]:h-full [&>svg]:w-full"
|
|
3043
|
+
dangerouslySetInnerHTML={{
|
|
3044
|
+
__html: activeProfile.svgIcon,
|
|
3045
|
+
}}
|
|
3046
|
+
/>
|
|
3047
|
+
) : (
|
|
3048
|
+
<SecretAgentIcon
|
|
3049
|
+
size={96}
|
|
3050
|
+
strokeWidth={1}
|
|
3051
|
+
className="mx-auto mb-4 text-gray-400"
|
|
3052
|
+
/>
|
|
3053
|
+
)}
|
|
3054
|
+
{activeProfile.greetingMessage ? (
|
|
3055
|
+
<ViewTransition>
|
|
3056
|
+
<div
|
|
3057
|
+
className="prose prose-sm mx-auto text-center"
|
|
3058
|
+
dangerouslySetInnerHTML={{
|
|
3059
|
+
__html: activeProfile.greetingMessage,
|
|
3060
|
+
}}
|
|
3061
|
+
/>
|
|
3062
|
+
</ViewTransition>
|
|
3063
|
+
) : (
|
|
3064
|
+
<>
|
|
3065
|
+
<h3 className="mb-2 text-lg font-medium text-gray-900">
|
|
3066
|
+
Start a conversation
|
|
3067
|
+
</h3>
|
|
3068
|
+
<p className="mb-4 text-sm text-gray-500">
|
|
3069
|
+
Send a message to begin working with your AI agent.
|
|
3070
|
+
</p>
|
|
3071
|
+
<div className="text-xs text-gray-400">
|
|
3072
|
+
Your agent can help with content editing, research, and
|
|
3073
|
+
automation tasks.
|
|
3074
|
+
</div>
|
|
3075
|
+
</>
|
|
3076
|
+
)}
|
|
3277
3077
|
</>
|
|
3278
3078
|
)}
|
|
3279
3079
|
</div>
|
|
@@ -3319,6 +3119,8 @@ export function AgentTerminal({
|
|
|
3319
3119
|
editOperations={[]}
|
|
3320
3120
|
error={error || undefined}
|
|
3321
3121
|
profileSvgIcon={activeProfile?.svgIcon}
|
|
3122
|
+
agentId={agent?.id || agentStub.id}
|
|
3123
|
+
agentName={activeProfile?.name}
|
|
3322
3124
|
onQuickAction={(action) => {
|
|
3323
3125
|
const text = (
|
|
3324
3126
|
action.prompt ||
|
|
@@ -3327,6 +3129,19 @@ export function AgentTerminal({
|
|
|
3327
3129
|
""
|
|
3328
3130
|
).trim();
|
|
3329
3131
|
if (!text) return;
|
|
3132
|
+
|
|
3133
|
+
// Check if text contains placeholders ({placeholder} or <placeholder>)
|
|
3134
|
+
const hasPlaceholders = /\{([^}]+)\}|<([^>]+)>/.test(text);
|
|
3135
|
+
|
|
3136
|
+
if (hasPlaceholders) {
|
|
3137
|
+
// Show placeholder input
|
|
3138
|
+
setActivePlaceholderInput({
|
|
3139
|
+
text,
|
|
3140
|
+
behavior: action.behavior,
|
|
3141
|
+
});
|
|
3142
|
+
return;
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3330
3145
|
if (action.behavior === "compose") {
|
|
3331
3146
|
setPrompt(text);
|
|
3332
3147
|
setInputPlaceholder(
|
|
@@ -3365,361 +3180,441 @@ export function AgentTerminal({
|
|
|
3365
3180
|
</div>
|
|
3366
3181
|
|
|
3367
3182
|
{/* Context Info Bar */}
|
|
3368
|
-
{renderContextInfoBar()}
|
|
3183
|
+
{!hideContext && renderContextInfoBar()}
|
|
3369
3184
|
|
|
3370
3185
|
{/* Todo List Panel */}
|
|
3371
|
-
|
|
3186
|
+
{!hideContext && (
|
|
3187
|
+
<TodoListPanel messages={messages} agentMetadata={agentMetadata} />
|
|
3188
|
+
)}
|
|
3372
3189
|
|
|
3373
3190
|
{/* Input */}
|
|
3374
3191
|
<div className="border-t border-gray-200 p-4">
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3192
|
+
{activePlaceholderInput ? (
|
|
3193
|
+
// Placeholder Input (from quick actions)
|
|
3194
|
+
<PlaceholderInput
|
|
3195
|
+
text={activePlaceholderInput.text}
|
|
3196
|
+
showButtons={false}
|
|
3197
|
+
onComplete={(filledText) => {
|
|
3198
|
+
setActivePlaceholderInput(null);
|
|
3199
|
+
|
|
3200
|
+
if (activePlaceholderInput.behavior === "compose") {
|
|
3201
|
+
setPrompt(filledText);
|
|
3202
|
+
setInputPlaceholder("Review and edit, then press Enter to send");
|
|
3203
|
+
if (textareaRef.current) {
|
|
3204
|
+
try {
|
|
3205
|
+
textareaRef.current.focus();
|
|
3206
|
+
const v = textareaRef.current.value || "";
|
|
3207
|
+
textareaRef.current.selectionStart = v.length;
|
|
3208
|
+
textareaRef.current.selectionEnd = v.length;
|
|
3209
|
+
} catch {}
|
|
3210
|
+
}
|
|
3211
|
+
} else {
|
|
3212
|
+
// Submit behavior or default
|
|
3213
|
+
if (isExecuting) {
|
|
3214
|
+
try {
|
|
3215
|
+
handleStop();
|
|
3216
|
+
} catch {}
|
|
3217
|
+
}
|
|
3218
|
+
sendQuickMessage(filledText);
|
|
3384
3219
|
}
|
|
3385
3220
|
}}
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
data-testid="agent-terminal-prompt"
|
|
3390
|
-
disabled={isSubmitting}
|
|
3221
|
+
onCancel={() => {
|
|
3222
|
+
setActivePlaceholderInput(null);
|
|
3223
|
+
}}
|
|
3391
3224
|
/>
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
: mode === "supervised"
|
|
3403
|
-
? "border-amber-300 bg-amber-50 text-amber-700"
|
|
3404
|
-
: "border-red-300 bg-red-50 text-red-700"
|
|
3405
|
-
}`}
|
|
3406
|
-
value={mode}
|
|
3407
|
-
onChange={async (e) => {
|
|
3408
|
-
const nextMode =
|
|
3409
|
-
(e.target.value as AgentMode) || "supervised";
|
|
3410
|
-
// Optimistic UI update
|
|
3411
|
-
setMode(nextMode);
|
|
3412
|
-
const current = agentMetadata || ({} as AgentMetadata);
|
|
3413
|
-
const nextMeta: AgentMetadata = {
|
|
3414
|
-
...current,
|
|
3415
|
-
mode: nextMode,
|
|
3416
|
-
} as AgentMetadata;
|
|
3417
|
-
try {
|
|
3418
|
-
if (!agent?.id || agent.status === "new") {
|
|
3419
|
-
setAgentMetadata(nextMeta);
|
|
3420
|
-
// Cache until first start when agent is persisted
|
|
3421
|
-
pendingSettingsRef.current = {
|
|
3422
|
-
...(pendingSettingsRef.current || {}),
|
|
3423
|
-
mode: nextMode,
|
|
3424
|
-
};
|
|
3425
|
-
return;
|
|
3426
|
-
}
|
|
3427
|
-
await updateAgentSettings(agent.id, { mode: nextMode });
|
|
3428
|
-
setAgentMetadata(nextMeta);
|
|
3429
|
-
setAgent((prev) =>
|
|
3430
|
-
prev
|
|
3431
|
-
? { ...prev, metadata: JSON.stringify(nextMeta) }
|
|
3432
|
-
: prev,
|
|
3433
|
-
);
|
|
3434
|
-
} catch (e2) {
|
|
3435
|
-
console.error("Failed to persist mode change", e2);
|
|
3436
|
-
}
|
|
3437
|
-
}}
|
|
3438
|
-
title="Mode"
|
|
3439
|
-
aria-label="Mode"
|
|
3440
|
-
data-testid="agent-mode-select"
|
|
3441
|
-
>
|
|
3442
|
-
<option value="supervised">Supervised</option>
|
|
3443
|
-
<option value="autonomous">Autonomous</option>
|
|
3444
|
-
<option value="read-only">Read-Only</option>
|
|
3445
|
-
</select>
|
|
3446
|
-
</TooltipTrigger>
|
|
3447
|
-
<TooltipContent side="top" sideOffset={6}>
|
|
3448
|
-
<div className="max-w-[320px] space-y-1">
|
|
3449
|
-
<div>
|
|
3450
|
-
<span className="font-semibold text-green-500">
|
|
3451
|
-
Read-Only
|
|
3452
|
-
</span>
|
|
3453
|
-
: Limited tool access as configured by the profile (Ask Mode
|
|
3454
|
-
Tools).
|
|
3455
|
-
</div>
|
|
3456
|
-
<div>
|
|
3457
|
-
<span className="font-semibold text-amber-500">
|
|
3458
|
-
Supervised
|
|
3459
|
-
</span>
|
|
3460
|
-
: Full tool access, but writes are limited to pages/items in
|
|
3461
|
-
the current context. Creating new items or updating existing
|
|
3462
|
-
items outside the current context requires explicit
|
|
3463
|
-
approval.
|
|
3464
|
-
</div>
|
|
3465
|
-
<div>
|
|
3466
|
-
<span className="font-semibold text-red-500">
|
|
3467
|
-
Autonomous
|
|
3468
|
-
</span>
|
|
3469
|
-
: Full tool access; can write across the site/project only
|
|
3470
|
-
limited by user permissions.
|
|
3471
|
-
</div>
|
|
3472
|
-
</div>
|
|
3473
|
-
</TooltipContent>
|
|
3474
|
-
</Tooltip>
|
|
3475
|
-
{profiles?.length > 0 && (
|
|
3476
|
-
<select
|
|
3477
|
-
className="h-5 rounded border px-1.5 text-[10px] text-gray-500"
|
|
3478
|
-
value={activeProfile?.id || ""}
|
|
3479
|
-
onChange={async (e) => {
|
|
3480
|
-
const nextProfile = profiles.find(
|
|
3481
|
-
(x) => x.id === e.target.value,
|
|
3482
|
-
);
|
|
3483
|
-
if (!nextProfile) return;
|
|
3484
|
-
setActiveProfile(nextProfile);
|
|
3225
|
+
) : prompt && /\{([^}]+)\}|<([^>]+)>/.test(prompt) ? (
|
|
3226
|
+
// Template mode: show PlaceholderInput when prompt contains placeholders
|
|
3227
|
+
<PlaceholderInput
|
|
3228
|
+
text={prompt}
|
|
3229
|
+
showButtons={false}
|
|
3230
|
+
onComplete={(filledText) => {
|
|
3231
|
+
setPrompt(filledText);
|
|
3232
|
+
// Auto-submit after filling placeholders
|
|
3233
|
+
if (filledText.trim()) {
|
|
3234
|
+
if (isExecuting) {
|
|
3485
3235
|
try {
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
profileId: nextProfile.id,
|
|
3489
|
-
profileName: nextProfile.name,
|
|
3490
|
-
});
|
|
3491
|
-
} else {
|
|
3492
|
-
// cache until first start
|
|
3493
|
-
pendingSettingsRef.current = {
|
|
3494
|
-
...(pendingSettingsRef.current || {}),
|
|
3495
|
-
// we cache profile by updating local metadata
|
|
3496
|
-
};
|
|
3497
|
-
setAgentMetadata((current) => {
|
|
3498
|
-
const next = { ...(current || {}) } as any;
|
|
3499
|
-
next.profile = nextProfile.name;
|
|
3500
|
-
next.additionalData = {
|
|
3501
|
-
...(next.additionalData || {}),
|
|
3502
|
-
profileId: nextProfile.id,
|
|
3503
|
-
profileName: nextProfile.name,
|
|
3504
|
-
};
|
|
3505
|
-
return next;
|
|
3506
|
-
});
|
|
3507
|
-
}
|
|
3508
|
-
// reflect in local agent stub so tabs and titles can use it if needed
|
|
3509
|
-
setAgent((prev) =>
|
|
3510
|
-
prev
|
|
3511
|
-
? {
|
|
3512
|
-
...prev,
|
|
3513
|
-
metadata: JSON.stringify({
|
|
3514
|
-
...(agentMetadata || {}),
|
|
3515
|
-
profile: nextProfile.name,
|
|
3516
|
-
additionalData: {
|
|
3517
|
-
...((agentMetadata as any)?.additionalData ||
|
|
3518
|
-
{}),
|
|
3519
|
-
profileId: nextProfile.id,
|
|
3520
|
-
profileName: nextProfile.name,
|
|
3521
|
-
},
|
|
3522
|
-
}),
|
|
3523
|
-
}
|
|
3524
|
-
: prev,
|
|
3525
|
-
);
|
|
3526
|
-
} catch (err) {
|
|
3527
|
-
console.error("Failed to persist agent profile", err);
|
|
3528
|
-
}
|
|
3529
|
-
}}
|
|
3530
|
-
title="Profile"
|
|
3531
|
-
aria-label="Profile"
|
|
3532
|
-
data-testid="agent-profile-select"
|
|
3533
|
-
>
|
|
3534
|
-
{profiles.map((p) => (
|
|
3535
|
-
<option key={p.id} value={p.id}>
|
|
3536
|
-
{p.name}
|
|
3537
|
-
</option>
|
|
3538
|
-
))}
|
|
3539
|
-
</select>
|
|
3540
|
-
)}
|
|
3541
|
-
{activeProfile?.models?.length ? (
|
|
3542
|
-
<select
|
|
3543
|
-
className="h-5 rounded border px-1.5 text-[10px] text-gray-500"
|
|
3544
|
-
value={selectedModelId || ""}
|
|
3545
|
-
onChange={async (e) => {
|
|
3546
|
-
const nextId = e.target.value;
|
|
3547
|
-
setSelectedModelId(nextId);
|
|
3548
|
-
const modelName =
|
|
3549
|
-
activeProfile?.models?.find((m) => m.id === nextId)?.name ||
|
|
3550
|
-
"";
|
|
3551
|
-
// Update local agent state immediately for UX and to reflect in streaming stub
|
|
3552
|
-
setAgent((prev) =>
|
|
3553
|
-
prev ? { ...prev, model: modelName } : prev,
|
|
3554
|
-
);
|
|
3555
|
-
// Persist only for existing agents; otherwise cache until first start
|
|
3556
|
-
try {
|
|
3557
|
-
if (agent?.id && agent.status !== "new") {
|
|
3558
|
-
await updateAgentSettings(agent.id, { model: modelName });
|
|
3559
|
-
} else {
|
|
3560
|
-
pendingSettingsRef.current = {
|
|
3561
|
-
...(pendingSettingsRef.current || {}),
|
|
3562
|
-
modelName,
|
|
3563
|
-
};
|
|
3564
|
-
}
|
|
3565
|
-
} catch (err) {
|
|
3566
|
-
console.error("Failed to persist agent model", err);
|
|
3567
|
-
}
|
|
3568
|
-
}}
|
|
3569
|
-
title="Model"
|
|
3570
|
-
aria-label="Model"
|
|
3571
|
-
data-testid="agent-model-select"
|
|
3572
|
-
>
|
|
3573
|
-
{activeProfile.models.map((m) => (
|
|
3574
|
-
<option key={m.id} value={m.id}>
|
|
3575
|
-
{m.name}
|
|
3576
|
-
</option>
|
|
3577
|
-
))}
|
|
3578
|
-
</select>
|
|
3579
|
-
) : null}
|
|
3580
|
-
{activeProfile?.prompts?.length ? (
|
|
3581
|
-
<Popover open={showPredefined} onOpenChange={setShowPredefined}>
|
|
3582
|
-
<PopoverTrigger asChild>
|
|
3583
|
-
<button
|
|
3584
|
-
className="rounded p-1 hover:bg-gray-100"
|
|
3585
|
-
onClick={() => {}}
|
|
3586
|
-
title="Predefined prompts"
|
|
3587
|
-
aria-label="Predefined prompts"
|
|
3588
|
-
>
|
|
3589
|
-
<Wand2 className="h-3 w-3" strokeWidth={1} />
|
|
3590
|
-
</button>
|
|
3591
|
-
</PopoverTrigger>
|
|
3592
|
-
<PopoverContent className="w-64 p-0" align="start">
|
|
3593
|
-
<div className="max-h-56 overflow-y-auto p-2">
|
|
3594
|
-
{activeProfile.prompts.map((p, index) => (
|
|
3595
|
-
<div
|
|
3596
|
-
key={index}
|
|
3597
|
-
className="cursor-pointer rounded p-1.5 text-xs text-gray-700 hover:bg-gray-100"
|
|
3598
|
-
onClick={() => {
|
|
3599
|
-
setPrompt(p.prompt);
|
|
3600
|
-
setShowPredefined(false);
|
|
3601
|
-
if (textareaRef.current) textareaRef.current.focus();
|
|
3602
|
-
}}
|
|
3603
|
-
>
|
|
3604
|
-
{p.title}
|
|
3605
|
-
</div>
|
|
3606
|
-
))}
|
|
3607
|
-
</div>
|
|
3608
|
-
</PopoverContent>
|
|
3609
|
-
</Popover>
|
|
3610
|
-
) : null}
|
|
3611
|
-
</div>
|
|
3612
|
-
<div className="flex items-center gap-1 self-end">
|
|
3613
|
-
{isVoiceSupported ? (
|
|
3614
|
-
<Button
|
|
3615
|
-
onClick={toggleVoice}
|
|
3616
|
-
size="sm"
|
|
3617
|
-
className="h-5.5 w-5.5 cursor-pointer rounded-full"
|
|
3618
|
-
title={isListening ? "Stop voice input" : "Start voice input"}
|
|
3619
|
-
aria-label={
|
|
3620
|
-
isListening ? "Stop voice input" : "Start voice input"
|
|
3236
|
+
handleStop();
|
|
3237
|
+
} catch {}
|
|
3621
3238
|
}
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
)
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
<Button
|
|
3632
|
-
onClick={isExecuting ? handleStop : handleSubmit}
|
|
3633
|
-
disabled={!isExecuting && !prompt.trim()}
|
|
3634
|
-
size="sm"
|
|
3635
|
-
className="h-5.5 w-5.5 cursor-pointer rounded-full"
|
|
3636
|
-
title={isExecuting ? "Stop" : "Send"}
|
|
3637
|
-
aria-label={isExecuting ? "Stop" : "Send"}
|
|
3638
|
-
data-testid="agent-send-stop-button"
|
|
3639
|
-
data-executing={isExecuting ? "true" : "false"}
|
|
3640
|
-
>
|
|
3641
|
-
{isExecuting ? (
|
|
3642
|
-
<Square className="size-3" strokeWidth={1} />
|
|
3643
|
-
) : (
|
|
3644
|
-
<Send className="size-3" strokeWidth={1} />
|
|
3645
|
-
)}
|
|
3646
|
-
</Button>
|
|
3647
|
-
</div>
|
|
3648
|
-
</div>
|
|
3649
|
-
<div className="mt-1 flex items-center gap-2 text-[10px] text-gray-500">
|
|
3650
|
-
<AgentCostDisplay
|
|
3651
|
-
totalTokens={
|
|
3652
|
-
liveTotals
|
|
3653
|
-
? {
|
|
3654
|
-
input: liveTotals.input,
|
|
3655
|
-
output: liveTotals.output,
|
|
3656
|
-
cached: liveTotals.cached,
|
|
3657
|
-
cacheWrite: liveTotals.cacheWrite ?? 0,
|
|
3658
|
-
inputCost: liveTotals.inputCost,
|
|
3659
|
-
outputCost: liveTotals.outputCost,
|
|
3660
|
-
cachedCost: liveTotals.cachedCost,
|
|
3661
|
-
cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
|
|
3662
|
-
totalCost: liveTotals.totalCost,
|
|
3663
|
-
}
|
|
3664
|
-
: totalTokens
|
|
3665
|
-
}
|
|
3666
|
-
costLimit={effectiveCostLimit}
|
|
3239
|
+
sendQuickMessage(filledText);
|
|
3240
|
+
}
|
|
3241
|
+
}}
|
|
3242
|
+
onCancel={() => {
|
|
3243
|
+
setPrompt("");
|
|
3244
|
+
setInputPlaceholder(
|
|
3245
|
+
"Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)",
|
|
3246
|
+
);
|
|
3247
|
+
}}
|
|
3667
3248
|
/>
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
const formatTokens = (tokens: number) => {
|
|
3679
|
-
if (tokens >= 1000) {
|
|
3680
|
-
return `${(tokens / 1000).toFixed(1)}k`;
|
|
3249
|
+
) : (
|
|
3250
|
+
<div className="flex items-stretch gap-2">
|
|
3251
|
+
<Textarea
|
|
3252
|
+
ref={textareaRef}
|
|
3253
|
+
value={prompt}
|
|
3254
|
+
onChange={(e) => {
|
|
3255
|
+
setPrompt(e.target.value);
|
|
3256
|
+
// Reset history index when user starts typing
|
|
3257
|
+
if (currentHistoryIndex !== -1) {
|
|
3258
|
+
setCurrentHistoryIndex(-1);
|
|
3681
3259
|
}
|
|
3682
|
-
|
|
3683
|
-
}
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3260
|
+
}}
|
|
3261
|
+
onKeyDown={handleKeyPress}
|
|
3262
|
+
placeholder={inputPlaceholder}
|
|
3263
|
+
className="h-[80px] flex-1 resize-none overflow-y-auto text-xs"
|
|
3264
|
+
data-testid="agent-terminal-prompt"
|
|
3265
|
+
disabled={isSubmitting}
|
|
3266
|
+
/>
|
|
3267
|
+
</div>
|
|
3268
|
+
)}
|
|
3269
|
+
{!hideBottomControls && (
|
|
3270
|
+
<>
|
|
3271
|
+
<div className="flex items-stretch justify-between gap-2">
|
|
3272
|
+
{/* Profile/Model selectors below the input */}
|
|
3273
|
+
<div className="mt-2 flex flex-wrap items-center justify-start gap-2">
|
|
3274
|
+
<Tooltip delayDuration={400}>
|
|
3689
3275
|
<TooltipTrigger asChild>
|
|
3690
|
-
<
|
|
3691
|
-
|
|
3692
|
-
|
|
3276
|
+
<select
|
|
3277
|
+
className={`h-5 rounded border px-1.5 text-[10px] ${
|
|
3278
|
+
mode === "read-only"
|
|
3279
|
+
? "border-green-300 bg-green-50 text-green-700"
|
|
3280
|
+
: mode === "supervised"
|
|
3281
|
+
? "border-amber-300 bg-amber-50 text-amber-700"
|
|
3282
|
+
: "border-red-300 bg-red-50 text-red-700"
|
|
3283
|
+
}`}
|
|
3284
|
+
value={mode}
|
|
3285
|
+
onChange={async (e) => {
|
|
3286
|
+
const nextMode =
|
|
3287
|
+
(e.target.value as AgentMode) || "supervised";
|
|
3288
|
+
// Optimistic UI update
|
|
3289
|
+
setMode(nextMode);
|
|
3290
|
+
const current = agentMetadata || ({} as AgentMetadata);
|
|
3291
|
+
const nextMeta: AgentMetadata = {
|
|
3292
|
+
...current,
|
|
3293
|
+
mode: nextMode,
|
|
3294
|
+
} as AgentMetadata;
|
|
3295
|
+
try {
|
|
3296
|
+
if (!agent?.id || agent.status === "new") {
|
|
3297
|
+
setAgentMetadata(nextMeta);
|
|
3298
|
+
// Cache until first start when agent is persisted
|
|
3299
|
+
pendingSettingsRef.current = {
|
|
3300
|
+
...(pendingSettingsRef.current || {}),
|
|
3301
|
+
mode: nextMode,
|
|
3302
|
+
};
|
|
3303
|
+
return;
|
|
3304
|
+
}
|
|
3305
|
+
await updateAgentSettings(agent.id, {
|
|
3306
|
+
mode: nextMode,
|
|
3307
|
+
});
|
|
3308
|
+
setAgentMetadata(nextMeta);
|
|
3309
|
+
setAgent((prev) =>
|
|
3310
|
+
prev
|
|
3311
|
+
? { ...prev, metadata: JSON.stringify(nextMeta) }
|
|
3312
|
+
: prev,
|
|
3313
|
+
);
|
|
3314
|
+
} catch (e2) {
|
|
3315
|
+
console.error("Failed to persist mode change", e2);
|
|
3316
|
+
}
|
|
3317
|
+
}}
|
|
3318
|
+
title="Mode"
|
|
3319
|
+
aria-label="Mode"
|
|
3320
|
+
data-testid="agent-mode-select"
|
|
3321
|
+
>
|
|
3322
|
+
<option value="supervised">Supervised</option>
|
|
3323
|
+
<option value="autonomous">Autonomous</option>
|
|
3324
|
+
<option value="read-only">Read-Only</option>
|
|
3325
|
+
</select>
|
|
3693
3326
|
</TooltipTrigger>
|
|
3694
3327
|
<TooltipContent side="top" sideOffset={6}>
|
|
3695
|
-
<div className="max-w-[320px] space-y-1
|
|
3328
|
+
<div className="max-w-[320px] space-y-1">
|
|
3696
3329
|
<div>
|
|
3697
|
-
<span className="font-semibold">
|
|
3698
|
-
|
|
3330
|
+
<span className="font-semibold text-green-500">
|
|
3331
|
+
Read-Only
|
|
3332
|
+
</span>
|
|
3333
|
+
: Limited tool access as configured by the profile (Ask
|
|
3334
|
+
Mode Tools).
|
|
3699
3335
|
</div>
|
|
3700
3336
|
<div>
|
|
3701
|
-
<span className="font-semibold">
|
|
3702
|
-
|
|
3703
|
-
|
|
3337
|
+
<span className="font-semibold text-amber-500">
|
|
3338
|
+
Supervised
|
|
3339
|
+
</span>
|
|
3340
|
+
: Full tool access, but writes are limited to
|
|
3341
|
+
pages/items in the current context. Creating new items
|
|
3342
|
+
or updating existing items outside the current context
|
|
3343
|
+
requires explicit approval.
|
|
3704
3344
|
</div>
|
|
3705
|
-
{typeof s.maxCompletionTokens === "number" && (
|
|
3706
|
-
<div>
|
|
3707
|
-
<span className="font-semibold">Max completion:</span>{" "}
|
|
3708
|
-
{formatTokens(s.maxCompletionTokens)} tokens
|
|
3709
|
-
</div>
|
|
3710
|
-
)}
|
|
3711
3345
|
<div>
|
|
3712
|
-
<span className="font-semibold">
|
|
3346
|
+
<span className="font-semibold text-red-500">
|
|
3347
|
+
Autonomous
|
|
3348
|
+
</span>
|
|
3349
|
+
: Full tool access; can write across the site/project
|
|
3350
|
+
only limited by user permissions.
|
|
3713
3351
|
</div>
|
|
3714
3352
|
</div>
|
|
3715
3353
|
</TooltipContent>
|
|
3716
3354
|
</Tooltip>
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3355
|
+
{profiles?.length > 0 && (
|
|
3356
|
+
<select
|
|
3357
|
+
className="h-5 rounded border px-1.5 text-[10px] text-gray-500"
|
|
3358
|
+
value={activeProfile?.id || ""}
|
|
3359
|
+
onChange={async (e) => {
|
|
3360
|
+
const nextProfile = profiles.find(
|
|
3361
|
+
(x) => x.id === e.target.value,
|
|
3362
|
+
);
|
|
3363
|
+
if (!nextProfile) return;
|
|
3364
|
+
setActiveProfile(nextProfile);
|
|
3365
|
+
try {
|
|
3366
|
+
if (agent?.id && agent.status !== "new") {
|
|
3367
|
+
await updateAgentSettings(agent.id, {
|
|
3368
|
+
profileId: nextProfile.id,
|
|
3369
|
+
profileName: nextProfile.name,
|
|
3370
|
+
});
|
|
3371
|
+
} else {
|
|
3372
|
+
// cache until first start
|
|
3373
|
+
pendingSettingsRef.current = {
|
|
3374
|
+
...(pendingSettingsRef.current || {}),
|
|
3375
|
+
// we cache profile by updating local metadata
|
|
3376
|
+
};
|
|
3377
|
+
setAgentMetadata((current) => {
|
|
3378
|
+
const next = { ...(current || {}) } as any;
|
|
3379
|
+
next.profile = nextProfile.name;
|
|
3380
|
+
next.additionalData = {
|
|
3381
|
+
...(next.additionalData || {}),
|
|
3382
|
+
profileId: nextProfile.id,
|
|
3383
|
+
profileName: nextProfile.name,
|
|
3384
|
+
};
|
|
3385
|
+
return next;
|
|
3386
|
+
});
|
|
3387
|
+
}
|
|
3388
|
+
// reflect in local agent stub so tabs and titles can use it if needed
|
|
3389
|
+
setAgent((prev) =>
|
|
3390
|
+
prev
|
|
3391
|
+
? {
|
|
3392
|
+
...prev,
|
|
3393
|
+
metadata: JSON.stringify({
|
|
3394
|
+
...(agentMetadata || {}),
|
|
3395
|
+
profile: nextProfile.name,
|
|
3396
|
+
additionalData: {
|
|
3397
|
+
...((agentMetadata as any)
|
|
3398
|
+
?.additionalData || {}),
|
|
3399
|
+
profileId: nextProfile.id,
|
|
3400
|
+
profileName: nextProfile.name,
|
|
3401
|
+
},
|
|
3402
|
+
}),
|
|
3403
|
+
}
|
|
3404
|
+
: prev,
|
|
3405
|
+
);
|
|
3406
|
+
} catch (err) {
|
|
3407
|
+
console.error("Failed to persist agent profile", err);
|
|
3408
|
+
}
|
|
3409
|
+
}}
|
|
3410
|
+
title="Profile"
|
|
3411
|
+
aria-label="Profile"
|
|
3412
|
+
data-testid="agent-profile-select"
|
|
3413
|
+
>
|
|
3414
|
+
{profiles.map((p) => (
|
|
3415
|
+
<option key={p.id} value={p.id}>
|
|
3416
|
+
{p.name}
|
|
3417
|
+
</option>
|
|
3418
|
+
))}
|
|
3419
|
+
</select>
|
|
3420
|
+
)}
|
|
3421
|
+
{activeProfile?.models?.length ? (
|
|
3422
|
+
<select
|
|
3423
|
+
className="h-5 rounded border px-1.5 text-[10px] text-gray-500"
|
|
3424
|
+
value={selectedModelId || ""}
|
|
3425
|
+
onChange={async (e) => {
|
|
3426
|
+
const nextId = e.target.value;
|
|
3427
|
+
setSelectedModelId(nextId);
|
|
3428
|
+
const modelName =
|
|
3429
|
+
activeProfile?.models?.find((m) => m.id === nextId)
|
|
3430
|
+
?.name || "";
|
|
3431
|
+
// Update local agent state immediately for UX and to reflect in streaming stub
|
|
3432
|
+
setAgent((prev) =>
|
|
3433
|
+
prev ? { ...prev, model: modelName } : prev,
|
|
3434
|
+
);
|
|
3435
|
+
// Persist only for existing agents; otherwise cache until first start
|
|
3436
|
+
try {
|
|
3437
|
+
if (agent?.id && agent.status !== "new") {
|
|
3438
|
+
await updateAgentSettings(agent.id, {
|
|
3439
|
+
model: modelName,
|
|
3440
|
+
});
|
|
3441
|
+
} else {
|
|
3442
|
+
pendingSettingsRef.current = {
|
|
3443
|
+
...(pendingSettingsRef.current || {}),
|
|
3444
|
+
modelName,
|
|
3445
|
+
};
|
|
3446
|
+
}
|
|
3447
|
+
} catch (err) {
|
|
3448
|
+
console.error("Failed to persist agent model", err);
|
|
3449
|
+
}
|
|
3450
|
+
}}
|
|
3451
|
+
title="Model"
|
|
3452
|
+
aria-label="Model"
|
|
3453
|
+
data-testid="agent-model-select"
|
|
3454
|
+
>
|
|
3455
|
+
{activeProfile.models.map((m) => (
|
|
3456
|
+
<option key={m.id} value={m.id}>
|
|
3457
|
+
{m.name}
|
|
3458
|
+
</option>
|
|
3459
|
+
))}
|
|
3460
|
+
</select>
|
|
3461
|
+
) : null}
|
|
3462
|
+
{activeProfile?.prompts?.length ? (
|
|
3463
|
+
<Popover
|
|
3464
|
+
open={showPredefined}
|
|
3465
|
+
onOpenChange={setShowPredefined}
|
|
3466
|
+
>
|
|
3467
|
+
<PopoverTrigger asChild>
|
|
3468
|
+
<button
|
|
3469
|
+
className="rounded p-1 hover:bg-gray-100"
|
|
3470
|
+
onClick={() => {}}
|
|
3471
|
+
title="Predefined prompts"
|
|
3472
|
+
aria-label="Predefined prompts"
|
|
3473
|
+
>
|
|
3474
|
+
<Wand2 className="h-3 w-3" strokeWidth={1} />
|
|
3475
|
+
</button>
|
|
3476
|
+
</PopoverTrigger>
|
|
3477
|
+
<PopoverContent className="w-64 p-0" align="start">
|
|
3478
|
+
<div className="max-h-56 overflow-y-auto p-2">
|
|
3479
|
+
{activeProfile.prompts.map((p, index) => (
|
|
3480
|
+
<div
|
|
3481
|
+
key={index}
|
|
3482
|
+
className="cursor-pointer rounded p-1.5 text-xs text-gray-700 hover:bg-gray-100"
|
|
3483
|
+
onClick={() => {
|
|
3484
|
+
setPrompt(p.prompt);
|
|
3485
|
+
setShowPredefined(false);
|
|
3486
|
+
if (textareaRef.current)
|
|
3487
|
+
textareaRef.current.focus();
|
|
3488
|
+
}}
|
|
3489
|
+
>
|
|
3490
|
+
{p.title}
|
|
3491
|
+
</div>
|
|
3492
|
+
))}
|
|
3493
|
+
</div>
|
|
3494
|
+
</PopoverContent>
|
|
3495
|
+
</Popover>
|
|
3496
|
+
) : null}
|
|
3497
|
+
</div>
|
|
3498
|
+
<div className="flex items-center gap-1 self-end">
|
|
3499
|
+
{isVoiceSupported ? (
|
|
3500
|
+
<Button
|
|
3501
|
+
onClick={toggleVoice}
|
|
3502
|
+
size="sm"
|
|
3503
|
+
className="h-5.5 w-5.5 cursor-pointer rounded-full"
|
|
3504
|
+
title={
|
|
3505
|
+
isListening ? "Stop voice input" : "Start voice input"
|
|
3506
|
+
}
|
|
3507
|
+
aria-label={
|
|
3508
|
+
isListening ? "Stop voice input" : "Start voice input"
|
|
3509
|
+
}
|
|
3510
|
+
aria-pressed={isListening}
|
|
3511
|
+
>
|
|
3512
|
+
{isListening ? (
|
|
3513
|
+
<MicOff className="size-3" strokeWidth={1} />
|
|
3514
|
+
) : (
|
|
3515
|
+
<Mic className="size-3" strokeWidth={1} />
|
|
3516
|
+
)}
|
|
3517
|
+
</Button>
|
|
3518
|
+
) : null}
|
|
3519
|
+
<Button
|
|
3520
|
+
onClick={isExecuting ? handleStop : handleSubmit}
|
|
3521
|
+
disabled={!isExecuting && !prompt.trim()}
|
|
3522
|
+
size="sm"
|
|
3523
|
+
className="h-5.5 w-5.5 cursor-pointer rounded-full"
|
|
3524
|
+
title={isExecuting ? "Stop" : "Send"}
|
|
3525
|
+
aria-label={isExecuting ? "Stop" : "Send"}
|
|
3526
|
+
data-testid="agent-send-stop-button"
|
|
3527
|
+
data-executing={isExecuting ? "true" : "false"}
|
|
3528
|
+
>
|
|
3529
|
+
{isExecuting ? (
|
|
3530
|
+
<Square className="size-3" strokeWidth={1} />
|
|
3531
|
+
) : (
|
|
3532
|
+
<Send className="size-3" strokeWidth={1} />
|
|
3533
|
+
)}
|
|
3534
|
+
</Button>
|
|
3535
|
+
</div>
|
|
3536
|
+
</div>
|
|
3537
|
+
<div className="mt-1 flex items-center gap-2 text-[10px] text-gray-500">
|
|
3538
|
+
<AgentCostDisplay
|
|
3539
|
+
totalTokens={
|
|
3540
|
+
liveTotals
|
|
3541
|
+
? {
|
|
3542
|
+
input: liveTotals.input,
|
|
3543
|
+
output: liveTotals.output,
|
|
3544
|
+
cached: liveTotals.cached,
|
|
3545
|
+
cacheWrite: liveTotals.cacheWrite ?? 0,
|
|
3546
|
+
inputCost: liveTotals.inputCost,
|
|
3547
|
+
outputCost: liveTotals.outputCost,
|
|
3548
|
+
cachedCost: liveTotals.cachedCost,
|
|
3549
|
+
cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
|
|
3550
|
+
totalCost: liveTotals.totalCost,
|
|
3551
|
+
}
|
|
3552
|
+
: totalTokens
|
|
3553
|
+
}
|
|
3554
|
+
costLimit={effectiveCostLimit}
|
|
3555
|
+
/>
|
|
3556
|
+
{(() => {
|
|
3557
|
+
try {
|
|
3558
|
+
const s = (window as any).__agentContextWindowStatus;
|
|
3559
|
+
if (!s || !s.contextWindowTokens) return null;
|
|
3560
|
+
const pct =
|
|
3561
|
+
typeof s.contextUsedPercent === "number"
|
|
3562
|
+
? `${s.contextUsedPercent.toFixed(1)}%`
|
|
3563
|
+
: undefined;
|
|
3564
|
+
|
|
3565
|
+
// Helper function to format tokens as "k"
|
|
3566
|
+
const formatTokens = (tokens: number) => {
|
|
3567
|
+
if (tokens >= 1000) {
|
|
3568
|
+
return `${(tokens / 1000).toFixed(1)}k`;
|
|
3569
|
+
}
|
|
3570
|
+
return tokens.toString();
|
|
3571
|
+
};
|
|
3572
|
+
|
|
3573
|
+
if (!pct) return null;
|
|
3574
|
+
|
|
3575
|
+
return (
|
|
3576
|
+
<Tooltip>
|
|
3577
|
+
<TooltipTrigger asChild>
|
|
3578
|
+
<div className="cursor-help rounded border border-gray-200 bg-gray-50 px-2 py-0.5">
|
|
3579
|
+
Context: {pct}
|
|
3580
|
+
</div>
|
|
3581
|
+
</TooltipTrigger>
|
|
3582
|
+
<TooltipContent side="top" sideOffset={6}>
|
|
3583
|
+
<div className="max-w-[320px] space-y-1 text-xs">
|
|
3584
|
+
<div>
|
|
3585
|
+
<span className="font-semibold">Model:</span>{" "}
|
|
3586
|
+
{s.model}
|
|
3587
|
+
{s.normalizedModel && ` (${s.normalizedModel})`}
|
|
3588
|
+
</div>
|
|
3589
|
+
<div>
|
|
3590
|
+
<span className="font-semibold">
|
|
3591
|
+
Context window:
|
|
3592
|
+
</span>{" "}
|
|
3593
|
+
{formatTokens(s.estimatedInputTokens || 0)} /{" "}
|
|
3594
|
+
{formatTokens(s.contextWindowTokens)} tokens
|
|
3595
|
+
</div>
|
|
3596
|
+
{typeof s.maxCompletionTokens === "number" && (
|
|
3597
|
+
<div>
|
|
3598
|
+
<span className="font-semibold">
|
|
3599
|
+
Max completion:
|
|
3600
|
+
</span>{" "}
|
|
3601
|
+
{formatTokens(s.maxCompletionTokens)} tokens
|
|
3602
|
+
</div>
|
|
3603
|
+
)}
|
|
3604
|
+
<div>
|
|
3605
|
+
<span className="font-semibold">Used:</span> {pct}
|
|
3606
|
+
</div>
|
|
3607
|
+
</div>
|
|
3608
|
+
</TooltipContent>
|
|
3609
|
+
</Tooltip>
|
|
3610
|
+
);
|
|
3611
|
+
} catch {
|
|
3612
|
+
return null;
|
|
3613
|
+
}
|
|
3614
|
+
})()}
|
|
3615
|
+
</div>
|
|
3616
|
+
</>
|
|
3617
|
+
)}
|
|
3723
3618
|
</div>
|
|
3724
3619
|
</div>
|
|
3725
3620
|
);
|