@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.
Files changed (102) hide show
  1. package/dist/agents-view/AgentCard.js +1 -1
  2. package/dist/agents-view/AgentCard.js.map +1 -1
  3. package/dist/agents-view/AgentsView.js +7 -5
  4. package/dist/agents-view/AgentsView.js.map +1 -1
  5. package/dist/components/ui/PlaceholderInput.d.ts +41 -0
  6. package/dist/components/ui/PlaceholderInput.js +160 -0
  7. package/dist/components/ui/PlaceholderInput.js.map +1 -0
  8. package/dist/components/ui/PlaceholderInputTypes.d.ts +41 -0
  9. package/dist/components/ui/PlaceholderInputTypes.js +48 -0
  10. package/dist/components/ui/PlaceholderInputTypes.js.map +1 -0
  11. package/dist/components/ui/PlaceholderItemSelector.d.ts +7 -0
  12. package/dist/components/ui/PlaceholderItemSelector.js +154 -0
  13. package/dist/components/ui/PlaceholderItemSelector.js.map +1 -0
  14. package/dist/config/config.js +7 -14
  15. package/dist/config/config.js.map +1 -1
  16. package/dist/editor/ItemInfo.js +3 -3
  17. package/dist/editor/ItemInfo.js.map +1 -1
  18. package/dist/editor/QuickItemSwitcher.js +1 -1
  19. package/dist/editor/QuickItemSwitcher.js.map +1 -1
  20. package/dist/editor/ai/AgentTerminal.d.ts +7 -1
  21. package/dist/editor/ai/AgentTerminal.js +256 -382
  22. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  23. package/dist/editor/ai/Agents.js +198 -84
  24. package/dist/editor/ai/Agents.js.map +1 -1
  25. package/dist/editor/ai/AiResponseMessage.d.ts +3 -1
  26. package/dist/editor/ai/AiResponseMessage.js +63 -12
  27. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  28. package/dist/editor/ai/ToolCallDisplay.d.ts +2 -1
  29. package/dist/editor/ai/ToolCallDisplay.js +13 -5
  30. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  31. package/dist/editor/client/EditorShell.js +6 -5
  32. package/dist/editor/client/EditorShell.js.map +1 -1
  33. package/dist/editor/client/hooks/useSocketMessageHandler.js +6 -4
  34. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  35. package/dist/editor/commands/componentCommands.js +2 -0
  36. package/dist/editor/commands/componentCommands.js.map +1 -1
  37. package/dist/editor/control-center/About.js +1 -1
  38. package/dist/editor/control-center/About.js.map +1 -1
  39. package/dist/editor/control-center/AllAgentsPanel.js +1 -1
  40. package/dist/editor/field-types/MultiLineText.js +1 -1
  41. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  42. package/dist/editor/services/aiService.d.ts +1 -0
  43. package/dist/editor/services/aiService.js.map +1 -1
  44. package/dist/editor/sidebar/Validation.js +1 -1
  45. package/dist/editor/sidebar/Validation.js.map +1 -1
  46. package/dist/index.d.ts +3 -0
  47. package/dist/index.js +4 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/page-wizard/PageWizard.js +3 -3
  50. package/dist/page-wizard/PageWizard.js.map +1 -1
  51. package/dist/page-wizard/WizardSteps.js +1 -1
  52. package/dist/page-wizard/WizardSteps.js.map +1 -1
  53. package/dist/revision.d.ts +2 -2
  54. package/dist/revision.js +2 -2
  55. package/dist/splash-screen/ModernSplashScreen.d.ts +8 -0
  56. package/dist/splash-screen/ModernSplashScreen.js +36 -0
  57. package/dist/splash-screen/ModernSplashScreen.js.map +1 -0
  58. package/dist/splash-screen/OpenPage.js +10 -6
  59. package/dist/splash-screen/OpenPage.js.map +1 -1
  60. package/dist/splash-screen/ParheliaAssistantChat.d.ts +8 -0
  61. package/dist/splash-screen/ParheliaAssistantChat.js +155 -0
  62. package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -0
  63. package/dist/splash-screen/RecentAgents.d.ts +7 -0
  64. package/dist/splash-screen/RecentAgents.js +76 -0
  65. package/dist/splash-screen/RecentAgents.js.map +1 -0
  66. package/dist/splash-screen/RecentPages.js +2 -2
  67. package/dist/splash-screen/RecentPages.js.map +1 -1
  68. package/dist/splash-screen/SplashScreen.js +1 -1
  69. package/dist/splash-screen/SplashScreen.js.map +1 -1
  70. package/dist/styles.css +241 -12
  71. package/package.json +1 -1
  72. package/src/agents-view/AgentCard.tsx +1 -6
  73. package/src/agents-view/AgentsView.tsx +18 -30
  74. package/src/components/ui/PlaceholderInput.tsx +290 -0
  75. package/src/components/ui/PlaceholderInputTypes.tsx +97 -0
  76. package/src/components/ui/PlaceholderItemSelector.tsx +253 -0
  77. package/src/config/config.tsx +8 -17
  78. package/src/editor/ItemInfo.tsx +3 -2
  79. package/src/editor/QuickItemSwitcher.tsx +1 -1
  80. package/src/editor/ai/AgentTerminal.tsx +544 -649
  81. package/src/editor/ai/Agents.tsx +464 -250
  82. package/src/editor/ai/AiResponseMessage.tsx +154 -29
  83. package/src/editor/ai/ToolCallDisplay.tsx +18 -4
  84. package/src/editor/client/EditorShell.tsx +9 -6
  85. package/src/editor/client/hooks/useSocketMessageHandler.ts +6 -7
  86. package/src/editor/commands/componentCommands.tsx +1 -0
  87. package/src/editor/control-center/About.tsx +2 -2
  88. package/src/editor/control-center/AllAgentsPanel.tsx +1 -1
  89. package/src/editor/field-types/MultiLineText.tsx +1 -1
  90. package/src/editor/services/aiService.ts +2 -0
  91. package/src/editor/sidebar/Validation.tsx +1 -1
  92. package/src/index.ts +5 -0
  93. package/src/page-wizard/PageWizard.tsx +3 -3
  94. package/src/page-wizard/WizardSteps.tsx +1 -1
  95. package/src/revision.ts +2 -2
  96. package/src/splash-screen/ModernSplashScreen.tsx +158 -0
  97. package/src/splash-screen/OpenPage.tsx +12 -4
  98. package/src/splash-screen/ParheliaAssistantChat.tsx +273 -0
  99. package/src/splash-screen/RecentAgents.tsx +151 -0
  100. package/src/splash-screen/RecentPages.tsx +58 -61
  101. package/src/splash-screen/SplashScreen.tsx +1 -1
  102. package/styles.css +20 -0
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React, { useEffect, useState, useRef, useCallback, useLayoutEffect, useMemo, } from "react";
2
+ import React, { useEffect, useState, useRef, useCallback, useLayoutEffect, useMemo, ViewTransition, } from "react";
3
3
  import { Send, AlertCircle, Loader2, User, Wand2, Square, Mic, MicOff, ChevronDown, ChevronUp, ListTodo, } from "lucide-react";
4
4
  import { DancingDots } from "./DancingDots";
5
- import { getAgent, startAgent, updateAgentContext, updateAgentSettings, updateAgentCostLimit, cancelAgent, canonicalizeAgentMetadata, } from "../services/agentService";
5
+ import { getAgent, startAgent, updateAgentSettings, updateAgentCostLimit, cancelAgent, canonicalizeAgentMetadata, } from "../services/agentService";
6
6
  import { useEditContext, useFieldsEditContext } from "../client/editContext";
7
7
  import { Textarea } from "../../components/ui/textarea";
8
8
  import { Button } from "../../components/ui/button";
9
+ import { PlaceholderInput } from "../../components/ui/PlaceholderInput";
9
10
  import { AiResponseMessage } from "./AiResponseMessage";
10
11
  import { AgentCostDisplay } from "./AgentCostDisplay";
11
12
  import { ContextInfoBar } from "./ContextInfoBar";
@@ -439,6 +440,12 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
439
440
  id: agentMessage.id,
440
441
  content: agentMessage.content,
441
442
  formattedContent: agentMessage.content
443
+ ?.replace(/^######\s+(.+)$/gm, "<h6>$1</h6>")
444
+ ?.replace(/^#####\s+(.+)$/gm, "<h5>$1</h5>")
445
+ ?.replace(/^####\s+(.+)$/gm, "<h4>$1</h4>")
446
+ ?.replace(/^###\s+(.+)$/gm, "<h3>$1</h3>")
447
+ ?.replace(/^##\s+(.+)$/gm, "<h2>$1</h2>")
448
+ ?.replace(/^#\s+(.+)$/gm, "<h1>$1</h1>")
442
449
  ?.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
443
450
  ?.replace(/\n/g, "<br/>"),
444
451
  name: agentMessage.name,
@@ -468,7 +475,7 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
468
475
  // interface AgentTerminalProps {
469
476
  // agentStub: Agent;
470
477
  // }
471
- export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive = true, }) {
478
+ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive = true, compact = false, hideContext = false, hideBottomControls = false, hideGreeting = false, className, initialPrompt, }) {
472
479
  const editContext = useEditContext();
473
480
  const fieldsContext = useFieldsEditContext();
474
481
  const [agent, setAgent] = useState(undefined);
@@ -478,6 +485,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
478
485
  const [isLoading, setIsLoading] = useState(false);
479
486
  const [isConnecting, setIsConnecting] = useState(false);
480
487
  const [isSubmitting, setIsSubmitting] = useState(false);
488
+ const [activePlaceholderInput, setActivePlaceholderInput] = useState(null);
481
489
  const [agentMetadata, setAgentMetadata] = useState(null);
482
490
  // Generate a stable clientSessionId per component instance for stream deduplication
483
491
  const clientSessionIdRef = useRef(null);
@@ -650,6 +658,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
650
658
  setIsVoiceSupported(false);
651
659
  }
652
660
  }, []);
661
+ // Auto-focus terminal input on mount
662
+ useEffect(() => {
663
+ if (textareaRef.current) {
664
+ textareaRef.current.focus();
665
+ }
666
+ }, []);
653
667
  // Start voice recognition
654
668
  const startVoice = useCallback(() => {
655
669
  try {
@@ -1178,9 +1192,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1178
1192
  const loadAgent = useCallback(async () => {
1179
1193
  try {
1180
1194
  if (agentStub.status === "new") {
1181
- // Set agent ID immediately for new agents
1182
- window.currentAgentId = agentStub.id;
1183
- // Set currentAgentId for new agent
1184
1195
  // Derive initial profile from provided metadata if present
1185
1196
  const initialProfileIdFromMeta = (() => {
1186
1197
  try {
@@ -1365,8 +1376,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1365
1376
  const merged = mergeMessagesById(dbMessages, prevMessages);
1366
1377
  return merged;
1367
1378
  });
1368
- // Set agent ID for existing agents too
1369
- window.currentAgentId = agentData.id;
1370
1379
  // Check if cost limit was exceeded (detect from existing messages)
1371
1380
  try {
1372
1381
  const costLimitMessage = (agentData.messages || []).find((msg) => msg.role === "assistant" &&
@@ -1878,18 +1887,19 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1878
1887
  return () => window.removeEventListener("editor:focusAgentPrompt", focusHandler);
1879
1888
  }, []);
1880
1889
  // Profiles are provided by parent component (Agents). No local loading here.
1881
- // Select active profile based on agent.profileId or default to first
1890
+ // Select active profile based on agent.profileId or agentStub.profileId
1882
1891
  useEffect(() => {
1883
1892
  if (!profiles || profiles.length === 0)
1884
1893
  return;
1885
- const candidate = agent?.profileId
1886
- ? (profiles.find((p) => p.id === agent?.profileId) ??
1887
- profiles[0])
1894
+ // For new agents, use agentStub.profileId; for loaded agents, use agent.profileId
1895
+ const profileIdToUse = agent?.profileId || agentStub.profileId;
1896
+ const candidate = profileIdToUse
1897
+ ? (profiles.find((p) => p.id === profileIdToUse) ?? profiles[0])
1888
1898
  : profiles[0];
1889
1899
  if (candidate && (!activeProfile || activeProfile.id !== candidate.id)) {
1890
1900
  setActiveProfile(candidate);
1891
1901
  }
1892
- }, [profiles, agent?.profileId]);
1902
+ }, [profiles, agent?.profileId, agentStub.profileId]);
1893
1903
  // Update selected model when the active profile or agent model changes
1894
1904
  useEffect(() => {
1895
1905
  if (!activeProfile)
@@ -1966,12 +1976,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1966
1976
  }
1967
1977
  }, [agent?.id]);
1968
1978
  const handleSubmit = async () => {
1969
- if (!prompt.trim() || isSubmitting || !editContext)
1979
+ if (isSubmitting || !editContext)
1970
1980
  return;
1971
1981
  try {
1972
1982
  setIsSubmitting(true);
1973
1983
  setError(null);
1974
- const agentId = agent?.id;
1984
+ // For new agents, use agentStub.id; for existing agents, use agent.id
1985
+ const agentId = agent?.id || agentStub.id;
1975
1986
  if (!agentId)
1976
1987
  return;
1977
1988
  // Optional context factory: invoke if configured and available, otherwise continue
@@ -1995,7 +2006,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1995
2006
  // It will be added when we receive the agent:user:message broadcast from the server
1996
2007
  // This ensures all tabs (including the sending tab) have the same messageId from the database
1997
2008
  const request = {
1998
- agentId: agent.id,
2009
+ agentId: agentId,
1999
2010
  message: prompt.trim(),
2000
2011
  sessionId: editContext.sessionId,
2001
2012
  profileId: activeProfile?.id || profiles[0]?.id || "",
@@ -2152,232 +2163,35 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2152
2163
  setIsSubmitting(false);
2153
2164
  }
2154
2165
  };
2155
- // Context info bar helpers - must be declared before any early returns to keep hooks order stable
2156
- const removeContextKey = useCallback(async (key, index) => {
2157
- if (!agent?.id)
2158
- return;
2159
- const current = agentMetadata || {};
2160
- // Exclude top-level context to avoid duplicate keys when spreading
2161
- const currentWithoutContext = { ...current };
2162
- delete currentWithoutContext.context;
2163
- const next = {
2164
- ...currentWithoutContext,
2165
- additionalData: {
2166
- ...(current.additionalData || {}),
2167
- context: {
2168
- ...((current.additionalData &&
2169
- current.additionalData.context) ||
2170
- {}),
2171
- },
2172
- },
2173
- };
2174
- if (next.additionalData && next.additionalData.context) {
2175
- const ctx = next.additionalData.context;
2176
- if (key === "items" && typeof index === "number" && ctx.items) {
2177
- ctx.items.splice(index, 1);
2178
- if (ctx.items.length === 0)
2179
- delete ctx.items;
2180
- }
2181
- else if (key === "componentIds" &&
2182
- typeof index === "number" &&
2183
- ctx.componentIds) {
2184
- ctx.componentIds.splice(index, 1);
2185
- if (ctx.componentIds.length === 0)
2186
- delete ctx.componentIds;
2187
- }
2188
- else if (key === "field") {
2189
- delete ctx.field;
2190
- }
2191
- else if (key === "comment") {
2192
- delete ctx.comment;
2193
- }
2194
- }
2195
- try {
2196
- // For new (not yet persisted) agents, only update local state
2197
- if (agent.status === "new") {
2198
- setAgentMetadata(next);
2199
- return;
2200
- }
2201
- // Persisted agents: update server and local cache
2202
- await updateAgentContext(agent.id, next);
2203
- setAgentMetadata(next);
2204
- setAgent((prev) => prev ? { ...prev, metadata: JSON.stringify(next) } : prev);
2205
- }
2206
- catch (e) {
2207
- console.error("Failed to update agent metadata", e);
2208
- }
2209
- }, [agent?.id, agentMetadata]);
2210
- const addPagesToContext = async () => {
2211
- if (!agent?.id || !editContext?.currentItemDescriptor)
2212
- return;
2213
- // Add the current page (currentItemDescriptor represents the current page)
2214
- // Note: Currently only supports adding the current page. To support multiple pages,
2215
- // we'd need a page selection mechanism in the UI (e.g., a page picker dialog)
2216
- const item = editContext.currentItemDescriptor;
2217
- const pageToAdd = {
2218
- id: item.id,
2219
- language: item.language,
2220
- version: item.version,
2221
- name: editContext.contentEditorItem?.name,
2222
- path: undefined,
2223
- };
2224
- const current = agentMetadata || {};
2225
- const currentPages = current.additionalData?.context?.items || [];
2226
- // Check if this page is already in context
2227
- const existingPageIds = new Set(currentPages.map((p) => `${p.id}-${p.language}-${p.version}`));
2228
- if (existingPageIds.has(`${pageToAdd.id}-${pageToAdd.language}-${pageToAdd.version}`)) {
2229
- return; // Page already exists
2230
- }
2231
- // Exclude top-level context to avoid duplicate keys when spreading
2232
- const currentWithoutContext = { ...current };
2233
- delete currentWithoutContext.context;
2234
- const next = {
2235
- ...currentWithoutContext,
2236
- additionalData: {
2237
- ...(current.additionalData || {}),
2238
- context: {
2239
- ...((current.additionalData &&
2240
- current.additionalData.context) ||
2241
- {}),
2242
- items: [...currentPages, pageToAdd],
2243
- },
2244
- },
2245
- };
2246
- try {
2247
- if (agent.status === "new") {
2248
- setAgentMetadata(next);
2249
- return;
2250
- }
2251
- await updateAgentContext(agent.id, next);
2252
- setAgentMetadata(next);
2253
- setAgent((prev) => prev ? { ...prev, metadata: JSON.stringify(next) } : prev);
2254
- }
2255
- catch (e) {
2256
- console.error("Failed to update agent metadata (add pages)", e);
2257
- }
2258
- };
2259
- const addSelectedComponentsToContext = async () => {
2260
- if (!agent?.id || !editContext?.selection?.length)
2261
- return;
2262
- const current = agentMetadata || {};
2263
- const currentComponentIds = current.additionalData?.context?.componentIds ||
2264
- [];
2265
- // Merge with existing components, avoiding duplicates
2266
- const existingIds = new Set(currentComponentIds);
2267
- const newComponentIds = editContext.selection.filter((id) => !existingIds.has(id));
2268
- if (newComponentIds.length === 0)
2269
- return; // No new components to add
2270
- // Exclude top-level context to avoid duplicate keys when spreading
2271
- const currentWithoutContext = { ...current };
2272
- delete currentWithoutContext.context;
2273
- const next = {
2274
- ...currentWithoutContext,
2275
- additionalData: {
2276
- ...(current.additionalData || {}),
2277
- context: {
2278
- ...((current.additionalData &&
2279
- current.additionalData.context) ||
2280
- {}),
2281
- componentIds: [...currentComponentIds, ...newComponentIds],
2282
- },
2283
- },
2284
- };
2285
- try {
2286
- if (agent.status === "new") {
2287
- setAgentMetadata(next);
2288
- return;
2289
- }
2290
- await updateAgentContext(agent.id, next);
2291
- setAgentMetadata(next);
2292
- setAgent((prev) => prev ? { ...prev, metadata: JSON.stringify(next) } : prev);
2293
- }
2294
- catch (e) {
2295
- console.error("Failed to update agent metadata (add components)", e);
2296
- }
2297
- };
2298
- const addComponentIdsToContext = async (ids) => {
2299
- if (!agent?.id || !ids?.length)
2300
- return;
2301
- const current = agentMetadata || {};
2302
- const currentComponentIds = current.additionalData?.context?.componentIds ||
2303
- [];
2304
- // Merge with existing components, avoiding duplicates
2305
- const existingIds = new Set(currentComponentIds);
2306
- const newComponentIds = ids.filter((id) => !!id && !existingIds.has(id));
2307
- if (newComponentIds.length === 0)
2308
- return;
2309
- // Exclude top-level context to avoid duplicate keys when spreading
2310
- const currentWithoutContext = { ...current };
2311
- delete currentWithoutContext.context;
2312
- const next = {
2313
- ...currentWithoutContext,
2314
- additionalData: {
2315
- ...(current.additionalData || {}),
2316
- context: {
2317
- ...((current.additionalData &&
2318
- current.additionalData.context) ||
2319
- {}),
2320
- componentIds: [...currentComponentIds, ...newComponentIds],
2321
- },
2322
- },
2323
- };
2324
- try {
2325
- if (agent.status === "new") {
2326
- setAgentMetadata(next);
2327
- return;
2328
- }
2329
- await updateAgentContext(agent.id, next);
2330
- setAgentMetadata(next);
2331
- setAgent((prev) => prev ? { ...prev, metadata: JSON.stringify(next) } : prev);
2332
- }
2333
- catch (e) {
2334
- console.error("Failed to update agent metadata (add components)", e);
2335
- }
2336
- };
2337
- const addPagesToContextFromItems = async (items) => {
2338
- if (!agent?.id || !items?.length)
2339
- return;
2340
- const current = agentMetadata || {};
2341
- const currentPages = current.additionalData?.context?.items || [];
2342
- const existingPageIds = new Set(currentPages.map((p) => `${p.id}-${p.language}-${p.version}`));
2343
- const pagesToAdd = items
2344
- .filter((it) => !!it?.id)
2345
- .map((it) => ({
2346
- id: it.id,
2347
- language: it.language,
2348
- version: it.version,
2349
- }))
2350
- .filter((p) => !existingPageIds.has(`${p.id}-${p.language}-${p.version}`));
2351
- if (pagesToAdd.length === 0)
2352
- return;
2353
- // Exclude top-level context to avoid duplicate keys when spreading
2354
- const currentWithoutContext = { ...current };
2355
- delete currentWithoutContext.context;
2356
- const next = {
2357
- ...currentWithoutContext,
2358
- additionalData: {
2359
- ...(current.additionalData || {}),
2360
- context: {
2361
- ...((current.additionalData &&
2362
- current.additionalData.context) ||
2363
- {}),
2364
- items: [...currentPages, ...pagesToAdd],
2365
- },
2366
- },
2367
- };
2368
- try {
2369
- if (agent.status === "new") {
2370
- setAgentMetadata(next);
2371
- return;
2372
- }
2373
- await updateAgentContext(agent.id, next);
2374
- setAgentMetadata(next);
2375
- setAgent((prev) => prev ? { ...prev, metadata: JSON.stringify(next) } : prev);
2376
- }
2377
- catch (e) {
2378
- console.error("Failed to update agent metadata (add items/pages)", e);
2166
+ // Auto-send initial prompt if provided and agent has no messages yet
2167
+ const initialPromptSentRef = useRef(false);
2168
+ useEffect(() => {
2169
+ if (initialPrompt !== undefined &&
2170
+ !isLoading &&
2171
+ !initialPromptSentRef.current &&
2172
+ messages.length === 0 &&
2173
+ agentStub.id &&
2174
+ editContext &&
2175
+ activeProfile && // MUST have activeProfile set, not just profiles.length
2176
+ profiles.length > 0) {
2177
+ initialPromptSentRef.current = true;
2178
+ // Delay slightly to ensure all state is ready
2179
+ setTimeout(() => {
2180
+ setPrompt(initialPrompt);
2181
+ // Trigger submit programmatically
2182
+ handleSubmit();
2183
+ }, 200);
2379
2184
  }
2380
- };
2185
+ }, [
2186
+ initialPrompt,
2187
+ isLoading,
2188
+ messages.length,
2189
+ agentStub.id,
2190
+ editContext,
2191
+ activeProfile,
2192
+ profiles.length,
2193
+ handleSubmit,
2194
+ ]);
2381
2195
  // Resolve display names when metadata or editor state changes
2382
2196
  useEffect(() => {
2383
2197
  const metaCtx = agentMetadata;
@@ -2551,11 +2365,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2551
2365
  }
2552
2366
  }, children: "Extend limit and continue" }) })] }));
2553
2367
  };
2554
- return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [error && (_jsx("div", { className: "m-4 rounded-lg border-l-4 border-red-500 bg-red-50 p-3 select-text", children: _jsxs("div", { className: "flex items-start", children: [_jsx(AlertCircle, { className: "mt-0.5 h-5 w-5 text-red-400", strokeWidth: 1 }), _jsxs("div", { className: "ml-3", children: [_jsx("p", { className: "text-sm font-medium text-red-800", children: "Error" }), _jsx("p", { className: "mt-1 text-sm text-red-700", children: error })] })] }) })), messages.length === 0 && !error && (_jsx("div", { className: "flex h-full items-center justify-center p-8", children: _jsxs("div", { className: "max-w-prose text-center", children: [activeProfile?.svgIcon ? (_jsx("div", { className: "mx-auto mb-4 flex h-24 w-24 items-center justify-center text-gray-400 [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
2555
- __html: activeProfile.svgIcon,
2556
- } })) : (_jsx(SecretAgentIcon, { size: 96, strokeWidth: 1, className: "mx-auto mb-4 text-gray-400" })), activeProfile?.greetingMessage ? (_jsx("div", { className: "prose prose-sm mx-auto text-center", dangerouslySetInnerHTML: {
2557
- __html: activeProfile.greetingMessage,
2558
- } })) : (_jsxs(_Fragment, { children: [_jsx("h3", { className: "mb-2 text-lg font-medium text-gray-900", children: "Start a conversation" }), _jsx("p", { className: "mb-4 text-sm text-gray-500", children: "Send a message to begin working with your AI agent." }), _jsx("div", { className: "text-xs text-gray-400", children: "Your agent can help with content editing, research, and automation tasks." })] }))] }) })), _jsx("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: groupConsecutiveMessages(messages).map((group, groupIndex) => {
2368
+ return (_jsxs("div", { className: `flex h-full flex-col ${className || ""}`, children: [_jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [error && (_jsx("div", { className: "m-4 rounded-lg border-l-4 border-red-500 bg-red-50 p-3 select-text", children: _jsxs("div", { className: "flex items-start", children: [_jsx(AlertCircle, { className: "mt-0.5 h-5 w-5 text-red-400", strokeWidth: 1 }), _jsxs("div", { className: "ml-3", children: [_jsx("p", { className: "text-sm font-medium text-red-800", children: "Error" }), _jsx("p", { className: "mt-1 text-sm text-red-700", children: error })] })] }) })), messages.length === 0 && !error && !hideGreeting && (_jsx("div", { className: "flex h-full items-center justify-center p-8", children: _jsx("div", { className: "max-w-prose text-center", children: !activeProfile ? (_jsx(Loader2, { className: "h-8 w-8 animate-spin text-gray-400 mx-auto" })) : (_jsxs(_Fragment, { children: [activeProfile.svgIcon ? (_jsx("div", { className: "mx-auto mb-4 flex h-24 w-24 items-center justify-center text-gray-400 [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
2369
+ __html: activeProfile.svgIcon,
2370
+ } })) : (_jsx(SecretAgentIcon, { size: 96, strokeWidth: 1, className: "mx-auto mb-4 text-gray-400" })), activeProfile.greetingMessage ? (_jsx(ViewTransition, { children: _jsx("div", { className: "prose prose-sm mx-auto text-center", dangerouslySetInnerHTML: {
2371
+ __html: activeProfile.greetingMessage,
2372
+ } }) })) : (_jsxs(_Fragment, { children: [_jsx("h3", { className: "mb-2 text-lg font-medium text-gray-900", children: "Start a conversation" }), _jsx("p", { className: "mb-4 text-sm text-gray-500", children: "Send a message to begin working with your AI agent." }), _jsx("div", { className: "text-xs text-gray-400", children: "Your agent can help with content editing, research, and automation tasks." })] }))] })) }) })), _jsx("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: groupConsecutiveMessages(messages).map((group, groupIndex) => {
2559
2373
  if (group.type === "user" && group.messages[0]) {
2560
2374
  // Render user message
2561
2375
  return (_jsx(UserMessage, { message: group.messages[0] }, groupIndex));
@@ -2575,13 +2389,23 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2575
2389
  return null;
2576
2390
  }
2577
2391
  const convertedMessages = convertAgentMessagesToAiFormat(filteredMessages);
2578
- return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isSubmitting && !isConnecting, editOperations: [], error: error || undefined, profileSvgIcon: activeProfile?.svgIcon, onQuickAction: (action) => {
2392
+ return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isSubmitting && !isConnecting, editOperations: [], error: error || undefined, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.name, onQuickAction: (action) => {
2579
2393
  const text = (action.prompt ||
2580
2394
  action.value ||
2581
2395
  action.label ||
2582
2396
  "").trim();
2583
2397
  if (!text)
2584
2398
  return;
2399
+ // Check if text contains placeholders ({placeholder} or <placeholder>)
2400
+ const hasPlaceholders = /\{([^}]+)\}|<([^>]+)>/.test(text);
2401
+ if (hasPlaceholders) {
2402
+ // Show placeholder input
2403
+ setActivePlaceholderInput({
2404
+ text,
2405
+ behavior: action.behavior,
2406
+ });
2407
+ return;
2408
+ }
2585
2409
  if (action.behavior === "compose") {
2586
2410
  setPrompt(text);
2587
2411
  setInputPlaceholder(action.placeholder ||
@@ -2607,154 +2431,204 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2607
2431
  sendQuickMessage(text);
2608
2432
  } }, groupIndex));
2609
2433
  }
2610
- }) }), _jsx("div", { className: showDots ? "visible" : "invisible", children: _jsx(DancingDots, {}) }), renderCostLimitBanner(), _jsx("div", { ref: messagesEndRef })] }), renderContextInfoBar(), _jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata }), _jsxs("div", { className: "border-t border-gray-200 p-4", children: [_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, value: prompt, onChange: (e) => {
2434
+ }) }), _jsx("div", { className: showDots ? "visible" : "invisible", children: _jsx(DancingDots, {}) }), renderCostLimitBanner(), _jsx("div", { ref: messagesEndRef })] }), !hideContext && renderContextInfoBar(), !hideContext && (_jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata })), _jsxs("div", { className: "border-t border-gray-200 p-4", children: [activePlaceholderInput ? (
2435
+ // Placeholder Input (from quick actions)
2436
+ _jsx(PlaceholderInput, { text: activePlaceholderInput.text, showButtons: false, onComplete: (filledText) => {
2437
+ setActivePlaceholderInput(null);
2438
+ if (activePlaceholderInput.behavior === "compose") {
2439
+ setPrompt(filledText);
2440
+ setInputPlaceholder("Review and edit, then press Enter to send");
2441
+ if (textareaRef.current) {
2442
+ try {
2443
+ textareaRef.current.focus();
2444
+ const v = textareaRef.current.value || "";
2445
+ textareaRef.current.selectionStart = v.length;
2446
+ textareaRef.current.selectionEnd = v.length;
2447
+ }
2448
+ catch { }
2449
+ }
2450
+ }
2451
+ else {
2452
+ // Submit behavior or default
2453
+ if (isExecuting) {
2454
+ try {
2455
+ handleStop();
2456
+ }
2457
+ catch { }
2458
+ }
2459
+ sendQuickMessage(filledText);
2460
+ }
2461
+ }, onCancel: () => {
2462
+ setActivePlaceholderInput(null);
2463
+ } })) : prompt && /\{([^}]+)\}|<([^>]+)>/.test(prompt) ? (
2464
+ // Template mode: show PlaceholderInput when prompt contains placeholders
2465
+ _jsx(PlaceholderInput, { text: prompt, showButtons: false, onComplete: (filledText) => {
2466
+ setPrompt(filledText);
2467
+ // Auto-submit after filling placeholders
2468
+ if (filledText.trim()) {
2469
+ if (isExecuting) {
2470
+ try {
2471
+ handleStop();
2472
+ }
2473
+ catch { }
2474
+ }
2475
+ sendQuickMessage(filledText);
2476
+ }
2477
+ }, onCancel: () => {
2478
+ setPrompt("");
2479
+ setInputPlaceholder("Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)");
2480
+ } })) : (_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, value: prompt, onChange: (e) => {
2611
2481
  setPrompt(e.target.value);
2612
2482
  // Reset history index when user starts typing
2613
2483
  if (currentHistoryIndex !== -1) {
2614
2484
  setCurrentHistoryIndex(-1);
2615
2485
  }
2616
- }, onKeyDown: handleKeyPress, placeholder: inputPlaceholder, className: "h-[80px] flex-1 resize-none overflow-y-auto text-xs", "data-testid": "agent-terminal-prompt", disabled: isSubmitting }) }), _jsxs("div", { className: "flex items-stretch justify-between gap-2", children: [_jsxs("div", { className: "mt-2 flex flex-wrap items-center justify-start gap-2", children: [_jsxs(Tooltip, { delayDuration: 400, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("select", { className: `h-5 rounded border px-1.5 text-[10px] ${mode === "read-only"
2617
- ? "border-green-300 bg-green-50 text-green-700"
2618
- : mode === "supervised"
2619
- ? "border-amber-300 bg-amber-50 text-amber-700"
2620
- : "border-red-300 bg-red-50 text-red-700"}`, value: mode, onChange: async (e) => {
2621
- const nextMode = e.target.value || "supervised";
2622
- // Optimistic UI update
2623
- setMode(nextMode);
2624
- const current = agentMetadata || {};
2625
- const nextMeta = {
2626
- ...current,
2627
- mode: nextMode,
2628
- };
2629
- try {
2630
- if (!agent?.id || agent.status === "new") {
2631
- setAgentMetadata(nextMeta);
2632
- // Cache until first start when agent is persisted
2633
- pendingSettingsRef.current = {
2634
- ...(pendingSettingsRef.current || {}),
2486
+ }, onKeyDown: handleKeyPress, placeholder: inputPlaceholder, className: "h-[80px] flex-1 resize-none overflow-y-auto text-xs", "data-testid": "agent-terminal-prompt", disabled: isSubmitting }) })), !hideBottomControls && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-stretch justify-between gap-2", children: [_jsxs("div", { className: "mt-2 flex flex-wrap items-center justify-start gap-2", children: [_jsxs(Tooltip, { delayDuration: 400, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("select", { className: `h-5 rounded border px-1.5 text-[10px] ${mode === "read-only"
2487
+ ? "border-green-300 bg-green-50 text-green-700"
2488
+ : mode === "supervised"
2489
+ ? "border-amber-300 bg-amber-50 text-amber-700"
2490
+ : "border-red-300 bg-red-50 text-red-700"}`, value: mode, onChange: async (e) => {
2491
+ const nextMode = e.target.value || "supervised";
2492
+ // Optimistic UI update
2493
+ setMode(nextMode);
2494
+ const current = agentMetadata || {};
2495
+ const nextMeta = {
2496
+ ...current,
2635
2497
  mode: nextMode,
2636
2498
  };
2637
- return;
2499
+ try {
2500
+ if (!agent?.id || agent.status === "new") {
2501
+ setAgentMetadata(nextMeta);
2502
+ // Cache until first start when agent is persisted
2503
+ pendingSettingsRef.current = {
2504
+ ...(pendingSettingsRef.current || {}),
2505
+ mode: nextMode,
2506
+ };
2507
+ return;
2508
+ }
2509
+ await updateAgentSettings(agent.id, {
2510
+ mode: nextMode,
2511
+ });
2512
+ setAgentMetadata(nextMeta);
2513
+ setAgent((prev) => prev
2514
+ ? { ...prev, metadata: JSON.stringify(nextMeta) }
2515
+ : prev);
2516
+ }
2517
+ catch (e2) {
2518
+ console.error("Failed to persist mode change", e2);
2519
+ }
2520
+ }, title: "Mode", "aria-label": "Mode", "data-testid": "agent-mode-select", children: [_jsx("option", { value: "supervised", children: "Supervised" }), _jsx("option", { value: "autonomous", children: "Autonomous" }), _jsx("option", { value: "read-only", children: "Read-Only" })] }) }), _jsx(TooltipContent, { side: "top", sideOffset: 6, children: _jsxs("div", { className: "max-w-[320px] space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "font-semibold text-green-500", children: "Read-Only" }), ": Limited tool access as configured by the profile (Ask Mode Tools)."] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold text-amber-500", children: "Supervised" }), ": Full tool access, but writes are limited to pages/items in the current context. Creating new items or updating existing items outside the current context requires explicit approval."] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold text-red-500", children: "Autonomous" }), ": Full tool access; can write across the site/project only limited by user permissions."] })] }) })] }), profiles?.length > 0 && (_jsx("select", { className: "h-5 rounded border px-1.5 text-[10px] text-gray-500", value: activeProfile?.id || "", onChange: async (e) => {
2521
+ const nextProfile = profiles.find((x) => x.id === e.target.value);
2522
+ if (!nextProfile)
2523
+ return;
2524
+ setActiveProfile(nextProfile);
2525
+ try {
2526
+ if (agent?.id && agent.status !== "new") {
2527
+ await updateAgentSettings(agent.id, {
2528
+ profileId: nextProfile.id,
2529
+ profileName: nextProfile.name,
2530
+ });
2531
+ }
2532
+ else {
2533
+ // cache until first start
2534
+ pendingSettingsRef.current = {
2535
+ ...(pendingSettingsRef.current || {}),
2536
+ // we cache profile by updating local metadata
2537
+ };
2538
+ setAgentMetadata((current) => {
2539
+ const next = { ...(current || {}) };
2540
+ next.profile = nextProfile.name;
2541
+ next.additionalData = {
2542
+ ...(next.additionalData || {}),
2543
+ profileId: nextProfile.id,
2544
+ profileName: nextProfile.name,
2545
+ };
2546
+ return next;
2547
+ });
2548
+ }
2549
+ // reflect in local agent stub so tabs and titles can use it if needed
2550
+ setAgent((prev) => prev
2551
+ ? {
2552
+ ...prev,
2553
+ metadata: JSON.stringify({
2554
+ ...(agentMetadata || {}),
2555
+ profile: nextProfile.name,
2556
+ additionalData: {
2557
+ ...(agentMetadata
2558
+ ?.additionalData || {}),
2559
+ profileId: nextProfile.id,
2560
+ profileName: nextProfile.name,
2561
+ },
2562
+ }),
2638
2563
  }
2639
- await updateAgentSettings(agent.id, { mode: nextMode });
2640
- setAgentMetadata(nextMeta);
2641
- setAgent((prev) => prev
2642
- ? { ...prev, metadata: JSON.stringify(nextMeta) }
2643
- : prev);
2564
+ : prev);
2565
+ }
2566
+ catch (err) {
2567
+ console.error("Failed to persist agent profile", err);
2568
+ }
2569
+ }, title: "Profile", "aria-label": "Profile", "data-testid": "agent-profile-select", children: profiles.map((p) => (_jsx("option", { value: p.id, children: p.name }, p.id))) })), activeProfile?.models?.length ? (_jsx("select", { className: "h-5 rounded border px-1.5 text-[10px] text-gray-500", value: selectedModelId || "", onChange: async (e) => {
2570
+ const nextId = e.target.value;
2571
+ setSelectedModelId(nextId);
2572
+ const modelName = activeProfile?.models?.find((m) => m.id === nextId)
2573
+ ?.name || "";
2574
+ // Update local agent state immediately for UX and to reflect in streaming stub
2575
+ setAgent((prev) => prev ? { ...prev, model: modelName } : prev);
2576
+ // Persist only for existing agents; otherwise cache until first start
2577
+ try {
2578
+ if (agent?.id && agent.status !== "new") {
2579
+ await updateAgentSettings(agent.id, {
2580
+ model: modelName,
2581
+ });
2644
2582
  }
2645
- catch (e2) {
2646
- console.error("Failed to persist mode change", e2);
2583
+ else {
2584
+ pendingSettingsRef.current = {
2585
+ ...(pendingSettingsRef.current || {}),
2586
+ modelName,
2587
+ };
2647
2588
  }
2648
- }, title: "Mode", "aria-label": "Mode", "data-testid": "agent-mode-select", children: [_jsx("option", { value: "supervised", children: "Supervised" }), _jsx("option", { value: "autonomous", children: "Autonomous" }), _jsx("option", { value: "read-only", children: "Read-Only" })] }) }), _jsx(TooltipContent, { side: "top", sideOffset: 6, children: _jsxs("div", { className: "max-w-[320px] space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "font-semibold text-green-500", children: "Read-Only" }), ": Limited tool access as configured by the profile (Ask Mode Tools)."] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold text-amber-500", children: "Supervised" }), ": Full tool access, but writes are limited to pages/items in the current context. Creating new items or updating existing items outside the current context requires explicit approval."] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold text-red-500", children: "Autonomous" }), ": Full tool access; can write across the site/project only limited by user permissions."] })] }) })] }), profiles?.length > 0 && (_jsx("select", { className: "h-5 rounded border px-1.5 text-[10px] text-gray-500", value: activeProfile?.id || "", onChange: async (e) => {
2649
- const nextProfile = profiles.find((x) => x.id === e.target.value);
2650
- if (!nextProfile)
2651
- return;
2652
- setActiveProfile(nextProfile);
2653
- try {
2654
- if (agent?.id && agent.status !== "new") {
2655
- await updateAgentSettings(agent.id, {
2656
- profileId: nextProfile.id,
2657
- profileName: nextProfile.name,
2658
- });
2659
- }
2660
- else {
2661
- // cache until first start
2662
- pendingSettingsRef.current = {
2663
- ...(pendingSettingsRef.current || {}),
2664
- // we cache profile by updating local metadata
2665
- };
2666
- setAgentMetadata((current) => {
2667
- const next = { ...(current || {}) };
2668
- next.profile = nextProfile.name;
2669
- next.additionalData = {
2670
- ...(next.additionalData || {}),
2671
- profileId: nextProfile.id,
2672
- profileName: nextProfile.name,
2673
- };
2674
- return next;
2675
- });
2676
- }
2677
- // reflect in local agent stub so tabs and titles can use it if needed
2678
- setAgent((prev) => prev
2679
- ? {
2680
- ...prev,
2681
- metadata: JSON.stringify({
2682
- ...(agentMetadata || {}),
2683
- profile: nextProfile.name,
2684
- additionalData: {
2685
- ...(agentMetadata?.additionalData ||
2686
- {}),
2687
- profileId: nextProfile.id,
2688
- profileName: nextProfile.name,
2689
- },
2690
- }),
2691
2589
  }
2692
- : prev);
2693
- }
2694
- catch (err) {
2695
- console.error("Failed to persist agent profile", err);
2590
+ catch (err) {
2591
+ console.error("Failed to persist agent model", err);
2592
+ }
2593
+ }, title: "Model", "aria-label": "Model", "data-testid": "agent-model-select", children: activeProfile.models.map((m) => (_jsx("option", { value: m.id, children: m.name }, m.id))) })) : null, activeProfile?.prompts?.length ? (_jsxs(Popover, { open: showPredefined, onOpenChange: setShowPredefined, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { className: "rounded p-1 hover:bg-gray-100", onClick: () => { }, title: "Predefined prompts", "aria-label": "Predefined prompts", children: _jsx(Wand2, { className: "h-3 w-3", strokeWidth: 1 }) }) }), _jsx(PopoverContent, { className: "w-64 p-0", align: "start", children: _jsx("div", { className: "max-h-56 overflow-y-auto p-2", children: activeProfile.prompts.map((p, index) => (_jsx("div", { className: "cursor-pointer rounded p-1.5 text-xs text-gray-700 hover:bg-gray-100", onClick: () => {
2594
+ setPrompt(p.prompt);
2595
+ setShowPredefined(false);
2596
+ if (textareaRef.current)
2597
+ textareaRef.current.focus();
2598
+ }, children: p.title }, index))) }) })] })) : null] }), _jsxs("div", { className: "flex items-center gap-1 self-end", children: [isVoiceSupported ? (_jsx(Button, { onClick: toggleVoice, size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isListening ? "Stop voice input" : "Start voice input", "aria-label": isListening ? "Stop voice input" : "Start voice input", "aria-pressed": isListening, children: isListening ? (_jsx(MicOff, { className: "size-3", strokeWidth: 1 })) : (_jsx(Mic, { className: "size-3", strokeWidth: 1 })) })) : null, _jsx(Button, { onClick: isExecuting ? handleStop : handleSubmit, disabled: !isExecuting && !prompt.trim(), size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isExecuting ? "Stop" : "Send", "aria-label": isExecuting ? "Stop" : "Send", "data-testid": "agent-send-stop-button", "data-executing": isExecuting ? "true" : "false", children: isExecuting ? (_jsx(Square, { className: "size-3", strokeWidth: 1 })) : (_jsx(Send, { className: "size-3", strokeWidth: 1 })) })] })] }), _jsxs("div", { className: "mt-1 flex items-center gap-2 text-[10px] text-gray-500", children: [_jsx(AgentCostDisplay, { totalTokens: liveTotals
2599
+ ? {
2600
+ input: liveTotals.input,
2601
+ output: liveTotals.output,
2602
+ cached: liveTotals.cached,
2603
+ cacheWrite: liveTotals.cacheWrite ?? 0,
2604
+ inputCost: liveTotals.inputCost,
2605
+ outputCost: liveTotals.outputCost,
2606
+ cachedCost: liveTotals.cachedCost,
2607
+ cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
2608
+ totalCost: liveTotals.totalCost,
2696
2609
  }
2697
- }, title: "Profile", "aria-label": "Profile", "data-testid": "agent-profile-select", children: profiles.map((p) => (_jsx("option", { value: p.id, children: p.name }, p.id))) })), activeProfile?.models?.length ? (_jsx("select", { className: "h-5 rounded border px-1.5 text-[10px] text-gray-500", value: selectedModelId || "", onChange: async (e) => {
2698
- const nextId = e.target.value;
2699
- setSelectedModelId(nextId);
2700
- const modelName = activeProfile?.models?.find((m) => m.id === nextId)?.name ||
2701
- "";
2702
- // Update local agent state immediately for UX and to reflect in streaming stub
2703
- setAgent((prev) => prev ? { ...prev, model: modelName } : prev);
2704
- // Persist only for existing agents; otherwise cache until first start
2705
- try {
2706
- if (agent?.id && agent.status !== "new") {
2707
- await updateAgentSettings(agent.id, { model: modelName });
2708
- }
2709
- else {
2710
- pendingSettingsRef.current = {
2711
- ...(pendingSettingsRef.current || {}),
2712
- modelName,
2713
- };
2610
+ : totalTokens, costLimit: effectiveCostLimit }), (() => {
2611
+ try {
2612
+ const s = window.__agentContextWindowStatus;
2613
+ if (!s || !s.contextWindowTokens)
2614
+ return null;
2615
+ const pct = typeof s.contextUsedPercent === "number"
2616
+ ? `${s.contextUsedPercent.toFixed(1)}%`
2617
+ : undefined;
2618
+ // Helper function to format tokens as "k"
2619
+ const formatTokens = (tokens) => {
2620
+ if (tokens >= 1000) {
2621
+ return `${(tokens / 1000).toFixed(1)}k`;
2714
2622
  }
2715
- }
2716
- catch (err) {
2717
- console.error("Failed to persist agent model", err);
2718
- }
2719
- }, title: "Model", "aria-label": "Model", "data-testid": "agent-model-select", children: activeProfile.models.map((m) => (_jsx("option", { value: m.id, children: m.name }, m.id))) })) : null, activeProfile?.prompts?.length ? (_jsxs(Popover, { open: showPredefined, onOpenChange: setShowPredefined, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { className: "rounded p-1 hover:bg-gray-100", onClick: () => { }, title: "Predefined prompts", "aria-label": "Predefined prompts", children: _jsx(Wand2, { className: "h-3 w-3", strokeWidth: 1 }) }) }), _jsx(PopoverContent, { className: "w-64 p-0", align: "start", children: _jsx("div", { className: "max-h-56 overflow-y-auto p-2", children: activeProfile.prompts.map((p, index) => (_jsx("div", { className: "cursor-pointer rounded p-1.5 text-xs text-gray-700 hover:bg-gray-100", onClick: () => {
2720
- setPrompt(p.prompt);
2721
- setShowPredefined(false);
2722
- if (textareaRef.current)
2723
- textareaRef.current.focus();
2724
- }, children: p.title }, index))) }) })] })) : null] }), _jsxs("div", { className: "flex items-center gap-1 self-end", children: [isVoiceSupported ? (_jsx(Button, { onClick: toggleVoice, size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isListening ? "Stop voice input" : "Start voice input", "aria-label": isListening ? "Stop voice input" : "Start voice input", "aria-pressed": isListening, children: isListening ? (_jsx(MicOff, { className: "size-3", strokeWidth: 1 })) : (_jsx(Mic, { className: "size-3", strokeWidth: 1 })) })) : null, _jsx(Button, { onClick: isExecuting ? handleStop : handleSubmit, disabled: !isExecuting && !prompt.trim(), size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isExecuting ? "Stop" : "Send", "aria-label": isExecuting ? "Stop" : "Send", "data-testid": "agent-send-stop-button", "data-executing": isExecuting ? "true" : "false", children: isExecuting ? (_jsx(Square, { className: "size-3", strokeWidth: 1 })) : (_jsx(Send, { className: "size-3", strokeWidth: 1 })) })] })] }), _jsxs("div", { className: "mt-1 flex items-center gap-2 text-[10px] text-gray-500", children: [_jsx(AgentCostDisplay, { totalTokens: liveTotals
2725
- ? {
2726
- input: liveTotals.input,
2727
- output: liveTotals.output,
2728
- cached: liveTotals.cached,
2729
- cacheWrite: liveTotals.cacheWrite ?? 0,
2730
- inputCost: liveTotals.inputCost,
2731
- outputCost: liveTotals.outputCost,
2732
- cachedCost: liveTotals.cachedCost,
2733
- cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
2734
- totalCost: liveTotals.totalCost,
2735
- }
2736
- : totalTokens, costLimit: effectiveCostLimit }), (() => {
2737
- try {
2738
- const s = window.__agentContextWindowStatus;
2739
- if (!s || !s.contextWindowTokens)
2740
- return null;
2741
- const pct = typeof s.contextUsedPercent === "number"
2742
- ? `${s.contextUsedPercent.toFixed(1)}%`
2743
- : undefined;
2744
- // Helper function to format tokens as "k"
2745
- const formatTokens = (tokens) => {
2746
- if (tokens >= 1000) {
2747
- return `${(tokens / 1000).toFixed(1)}k`;
2623
+ return tokens.toString();
2624
+ };
2625
+ if (!pct)
2626
+ return null;
2627
+ return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("div", { className: "cursor-help rounded border border-gray-200 bg-gray-50 px-2 py-0.5", children: ["Context: ", pct] }) }), _jsx(TooltipContent, { side: "top", sideOffset: 6, children: _jsxs("div", { className: "max-w-[320px] space-y-1 text-xs", children: [_jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Model:" }), " ", s.model, s.normalizedModel && ` (${s.normalizedModel})`] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Context window:" }), " ", formatTokens(s.estimatedInputTokens || 0), " /", " ", formatTokens(s.contextWindowTokens), " tokens"] }), typeof s.maxCompletionTokens === "number" && (_jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Max completion:" }), " ", formatTokens(s.maxCompletionTokens), " tokens"] })), _jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Used:" }), " ", pct] })] }) })] }));
2748
2628
  }
2749
- return tokens.toString();
2750
- };
2751
- if (!pct)
2752
- return null;
2753
- return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("div", { className: "cursor-help rounded border border-gray-200 bg-gray-50 px-2 py-0.5", children: ["Context: ", pct] }) }), _jsx(TooltipContent, { side: "top", sideOffset: 6, children: _jsxs("div", { className: "max-w-[320px] space-y-1 text-xs", children: [_jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Model:" }), " ", s.model, s.normalizedModel && ` (${s.normalizedModel})`] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Context window:" }), " ", formatTokens(s.estimatedInputTokens || 0), " /", " ", formatTokens(s.contextWindowTokens), " tokens"] }), typeof s.maxCompletionTokens === "number" && (_jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Max completion:" }), " ", formatTokens(s.maxCompletionTokens), " tokens"] })), _jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Used:" }), " ", pct] })] }) })] }));
2754
- }
2755
- catch {
2756
- return null;
2757
- }
2758
- })()] })] })] }));
2629
+ catch {
2630
+ return null;
2631
+ }
2632
+ })()] })] }))] })] }));
2759
2633
  }
2760
2634
  //# sourceMappingURL=AgentTerminal.js.map