@burdenoff/microfe-vibecontrols 2026.531.14 → 2026.531.16

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 (39) hide show
  1. package/dist/components/assistant/AssistantChatArea.js +37 -29
  2. package/dist/components/assistant/AssistantChatArea.js.map +1 -1
  3. package/dist/components/docs/DocsTabPanel.js +146 -125
  4. package/dist/components/docs/DocsTabPanel.js.map +1 -1
  5. package/dist/components/notes/NotesTabPanel.js +1 -1
  6. package/dist/components/notes/NotesTabPanel.js.map +1 -1
  7. package/dist/components/shared/PluginGate.js +1 -1
  8. package/dist/components/shared/PluginGate.js.map +1 -1
  9. package/dist/components/tunnels/TunnelCreateWizard.js +1 -1
  10. package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
  11. package/dist/pages/ActionsPage.js +1 -1
  12. package/dist/pages/ActionsPage.js.map +1 -1
  13. package/dist/pages/AgentDetailsPage.js +38 -37
  14. package/dist/pages/AgentDetailsPage.js.map +1 -1
  15. package/dist/pages/AgentsPage.js +1 -1
  16. package/dist/pages/AgentsPage.js.map +1 -1
  17. package/dist/pages/CatalogPage.js +1 -1
  18. package/dist/pages/CatalogPage.js.map +1 -1
  19. package/dist/pages/ComponentDetailPage.js +1 -1
  20. package/dist/pages/ComponentDetailPage.js.map +1 -1
  21. package/dist/pages/ComponentsPage.js +1 -1
  22. package/dist/pages/ComponentsPage.js.map +1 -1
  23. package/dist/pages/SessionDetailsPage.js +1 -1
  24. package/dist/pages/SessionDetailsPage.js.map +1 -1
  25. package/dist/pages/SessionsPage.js +1 -1
  26. package/dist/pages/SessionsPage.js.map +1 -1
  27. package/dist/pages/TargetsPage.js +1 -1
  28. package/dist/pages/TargetsPage.js.map +1 -1
  29. package/dist/pages/TemplateDetailPage.js +1 -1
  30. package/dist/pages/TemplateDetailPage.js.map +1 -1
  31. package/dist/pages/VibeDeckPage.js +1 -1
  32. package/dist/pages/VibeDeckPage.js.map +1 -1
  33. package/dist/pages/VibesPage.js +1 -1
  34. package/dist/pages/VibesPage.js.map +1 -1
  35. package/dist/pages/tunnels/TunnelsTab.js +1 -1
  36. package/dist/pages/tunnels/TunnelsTab.js.map +1 -1
  37. package/dist/providers/QuotaExhaustedProvider.js +1 -1
  38. package/dist/providers/QuotaExhaustedProvider.js.map +1 -1
  39. package/package.json +1 -1
@@ -8,11 +8,16 @@ import { AssistantModeSelector as g } from "./AssistantModeSelector.js";
8
8
  import { AssistantWelcome as _ } from "./AssistantWelcome.js";
9
9
  import { gatherPageContext as te } from "../../utils/pageContext.js";
10
10
  import { dispatchAssistantQuotaExhausted as v, notifyAiCreditsUpdated as ne } from "../../utils/aiCredits.js";
11
- import { useCallback as y, useEffect as b, useRef as x } from "react";
12
- import { useGatewayClient as re } from "@burdenoff/fe-libs/shared/graphql";
13
- import { useAuthToken as ie } from "@burdenoff/fe-libs/shared/providers/shell";
14
- import { jsx as S, jsxs as C } from "react/jsx-runtime";
11
+ import { useCallback as y, useEffect as b, useRef as re } from "react";
12
+ import { useGatewayClient as ie } from "@burdenoff/fe-libs/shared/graphql";
13
+ import { useAuthToken as ae } from "@burdenoff/fe-libs/shared/providers/shell";
14
+ import { jsx as x, jsxs as S } from "react/jsx-runtime";
15
15
  //#region src/components/assistant/AssistantChatArea.tsx
16
+ var oe = {
17
+ general: 18e4,
18
+ "api-calls": 18e4,
19
+ "vibe-plugins": 6e5
20
+ }, C = 18e4;
16
21
  function w() {
17
22
  return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
18
23
  }
@@ -28,13 +33,13 @@ function T(e) {
28
33
  return "";
29
34
  }
30
35
  }
31
- function ae() {
36
+ function se() {
32
37
  return new URLSearchParams(window.location.search).get("workspace") ?? T("workspaceId");
33
38
  }
34
- function oe() {
39
+ function ce() {
35
40
  return new URLSearchParams(window.location.search).get("org") ?? T("organizationId");
36
41
  }
37
- function se(e) {
42
+ function le(e) {
38
43
  let t = e instanceof Error ? e.message.toLowerCase() : String(e).toLowerCase();
39
44
  return t.includes("rate limit exceeded") || t.includes("unauthorized") || t.includes("k8s api error 401") ? !1 : [
40
45
  "sandbox not found",
@@ -77,7 +82,7 @@ function A(e, t) {
77
82
  tokens: r.info?.tokens
78
83
  };
79
84
  }
80
- function ce(e) {
85
+ function ue(e) {
81
86
  switch (e) {
82
87
  case "provisioning": return "Starting assistant environment...";
83
88
  case "connecting": return "Connecting to assistant...";
@@ -86,8 +91,8 @@ function ce(e) {
86
91
  }
87
92
  }
88
93
  var j = () => {
89
- let T = e(), D = re("workspace"), { getAccessToken: O, getWorkspaceToken: k, userId: j } = ie(), { conversations: M, activeConversationId: N, mode: P, processingByConversationId: F, setMode: I, setConversationMode: L, setActiveConversation: R, createConversation: z, addMessage: B, updateMessage: V, patchMessage: H, setConversationProcessing: U, clearConversationProcessing: le, setBackendSessionId: W, clearBackendSessionId: G, setSandbox: K, clearSandbox: q } = n(), J = M.find((e) => e.id === N)?.messages ?? [], Y = N ? F[N] : void 0, ue = Y?.messageId ?? null, de = Y?.status === "provisioning" || Y?.status === "connecting" || Y?.status === "streaming", X = y((e, t, n, r) => {
90
- U(e, n, t, null), r ? V(e, t, r) : V(e, t, ce(n));
94
+ let T = e(), D = ie("workspace"), { getAccessToken: O, getWorkspaceToken: k, userId: j } = ae(), { conversations: M, activeConversationId: N, mode: P, processingByConversationId: F, setMode: I, setConversationMode: L, setActiveConversation: R, createConversation: z, addMessage: B, updateMessage: V, patchMessage: H, setConversationProcessing: U, clearConversationProcessing: de, setBackendSessionId: W, clearBackendSessionId: G, setSandbox: K, clearSandbox: q } = n(), J = M.find((e) => e.id === N)?.messages ?? [], Y = N ? F[N] : void 0, fe = Y?.messageId ?? null, pe = Y?.status === "provisioning" || Y?.status === "connecting" || Y?.status === "streaming", X = y((e, t, n, r) => {
95
+ U(e, n, t, null), r ? V(e, t, r) : V(e, t, ue(n));
91
96
  }, [U, V]), Z = y(async (e, t, r, o, c) => {
92
97
  let l = n.getState().sandboxesByMode[r]?.sandboxId ?? null;
93
98
  if (!l) {
@@ -124,11 +129,11 @@ var j = () => {
124
129
  }
125
130
  let i = n.getState().mode, a = N, s = a ? n.getState().conversations.find((e) => e.id === a) : void 0;
126
131
  if (a ? s && s.mode !== i && (s.messages.length > 0 || s.backendSessionId) ? a = z(void 0, i) : s && s.mode !== i && L(a, i) : a = z(void 0, i), !a) return;
127
- let u = ae(), f = {
132
+ let u = se(), f = {
128
133
  accessToken: O(),
129
134
  workspaceToken: k(),
130
135
  userId: j,
131
- organizationId: oe()
136
+ organizationId: ce()
132
137
  }, p = {
133
138
  id: w(),
134
139
  role: "user",
@@ -168,11 +173,14 @@ var j = () => {
168
173
  X(a, m, "streaming", "Thinking...");
169
174
  let r = Date.now();
170
175
  await ee(D, t, u, n, e, i, te(), f);
171
- let s = A([], r), p = Date.now() + 18e4, g = Date.now();
176
+ let s = A([], r), p = Date.now() + (oe[i] ?? C), g = Date.now();
172
177
  for (; Date.now() < p && (s = A(await l(D, t, u, n, f, 50), r), H(a, m, { content: E(s.content) }), !s.done);) Date.now() - g > 3e4 && (await o(D, t, u, 600, f).catch(() => void 0), g = Date.now()), await new Promise((e) => setTimeout(e, 1500));
173
- if (!s.done) throw Error("Assistant response timed out while streaming progress");
178
+ if (!s.done && i !== "vibe-plugins") throw Error("Assistant response timed out while streaming progress");
174
179
  let _ = i === "vibe-plugins" ? await c(D, t, u, n, f).catch(() => []) : void 0;
175
- if (h(s.content, _, n, t), s.tokens) {
180
+ if (s.done || (s = {
181
+ ...s,
182
+ content: `${s.content}\n\n_Still building in the background — any artifacts below will finish shortly. Re-open this conversation to pick up the completed plugin._`
183
+ }), h(s.content, _, n, t), s.tokens) {
176
184
  H(a, m, { tokens: s.tokens });
177
185
  try {
178
186
  await d(D, u, n, s.tokens, f), ne();
@@ -180,7 +188,7 @@ var j = () => {
180
188
  }
181
189
  U(a, "complete", m, null);
182
190
  } catch (e) {
183
- if (n === 1 && se(e)) return G(a), q(i), g(2);
191
+ if (n === 1 && le(e)) return G(a), q(i), g(2);
184
192
  throw e;
185
193
  }
186
194
  };
@@ -214,9 +222,9 @@ var j = () => {
214
222
  X,
215
223
  V,
216
224
  j
217
- ]), fe = y((e) => {
225
+ ]), me = y((e) => {
218
226
  Q(e);
219
- }, [Q]), $ = x(Q);
227
+ }, [Q]), $ = re(Q);
220
228
  b(() => {
221
229
  $.current = Q;
222
230
  }, [Q]), b(() => {
@@ -228,7 +236,7 @@ var j = () => {
228
236
  }
229
237
  });
230
238
  }, []);
231
- let pe = y((e) => {
239
+ let he = y((e) => {
232
240
  let t = N ? n.getState().conversations.find((e) => e.id === N) : void 0;
233
241
  if (t && (t.messages.length > 0 || t.backendSessionId)) {
234
242
  if (t.mode !== e) {
@@ -244,23 +252,23 @@ var j = () => {
244
252
  L,
245
253
  I
246
254
  ]);
247
- return /* @__PURE__ */ C("div", {
255
+ return /* @__PURE__ */ S("div", {
248
256
  className: "flex flex-col flex-1 overflow-hidden pt-3",
249
- children: [J.length === 0 ? /* @__PURE__ */ S(_, {
257
+ children: [J.length === 0 ? /* @__PURE__ */ x(_, {
250
258
  mode: P,
251
- onSuggestionClick: fe
252
- }) : /* @__PURE__ */ S(h, {
259
+ onSuggestionClick: me
260
+ }) : /* @__PURE__ */ x(h, {
253
261
  messages: J,
254
- streamingMessageId: ue
255
- }), /* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("div", {
262
+ streamingMessageId: fe
263
+ }), /* @__PURE__ */ S("div", { children: [/* @__PURE__ */ x("div", {
256
264
  className: "px-3 pb-1",
257
- children: /* @__PURE__ */ S(g, {
265
+ children: /* @__PURE__ */ x(g, {
258
266
  mode: P,
259
- onModeChange: pe
267
+ onModeChange: he
260
268
  })
261
- }), /* @__PURE__ */ S(m, {
269
+ }), /* @__PURE__ */ x(m, {
262
270
  onSend: (e) => void Q(e),
263
- disabled: de,
271
+ disabled: pe,
264
272
  placeholder: T("vibecontrols.assistant.chatArea.placeholder", `Ask about ${P.replace(/-/g, " ")}...`)
265
273
  })] })]
266
274
  });
@@ -1 +1 @@
1
- {"version":3,"file":"AssistantChatArea.js","names":[],"sources":["../../../src/components/assistant/AssistantChatArea.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, type FC } from 'react';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { useAuthToken } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AssistantChatInput } from './AssistantChatInput';\nimport { AssistantMessageList } from './AssistantMessageList';\nimport { AssistantModeSelector } from './AssistantModeSelector';\nimport { AssistantWelcome } from './AssistantWelcome';\nimport { useAssistantStore } from '@/store/assistantStore';\nimport {\n checkAssistantAiCredits,\n createAssistantSandbox,\n createAssistantSession,\n extendAssistantSandboxTTL,\n findExistingSandbox,\n getAssistantArtifacts,\n getAssistantMessages,\n getAssistantSession,\n reportAssistantAiTokenUsage,\n sendAssistantPromptAsync,\n waitForAssistantServiceReady,\n waitForSandboxReady,\n type AssistantRawMessage,\n type AssistantSandboxAuthContext,\n} from '@/services/assistantApi';\nimport { gatherPageContext } from '@/utils/pageContext';\nimport { dispatchAssistantQuotaExhausted, notifyAiCreditsUpdated } from '@/utils/aiCredits';\nimport { isQuotaExhaustedError } from '@/utils/quotaUtils';\nimport { useTr } from '../../shared/hooks/useTr';\nimport type {\n AssistantArtifact,\n AssistantConversationStatus,\n AssistantMode,\n Message,\n} from '@/types/assistant';\n\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nfunction getProfileContextValue(key: 'workspaceId' | 'organizationId'): string {\n try {\n const activeContextKey =\n key === 'workspaceId'\n ? 'burdenoff-active-context-workspace'\n : 'burdenoff-active-context-organization';\n const activeContextValue = localStorage.getItem(activeContextKey);\n if (activeContextValue) return activeContextValue;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n if (!activeProfileId) return '';\n const raw = localStorage.getItem(`bf-p-${activeProfileId}-context`);\n if (!raw) return '';\n const context = JSON.parse(raw) as {\n workspaceId?: string;\n organizationId?: string;\n };\n return context[key] ?? '';\n } catch {\n return '';\n }\n}\n\nfunction getWorkspaceId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('workspace') ?? getProfileContextValue('workspaceId');\n}\n\nfunction getOrganizationId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('org') ?? getProfileContextValue('organizationId');\n}\n\nfunction isRecoverableAssistantError(error: unknown): boolean {\n const message =\n error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n\n if (\n message.includes('rate limit exceeded') ||\n message.includes('unauthorized') ||\n message.includes('k8s api error 401')\n ) {\n return false;\n }\n\n return [\n 'sandbox not found',\n 'sandbox is not running',\n 'sandbox service not available yet',\n 'sandbox failed',\n 'sandbox startup timed out',\n 'assistant service did not become healthy',\n 'proxy error: 404',\n 'proxy error: 502',\n 'proxy error: 503',\n 'session mode mismatch',\n ].some((fragment) => message.includes(fragment));\n}\n\nfunction sanitizeAssistantResponse(response: string): string {\n return response\n .replace(/(Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/(X-Workspace-Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9._-]+\\.[A-Za-z0-9._-]+\\b/g, '[REDACTED_JWT]')\n .replace(/\\bsk-ant-[A-Za-z0-9-]+\\b/g, '[REDACTED_API_KEY]');\n}\n\nfunction getRawMessageCreatedAt(message: AssistantRawMessage): number {\n return message.info?.time?.created ?? 0;\n}\n\nfunction getAssistantText(parts: AssistantRawMessage['parts']): string {\n return (parts ?? [])\n .filter((part) => part.type === 'text' && typeof part.text === 'string')\n .map((part) => part.text?.trim() ?? '')\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction getToolProgress(parts: AssistantRawMessage['parts']): string[] {\n return (parts ?? [])\n .filter((part) => part.type === 'tool' && part.tool)\n .map((part) => {\n const status = part.state?.status ?? 'running';\n const tool = part.tool ?? 'tool';\n if (status === 'completed') return `Completed: ${tool}`;\n if (status === 'failed') return `Failed: ${tool}`;\n return `Running: ${tool}`;\n });\n}\n\nfunction buildAssistantProgress(\n messages: AssistantRawMessage[],\n sinceMs: number\n): {\n content: string;\n done: boolean;\n tokens?: { input: number; output: number };\n} {\n const relevant = messages\n .filter(\n (message) => message.info?.role === 'assistant' && getRawMessageCreatedAt(message) >= sinceMs\n )\n .sort((left, right) => getRawMessageCreatedAt(left) - getRawMessageCreatedAt(right));\n\n if (relevant.length === 0) {\n return { content: 'Thinking...', done: false };\n }\n\n const latest = relevant[relevant.length - 1]!;\n const text = getAssistantText(latest.parts);\n const toolProgress = getToolProgress(latest.parts);\n const content = [text, ...toolProgress].filter(Boolean).join('\\n\\n') || 'Thinking...';\n\n return {\n content,\n done: Boolean(latest.info?.time?.completed) && (Boolean(text) || toolProgress.length > 0),\n tokens: latest.info?.tokens,\n };\n}\n\nfunction getConversationStatusMessage(status: AssistantConversationStatus): string {\n switch (status) {\n case 'provisioning':\n return 'Starting assistant environment...';\n case 'connecting':\n return 'Connecting to assistant...';\n case 'streaming':\n return 'Thinking...';\n default:\n return '';\n }\n}\n\nexport const AssistantChatArea: FC = () => {\n const tr = useTr();\n const apollo = useGatewayClient('workspace');\n const { getAccessToken, getWorkspaceToken, userId } = useAuthToken();\n\n const {\n conversations,\n activeConversationId,\n mode,\n processingByConversationId,\n setMode,\n setConversationMode,\n setActiveConversation,\n createConversation,\n addMessage,\n updateMessage,\n patchMessage,\n setConversationProcessing,\n clearConversationProcessing,\n setBackendSessionId,\n clearBackendSessionId,\n setSandbox,\n clearSandbox,\n } = useAssistantStore();\n\n const activeConversation = conversations.find(\n (conversation) => conversation.id === activeConversationId\n );\n const messages = activeConversation?.messages ?? [];\n const activeProcessing = activeConversationId\n ? processingByConversationId[activeConversationId]\n : undefined;\n const activeStreamingMessageId = activeProcessing?.messageId ?? null;\n const activeIsProcessing =\n activeProcessing?.status === 'provisioning' ||\n activeProcessing?.status === 'connecting' ||\n activeProcessing?.status === 'streaming';\n\n const updateConversationStage = useCallback(\n (\n conversationId: string,\n assistantMessageId: string,\n status: AssistantConversationStatus,\n content?: string\n ) => {\n setConversationProcessing(conversationId, status, assistantMessageId, null);\n if (content) {\n updateMessage(conversationId, assistantMessageId, content);\n } else {\n updateMessage(conversationId, assistantMessageId, getConversationStatusMessage(status));\n }\n },\n [setConversationProcessing, updateMessage]\n );\n\n const ensureAssistantRuntime = useCallback(\n async (\n conversationId: string,\n assistantMessageId: string,\n currentMode: AssistantMode,\n workspaceId: string,\n authContext: AssistantSandboxAuthContext\n ): Promise<{ sandboxId: string; sessionId: string }> => {\n let currentSandboxId =\n useAssistantStore.getState().sandboxesByMode[currentMode]?.sandboxId ?? null;\n\n if (!currentSandboxId) {\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Looking for existing environment...'\n );\n const existingId = await findExistingSandbox(apollo, workspaceId, currentMode, authContext);\n\n if (existingId) {\n currentSandboxId = existingId;\n setSandbox(currentMode, currentSandboxId, 'provisioning');\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Reusing assistant environment...'\n );\n await waitForSandboxReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 300_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'provisioning', message)\n );\n await waitForAssistantServiceReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 90_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'connecting', message)\n );\n setSandbox(currentMode, currentSandboxId, 'running');\n } else {\n if (currentMode === 'api-calls' && !getAccessToken()) {\n throw new Error(\n 'API Calls mode requires an authenticated session. Please sign in again.'\n );\n }\n\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Starting assistant environment...'\n );\n setSandbox(currentMode, null, 'provisioning');\n currentSandboxId = await createAssistantSandbox(\n apollo,\n currentMode,\n workspaceId,\n authContext\n );\n\n setSandbox(currentMode, currentSandboxId, 'provisioning');\n await waitForSandboxReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 300_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'provisioning', message)\n );\n await waitForAssistantServiceReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 90_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'connecting', message)\n );\n setSandbox(currentMode, currentSandboxId, 'running');\n }\n }\n\n let sessionId =\n useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === conversationId)\n ?.backendSessionId ?? null;\n\n if (sessionId) {\n try {\n const session = await getAssistantSession(\n apollo,\n currentSandboxId,\n workspaceId,\n sessionId,\n authContext\n );\n if (session.mode !== currentMode) {\n throw new Error(\n `Session mode mismatch: expected ${currentMode}, received ${session.mode}`\n );\n }\n } catch {\n clearBackendSessionId(conversationId);\n sessionId = null;\n }\n }\n\n if (!sessionId) {\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'connecting',\n 'Preparing session...'\n );\n const result = await createAssistantSession(\n apollo,\n currentSandboxId,\n workspaceId,\n currentMode,\n authContext\n );\n sessionId = result.sessionId;\n setBackendSessionId(conversationId, sessionId);\n }\n\n return {\n sandboxId: currentSandboxId,\n sessionId,\n };\n },\n [\n apollo,\n clearBackendSessionId,\n getAccessToken,\n setBackendSessionId,\n setSandbox,\n updateConversationStage,\n ]\n );\n\n const handleSend = useCallback(\n async (content: string) => {\n // Only block re-entry while a turn is genuinely in-flight. Earlier code\n // checked for *any* entry in the map, which stuck around as `complete`\n // / `error` after the first turn — making every subsequent message\n // silently disappear (the input cleared but handleSend bailed before\n // adding the user message to the conversation).\n if (activeConversationId) {\n const inflightStatus = processingByConversationId[activeConversationId]?.status;\n if (\n inflightStatus === 'provisioning' ||\n inflightStatus === 'connecting' ||\n inflightStatus === 'streaming'\n ) {\n return;\n }\n }\n\n const selectedMode = useAssistantStore.getState().mode;\n let conversationId = activeConversationId;\n const currentConversation = conversationId\n ? useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === conversationId)\n : undefined;\n\n if (!conversationId) {\n conversationId = createConversation(undefined, selectedMode);\n } else if (\n currentConversation &&\n currentConversation.mode !== selectedMode &&\n (currentConversation.messages.length > 0 || currentConversation.backendSessionId)\n ) {\n conversationId = createConversation(undefined, selectedMode);\n } else if (currentConversation && currentConversation.mode !== selectedMode) {\n setConversationMode(conversationId, selectedMode);\n }\n\n if (!conversationId) return;\n\n const workspaceId = getWorkspaceId();\n const authContext: AssistantSandboxAuthContext = {\n accessToken: getAccessToken(),\n workspaceToken: getWorkspaceToken(),\n userId,\n organizationId: getOrganizationId(),\n };\n\n const userMessage: Message = {\n id: generateId(),\n role: 'user',\n content,\n timestamp: new Date().toISOString(),\n };\n addMessage(conversationId, userMessage);\n\n const assistantMessageId = generateId();\n addMessage(conversationId, {\n id: assistantMessageId,\n role: 'assistant',\n content: '',\n timestamp: new Date().toISOString(),\n });\n\n const applyAssistantResponse = (\n response: string,\n artifacts?: AssistantArtifact[],\n sessionId?: string,\n sandboxId?: string\n ) => {\n patchMessage(conversationId, assistantMessageId, {\n content: sanitizeAssistantResponse(response),\n metadata:\n artifacts?.length || sessionId || sandboxId\n ? {\n artifacts,\n mode: selectedMode,\n sessionId,\n sandboxId,\n }\n : undefined,\n });\n };\n\n const sendWithRealBackend = async (attempt: 1 | 2 = 1): Promise<void> => {\n // Pre-flight AI-credit gate. The wspace gateway's @usage(checkQuota)\n // rejects with 429 QUOTA_EXHAUSTED when the monthly token budget is\n // depleted — block the send (don't provision a sandbox or dispatch the\n // prompt) and surface the upgrade dialog instead of a generic error.\n try {\n await checkAssistantAiCredits(apollo, workspaceId, authContext);\n } catch (creditError) {\n if (isQuotaExhaustedError(creditError)) {\n dispatchAssistantQuotaExhausted('ai-credits');\n applyAssistantResponse(\n \"You've run out of AI credits for this billing cycle. Upgrade your plan to keep chatting.\"\n );\n setConversationProcessing(conversationId, 'error', assistantMessageId, null);\n return;\n }\n throw creditError;\n }\n\n try {\n const { sandboxId, sessionId } = await ensureAssistantRuntime(\n conversationId,\n assistantMessageId,\n selectedMode,\n workspaceId,\n authContext\n );\n\n updateConversationStage(conversationId, assistantMessageId, 'streaming', 'Thinking...');\n const promptStartedAt = Date.now();\n await sendAssistantPromptAsync(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n content,\n selectedMode,\n gatherPageContext(),\n authContext\n );\n\n let result = buildAssistantProgress([], promptStartedAt);\n const timeoutAt = Date.now() + 180_000;\n let lastTtlExtensionAt = Date.now();\n\n while (Date.now() < timeoutAt) {\n const sessionMessages = await getAssistantMessages(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n authContext,\n 50\n );\n\n result = buildAssistantProgress(sessionMessages, promptStartedAt);\n patchMessage(conversationId, assistantMessageId, {\n content: sanitizeAssistantResponse(result.content),\n });\n\n if (result.done) break;\n\n if (Date.now() - lastTtlExtensionAt > 30_000) {\n await extendAssistantSandboxTTL(\n apollo,\n sandboxId,\n workspaceId,\n 600,\n authContext\n ).catch(() => undefined);\n lastTtlExtensionAt = Date.now();\n }\n\n await new Promise((resolve) => setTimeout(resolve, 1500));\n }\n\n if (!result.done) {\n throw new Error('Assistant response timed out while streaming progress');\n }\n\n const artifacts =\n selectedMode === 'vibe-plugins'\n ? await getAssistantArtifacts(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n authContext\n ).catch(() => [])\n : undefined;\n\n applyAssistantResponse(result.content, artifacts, sessionId, sandboxId);\n\n if (result.tokens) {\n patchMessage(conversationId, assistantMessageId, { tokens: result.tokens });\n // Charge the real token usage against the AI-credits quota (the\n // gateway records it via @usage(trackUsage)). We AWAIT it before\n // marking the turn complete / re-enabling input: otherwise a rapid\n // follow-up send would run its pre-flight checkAiCredits against a\n // stale balance (this turn's charge still in flight) and could\n // dispatch a prompt that should have been blocked, letting fast\n // sends bypass the credit cap. Still best-effort — a charge failure\n // never blocks the displayed answer.\n try {\n await reportAssistantAiTokenUsage(\n apollo,\n workspaceId,\n sessionId,\n result.tokens,\n authContext\n );\n notifyAiCreditsUpdated();\n } catch {\n // ignore — server-side @usage tracking is the source of truth\n }\n }\n setConversationProcessing(conversationId, 'complete', assistantMessageId, null);\n } catch (error) {\n if (attempt === 1 && isRecoverableAssistantError(error)) {\n clearBackendSessionId(conversationId);\n clearSandbox(selectedMode);\n return sendWithRealBackend(2);\n }\n\n throw error;\n }\n };\n\n try {\n if (!workspaceId) {\n throw new Error('Assistant backend is unavailable without an active workspace context.');\n }\n await sendWithRealBackend();\n } catch (error) {\n // A quota error reaching here is the sandbox-environment wall hit\n // during runtime provisioning (createSandbox @usage). Surface the\n // upgrade dialog instead of a generic failure (the AI-credit wall is\n // already handled by the pre-flight gate above).\n if (isQuotaExhaustedError(error)) {\n dispatchAssistantQuotaExhausted('sandbox');\n applyAssistantResponse(\n \"You've reached your sandbox limit on the current plan. Upgrade to start the AI assistant.\"\n );\n setConversationProcessing(conversationId, 'error', assistantMessageId, null);\n return;\n }\n const errorMessage = error instanceof Error ? error.message : 'Something went wrong';\n setConversationProcessing(conversationId, 'error', assistantMessageId, errorMessage);\n updateMessage(\n conversationId,\n assistantMessageId,\n `Sorry, an error occurred: ${errorMessage}. Please try again.`\n );\n }\n },\n [\n activeConversationId,\n addMessage,\n apollo,\n clearBackendSessionId,\n clearSandbox,\n conversations,\n createConversation,\n ensureAssistantRuntime,\n getAccessToken,\n getWorkspaceToken,\n patchMessage,\n processingByConversationId,\n setBackendSessionId,\n setConversationMode,\n setConversationProcessing,\n updateConversationStage,\n updateMessage,\n userId,\n ]\n );\n\n const handleSuggestionClick = useCallback(\n (suggestion: string) => {\n void handleSend(suggestion);\n },\n [handleSend]\n );\n\n // Auto-send any prompt that was queued by `useOpenAssistantHelp` (from\n // the shared GetHelpButton). We keep the latest handleSend in a ref so\n // the subscription effect below doesn't need to tear down on every\n // render — the store subscription lives for the component lifetime and\n // always calls the freshest handler.\n const handleSendRef = useRef(handleSend);\n useEffect(() => {\n handleSendRef.current = handleSend;\n }, [handleSend]);\n\n useEffect(() => {\n // Flush any prompt that was queued before this component mounted.\n const initial = useAssistantStore.getState().consumePendingPrompt();\n if (initial) {\n void handleSendRef.current(initial);\n }\n\n // Watch for new prompts queued while we're already mounted.\n const unsubscribe = useAssistantStore.subscribe((state, prev) => {\n if (state.pendingPrompt && state.pendingPrompt !== prev.pendingPrompt) {\n const queued = useAssistantStore.getState().consumePendingPrompt();\n if (queued) {\n void handleSendRef.current(queued);\n }\n }\n });\n return unsubscribe;\n }, []);\n\n const handleModeChange = useCallback(\n (newMode: AssistantMode) => {\n const currentConversation = activeConversationId\n ? useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === activeConversationId)\n : undefined;\n\n if (\n currentConversation &&\n (currentConversation.messages.length > 0 || currentConversation.backendSessionId)\n ) {\n if (currentConversation.mode !== newMode) {\n const reusableDraft = useAssistantStore\n .getState()\n .conversations.find(\n (conversation) =>\n conversation.mode === newMode &&\n conversation.messages.length === 0 &&\n !conversation.backendSessionId &&\n !useAssistantStore.getState().processingByConversationId[conversation.id]\n );\n\n if (reusableDraft) {\n setActiveConversation(reusableDraft.id);\n } else {\n createConversation(undefined, newMode);\n }\n }\n } else if (activeConversationId) {\n setConversationMode(activeConversationId, newMode);\n }\n\n setMode(newMode);\n },\n [activeConversationId, createConversation, setActiveConversation, setConversationMode, setMode]\n );\n\n return (\n <div className=\"flex flex-col flex-1 overflow-hidden pt-3\">\n {messages.length === 0 ? (\n <AssistantWelcome mode={mode} onSuggestionClick={handleSuggestionClick} />\n ) : (\n <AssistantMessageList messages={messages} streamingMessageId={activeStreamingMessageId} />\n )}\n\n <div>\n <div className=\"px-3 pb-1\">\n <AssistantModeSelector mode={mode} onModeChange={handleModeChange} />\n </div>\n <AssistantChatInput\n onSend={(value) => void handleSend(value)}\n disabled={activeIsProcessing}\n placeholder={tr(\n 'vibecontrols.assistant.chatArea.placeholder',\n `Ask about ${mode.replace(/-/g, ' ')}...`\n )}\n />\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;AAmCA,SAAS,IAAqB;AAC5B,QAAO,GAAG,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE;;AAGhE,SAAS,EAAuB,GAA+C;AAC7E,KAAI;EACF,IAAM,IACJ,MAAQ,gBACJ,uCACA,yCACA,IAAqB,aAAa,QAAQ,EAAiB;AACjE,MAAI,EAAoB,QAAO;EAE/B,IAAM,IAAkB,eAAe,QAAQ,oBAAoB;AACnE,MAAI,CAAC,EAAiB,QAAO;EAC7B,IAAM,IAAM,aAAa,QAAQ,QAAQ,EAAgB,UAAU;AAMnE,SALK,IACW,KAAK,MAAM,EAAI,CAIhB,MAAQ,KALN;SAMX;AACN,SAAO;;;AAIX,SAAS,KAAyB;AAEhC,QADe,IAAI,gBAAgB,OAAO,SAAS,OAAO,CAC5C,IAAI,YAAY,IAAI,EAAuB,cAAc;;AAGzE,SAAS,KAA4B;AAEnC,QADe,IAAI,gBAAgB,OAAO,SAAS,OAAO,CAC5C,IAAI,MAAM,IAAI,EAAuB,iBAAiB;;AAGtE,SAAS,GAA4B,GAAyB;CAC5D,IAAM,IACJ,aAAiB,QAAQ,EAAM,QAAQ,aAAa,GAAG,OAAO,EAAM,CAAC,aAAa;AAUpF,QAPE,EAAQ,SAAS,sBAAsB,IACvC,EAAQ,SAAS,eAAe,IAChC,EAAQ,SAAS,oBAAoB,GAE9B,KAGF;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,MAAM,MAAa,EAAQ,SAAS,EAAS,CAAC;;AAGlD,SAAS,EAA0B,GAA0B;AAC3D,QAAO,EACJ,QAAQ,6CAA6C,eAAe,CACpE,QAAQ,yDAAyD,eAAe,CAChF,QAAQ,4DAA4D,iBAAiB,CACrF,QAAQ,6BAA6B,qBAAqB;;AAG/D,SAAS,EAAuB,GAAsC;AACpE,QAAO,EAAQ,MAAM,MAAM,WAAW;;AAGxC,SAAS,EAAiB,GAA6C;AACrE,SAAQ,KAAS,EAAE,EAChB,QAAQ,MAAS,EAAK,SAAS,UAAU,OAAO,EAAK,QAAS,SAAS,CACvE,KAAK,MAAS,EAAK,MAAM,MAAM,IAAI,GAAG,CACtC,OAAO,QAAQ,CACf,KAAK,KAAK;;AAGf,SAAS,EAAgB,GAA+C;AACtE,SAAQ,KAAS,EAAE,EAChB,QAAQ,MAAS,EAAK,SAAS,UAAU,EAAK,KAAK,CACnD,KAAK,MAAS;EACb,IAAM,IAAS,EAAK,OAAO,UAAU,WAC/B,IAAO,EAAK,QAAQ;AAG1B,SAFI,MAAW,cAAoB,cAAc,MAC7C,MAAW,WAAiB,WAAW,MACpC,YAAY;GACnB;;AAGN,SAAS,EACP,GACA,GAKA;CACA,IAAM,IAAW,EACd,QACE,MAAY,EAAQ,MAAM,SAAS,eAAe,EAAuB,EAAQ,IAAI,EACvF,CACA,MAAM,GAAM,MAAU,EAAuB,EAAK,GAAG,EAAuB,EAAM,CAAC;AAEtF,KAAI,EAAS,WAAW,EACtB,QAAO;EAAE,SAAS;EAAe,MAAM;EAAO;CAGhD,IAAM,IAAS,EAAS,EAAS,SAAS,IACpC,IAAO,EAAiB,EAAO,MAAM,EACrC,IAAe,EAAgB,EAAO,MAAM;AAGlD,QAAO;EACL,SAHc,CAAC,GAAM,GAAG,EAAa,CAAC,OAAO,QAAQ,CAAC,KAAK,OAAO,IAAI;EAItE,MAAM,EAAQ,EAAO,MAAM,MAAM,cAAe,EAAQ,KAAS,EAAa,SAAS;EACvF,QAAQ,EAAO,MAAM;EACtB;;AAGH,SAAS,GAA6B,GAA6C;AACjF,SAAQ,GAAR;EACE,KAAK,eACH,QAAO;EACT,KAAK,aACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,QACE,QAAO;;;AAIb,IAAa,UAA8B;CACzC,IAAM,IAAK,GAAO,EACZ,IAAS,GAAiB,YAAY,EACtC,EAAE,mBAAgB,sBAAmB,cAAW,IAAc,EAE9D,EACJ,kBACA,yBACA,SACA,+BACA,YACA,wBACA,0BACA,uBACA,eACA,kBACA,iBACA,8BACA,iCACA,wBACA,0BACA,eACA,oBACE,GAAmB,EAKjB,IAHqB,EAAc,MACtC,MAAiB,EAAa,OAAO,EACvC,EACoC,YAAY,EAAE,EAC7C,IAAmB,IACrB,EAA2B,KAC3B,KAAA,GACE,KAA2B,GAAkB,aAAa,MAC1D,KACJ,GAAkB,WAAW,kBAC7B,GAAkB,WAAW,gBAC7B,GAAkB,WAAW,aAEzB,IAA0B,GAE5B,GACA,GACA,GACA,MACG;AAEH,EADA,EAA0B,GAAgB,GAAQ,GAAoB,KAAK,EACvE,IACF,EAAc,GAAgB,GAAoB,EAAQ,GAE1D,EAAc,GAAgB,GAAoB,GAA6B,EAAO,CAAC;IAG3F,CAAC,GAA2B,EAAc,CAC3C,EAEK,IAAyB,EAC7B,OACE,GACA,GACA,GACA,GACA,MACsD;EACtD,IAAI,IACF,EAAkB,UAAU,CAAC,gBAAgB,IAAc,aAAa;AAE1E,MAAI,CAAC,GAAkB;AACrB,KACE,GACA,GACA,gBACA,sCACD;GACD,IAAM,IAAa,MAAM,EAAoB,GAAQ,GAAa,GAAa,EAAY;AAE3F,OAAI,EA2BF,CA1BA,IAAmB,GACnB,EAAW,GAAa,GAAkB,eAAe,EACzD,EACE,GACA,GACA,gBACA,mCACD,EACD,MAAM,EACJ,GACA,GACA,GACA,GACA,MACC,MACC,EAAwB,GAAgB,GAAoB,gBAAgB,EAAQ,CACvF,EACD,MAAM,EACJ,GACA,GACA,GACA,GACA,MACC,MACC,EAAwB,GAAgB,GAAoB,cAAc,EAAQ,CACrF,EACD,EAAW,GAAa,GAAkB,UAAU;QAC/C;AACL,QAAI,MAAgB,eAAe,CAAC,GAAgB,CAClD,OAAU,MACR,0EACD;AAoCH,IAjCA,EACE,GACA,GACA,gBACA,oCACD,EACD,EAAW,GAAa,MAAM,eAAe,EAC7C,IAAmB,MAAM,EACvB,GACA,GACA,GACA,EACD,EAED,EAAW,GAAa,GAAkB,eAAe,EACzD,MAAM,EACJ,GACA,GACA,GACA,GACA,MACC,MACC,EAAwB,GAAgB,GAAoB,gBAAgB,EAAQ,CACvF,EACD,MAAM,EACJ,GACA,GACA,GACA,GACA,MACC,MACC,EAAwB,GAAgB,GAAoB,cAAc,EAAQ,CACrF,EACD,EAAW,GAAa,GAAkB,UAAU;;;EAIxD,IAAI,IACF,EACG,UAAU,CACV,cAAc,MAAM,MAAiB,EAAa,OAAO,EAAe,EACvE,oBAAoB;AAE1B,MAAI,EACF,KAAI;GACF,IAAM,IAAU,MAAM,EACpB,GACA,GACA,GACA,GACA,EACD;AACD,OAAI,EAAQ,SAAS,EACnB,OAAU,MACR,mCAAmC,EAAY,aAAa,EAAQ,OACrE;UAEG;AAEN,GADA,EAAsB,EAAe,EACrC,IAAY;;AAsBhB,SAlBK,MACH,EACE,GACA,GACA,cACA,uBACD,EAQD,KAPe,MAAM,EACnB,GACA,GACA,GACA,GACA,EACD,EACkB,WACnB,EAAoB,GAAgB,EAAU,GAGzC;GACL,WAAW;GACX;GACD;IAEH;EACE;EACA;EACA;EACA;EACA;EACA;EACD,CACF,EAEK,IAAa,EACjB,OAAO,MAAoB;AAMzB,MAAI,GAAsB;GACxB,IAAM,IAAiB,EAA2B,IAAuB;AACzE,OACE,MAAmB,kBACnB,MAAmB,gBACnB,MAAmB,YAEnB;;EAIJ,IAAM,IAAe,EAAkB,UAAU,CAAC,MAC9C,IAAiB,GACf,IAAsB,IACxB,EACG,UAAU,CACV,cAAc,MAAM,MAAiB,EAAa,OAAO,EAAe,GAC3E,KAAA;AAcJ,MAZK,IAGH,KACA,EAAoB,SAAS,MAC5B,EAAoB,SAAS,SAAS,KAAK,EAAoB,oBAEhE,IAAiB,EAAmB,KAAA,GAAW,EAAa,GACnD,KAAuB,EAAoB,SAAS,KAC7D,EAAoB,GAAgB,EAAa,GARjD,IAAiB,EAAmB,KAAA,GAAW,EAAa,EAW1D,CAAC,EAAgB;EAErB,IAAM,IAAc,IAAgB,EAC9B,IAA2C;GAC/C,aAAa,GAAgB;GAC7B,gBAAgB,GAAmB;GACnC;GACA,gBAAgB,IAAmB;GACpC,EAEK,IAAuB;GAC3B,IAAI,GAAY;GAChB,MAAM;GACN;GACA,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC;AACD,IAAW,GAAgB,EAAY;EAEvC,IAAM,IAAqB,GAAY;AACvC,IAAW,GAAgB;GACzB,IAAI;GACJ,MAAM;GACN,SAAS;GACT,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC,CAAC;EAEF,IAAM,KACJ,GACA,GACA,GACA,MACG;AACH,KAAa,GAAgB,GAAoB;IAC/C,SAAS,EAA0B,EAAS;IAC5C,UACE,GAAW,UAAU,KAAa,IAC9B;KACE;KACA,MAAM;KACN;KACA;KACD,GACD,KAAA;IACP,CAAC;KAGE,IAAsB,OAAO,IAAiB,MAAqB;AAKvE,OAAI;AACF,UAAM,EAAwB,GAAQ,GAAa,EAAY;YACxD,GAAa;AACpB,QAAI,EAAsB,EAAY,EAAE;AAKtC,KAJA,EAAgC,aAAa,EAC7C,EACE,2FACD,EACD,EAA0B,GAAgB,SAAS,GAAoB,KAAK;AAC5E;;AAEF,UAAM;;AAGR,OAAI;IACF,IAAM,EAAE,cAAW,iBAAc,MAAM,EACrC,GACA,GACA,GACA,GACA,EACD;AAED,MAAwB,GAAgB,GAAoB,aAAa,cAAc;IACvF,IAAM,IAAkB,KAAK,KAAK;AAClC,UAAM,GACJ,GACA,GACA,GACA,GACA,GACA,GACA,IAAmB,EACnB,EACD;IAED,IAAI,IAAS,EAAuB,EAAE,EAAE,EAAgB,EAClD,IAAY,KAAK,KAAK,GAAG,MAC3B,IAAqB,KAAK,KAAK;AAEnC,WAAO,KAAK,KAAK,GAAG,MAUlB,IAAS,EATe,MAAM,EAC5B,GACA,GACA,GACA,GACA,GACA,GACD,EAEgD,EAAgB,EACjE,EAAa,GAAgB,GAAoB,EAC/C,SAAS,EAA0B,EAAO,QAAQ,EACnD,CAAC,EAEE,GAAO,OAaX,CAXI,KAAK,KAAK,GAAG,IAAqB,QACpC,MAAM,EACJ,GACA,GACA,GACA,KACA,EACD,CAAC,YAAY,KAAA,EAAU,EACxB,IAAqB,KAAK,KAAK,GAGjC,MAAM,IAAI,SAAS,MAAY,WAAW,GAAS,KAAK,CAAC;AAG3D,QAAI,CAAC,EAAO,KACV,OAAU,MAAM,wDAAwD;IAG1E,IAAM,IACJ,MAAiB,iBACb,MAAM,EACJ,GACA,GACA,GACA,GACA,EACD,CAAC,YAAY,EAAE,CAAC,GACjB,KAAA;AAIN,QAFA,EAAuB,EAAO,SAAS,GAAW,GAAW,EAAU,EAEnE,EAAO,QAAQ;AACjB,OAAa,GAAgB,GAAoB,EAAE,QAAQ,EAAO,QAAQ,CAAC;AAS3E,SAAI;AAQF,MAPA,MAAM,EACJ,GACA,GACA,GACA,EAAO,QACP,EACD,EACD,IAAwB;aAClB;;AAIV,MAA0B,GAAgB,YAAY,GAAoB,KAAK;YACxE,GAAO;AACd,QAAI,MAAY,KAAK,GAA4B,EAAM,CAGrD,QAFA,EAAsB,EAAe,EACrC,EAAa,EAAa,EACnB,EAAoB,EAAE;AAG/B,UAAM;;;AAIV,MAAI;AACF,OAAI,CAAC,EACH,OAAU,MAAM,wEAAwE;AAE1F,SAAM,GAAqB;WACpB,GAAO;AAKd,OAAI,EAAsB,EAAM,EAAE;AAKhC,IAJA,EAAgC,UAAU,EAC1C,EACE,4FACD,EACD,EAA0B,GAAgB,SAAS,GAAoB,KAAK;AAC5E;;GAEF,IAAM,IAAe,aAAiB,QAAQ,EAAM,UAAU;AAE9D,GADA,EAA0B,GAAgB,SAAS,GAAoB,EAAa,EACpF,EACE,GACA,GACA,6BAA6B,EAAa,qBAC3C;;IAGL;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF,EAEK,KAAwB,GAC3B,MAAuB;AACjB,IAAW,EAAW;IAE7B,CAAC,EAAW,CACb,EAOK,IAAgB,EAAO,EAAW;AAKxC,CAJA,QAAgB;AACd,IAAc,UAAU;IACvB,CAAC,EAAW,CAAC,EAEhB,QAAgB;EAEd,IAAM,IAAU,EAAkB,UAAU,CAAC,sBAAsB;AAcnE,SAbI,KACG,EAAc,QAAQ,EAAQ,EAIjB,EAAkB,WAAW,GAAO,MAAS;AAC/D,OAAI,EAAM,iBAAiB,EAAM,kBAAkB,EAAK,eAAe;IACrE,IAAM,IAAS,EAAkB,UAAU,CAAC,sBAAsB;AAClE,IAAI,KACG,EAAc,QAAQ,EAAO;;IAGtC;IAED,EAAE,CAAC;CAEN,IAAM,KAAmB,GACtB,MAA2B;EAC1B,IAAM,IAAsB,IACxB,EACG,UAAU,CACV,cAAc,MAAM,MAAiB,EAAa,OAAO,EAAqB,GACjF,KAAA;AAEJ,MACE,MACC,EAAoB,SAAS,SAAS,KAAK,EAAoB;OAE5D,EAAoB,SAAS,GAAS;IACxC,IAAM,IAAgB,EACnB,UAAU,CACV,cAAc,MACZ,MACC,EAAa,SAAS,KACtB,EAAa,SAAS,WAAW,KACjC,CAAC,EAAa,oBACd,CAAC,EAAkB,UAAU,CAAC,2BAA2B,EAAa,IACzE;AAEH,IAAI,IACF,EAAsB,EAAc,GAAG,GAEvC,EAAmB,KAAA,GAAW,EAAQ;;SAGjC,KACT,EAAoB,GAAsB,EAAQ;AAGpD,IAAQ,EAAQ;IAElB;EAAC;EAAsB;EAAoB;EAAuB;EAAqB;EAAQ,CAChG;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACG,EAAS,WAAW,IACnB,kBAAC,GAAD;GAAwB;GAAM,mBAAmB;GAAyB,CAAA,GAE1E,kBAAC,GAAD;GAAgC;GAAU,oBAAoB;GAA4B,CAAA,EAG5F,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IAA6B;IAAM,cAAc;IAAoB,CAAA;GACjE,CAAA,EACN,kBAAC,GAAD;GACE,SAAS,MAAU,KAAK,EAAW,EAAM;GACzC,UAAU;GACV,aAAa,EACX,+CACA,aAAa,EAAK,QAAQ,MAAM,IAAI,CAAC,KACtC;GACD,CAAA,CACE,EAAA,CAAA,CACF"}
1
+ {"version":3,"file":"AssistantChatArea.js","names":[],"sources":["../../../src/components/assistant/AssistantChatArea.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, type FC } from 'react';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { useAuthToken } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AssistantChatInput } from './AssistantChatInput';\nimport { AssistantMessageList } from './AssistantMessageList';\nimport { AssistantModeSelector } from './AssistantModeSelector';\nimport { AssistantWelcome } from './AssistantWelcome';\nimport { useAssistantStore } from '@/store/assistantStore';\nimport {\n checkAssistantAiCredits,\n createAssistantSandbox,\n createAssistantSession,\n extendAssistantSandboxTTL,\n findExistingSandbox,\n getAssistantArtifacts,\n getAssistantMessages,\n getAssistantSession,\n reportAssistantAiTokenUsage,\n sendAssistantPromptAsync,\n waitForAssistantServiceReady,\n waitForSandboxReady,\n type AssistantRawMessage,\n type AssistantSandboxAuthContext,\n} from '@/services/assistantApi';\nimport { gatherPageContext } from '@/utils/pageContext';\nimport { dispatchAssistantQuotaExhausted, notifyAiCreditsUpdated } from '@/utils/aiCredits';\nimport { isQuotaExhaustedError } from '@/utils/quotaUtils';\nimport { useTr } from '../../shared/hooks/useTr';\nimport type {\n AssistantArtifact,\n AssistantConversationStatus,\n AssistantMode,\n Message,\n} from '@/types/assistant';\n\n// How long the client streams progress before giving up on a turn. Chat replies\n// (general / api-calls) finish in seconds; a vibe-plugins build is a multi-step\n// pipeline (read docs → scaffold → bun install → bun build → checklist) that runs\n// for minutes, so it gets a much larger budget. The sandbox TTL is auto-extended\n// every 30s during the poll loop, so a longer budget never outlives the sandbox.\nconst STREAM_BUDGET_MS: Record<AssistantMode, number> = {\n general: 180_000,\n 'api-calls': 180_000,\n 'vibe-plugins': 600_000,\n};\nconst DEFAULT_STREAM_BUDGET_MS = 180_000;\n\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nfunction getProfileContextValue(key: 'workspaceId' | 'organizationId'): string {\n try {\n const activeContextKey =\n key === 'workspaceId'\n ? 'burdenoff-active-context-workspace'\n : 'burdenoff-active-context-organization';\n const activeContextValue = localStorage.getItem(activeContextKey);\n if (activeContextValue) return activeContextValue;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n if (!activeProfileId) return '';\n const raw = localStorage.getItem(`bf-p-${activeProfileId}-context`);\n if (!raw) return '';\n const context = JSON.parse(raw) as {\n workspaceId?: string;\n organizationId?: string;\n };\n return context[key] ?? '';\n } catch {\n return '';\n }\n}\n\nfunction getWorkspaceId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('workspace') ?? getProfileContextValue('workspaceId');\n}\n\nfunction getOrganizationId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('org') ?? getProfileContextValue('organizationId');\n}\n\nfunction isRecoverableAssistantError(error: unknown): boolean {\n const message =\n error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n\n if (\n message.includes('rate limit exceeded') ||\n message.includes('unauthorized') ||\n message.includes('k8s api error 401')\n ) {\n return false;\n }\n\n return [\n 'sandbox not found',\n 'sandbox is not running',\n 'sandbox service not available yet',\n 'sandbox failed',\n 'sandbox startup timed out',\n 'assistant service did not become healthy',\n 'proxy error: 404',\n 'proxy error: 502',\n 'proxy error: 503',\n 'session mode mismatch',\n ].some((fragment) => message.includes(fragment));\n}\n\nfunction sanitizeAssistantResponse(response: string): string {\n return response\n .replace(/(Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/(X-Workspace-Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9._-]+\\.[A-Za-z0-9._-]+\\b/g, '[REDACTED_JWT]')\n .replace(/\\bsk-ant-[A-Za-z0-9-]+\\b/g, '[REDACTED_API_KEY]');\n}\n\nfunction getRawMessageCreatedAt(message: AssistantRawMessage): number {\n return message.info?.time?.created ?? 0;\n}\n\nfunction getAssistantText(parts: AssistantRawMessage['parts']): string {\n return (parts ?? [])\n .filter((part) => part.type === 'text' && typeof part.text === 'string')\n .map((part) => part.text?.trim() ?? '')\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction getToolProgress(parts: AssistantRawMessage['parts']): string[] {\n return (parts ?? [])\n .filter((part) => part.type === 'tool' && part.tool)\n .map((part) => {\n const status = part.state?.status ?? 'running';\n const tool = part.tool ?? 'tool';\n if (status === 'completed') return `Completed: ${tool}`;\n if (status === 'failed') return `Failed: ${tool}`;\n return `Running: ${tool}`;\n });\n}\n\nfunction buildAssistantProgress(\n messages: AssistantRawMessage[],\n sinceMs: number\n): {\n content: string;\n done: boolean;\n tokens?: { input: number; output: number };\n} {\n const relevant = messages\n .filter(\n (message) => message.info?.role === 'assistant' && getRawMessageCreatedAt(message) >= sinceMs\n )\n .sort((left, right) => getRawMessageCreatedAt(left) - getRawMessageCreatedAt(right));\n\n if (relevant.length === 0) {\n return { content: 'Thinking...', done: false };\n }\n\n const latest = relevant[relevant.length - 1]!;\n const text = getAssistantText(latest.parts);\n const toolProgress = getToolProgress(latest.parts);\n const content = [text, ...toolProgress].filter(Boolean).join('\\n\\n') || 'Thinking...';\n\n return {\n content,\n done: Boolean(latest.info?.time?.completed) && (Boolean(text) || toolProgress.length > 0),\n tokens: latest.info?.tokens,\n };\n}\n\nfunction getConversationStatusMessage(status: AssistantConversationStatus): string {\n switch (status) {\n case 'provisioning':\n return 'Starting assistant environment...';\n case 'connecting':\n return 'Connecting to assistant...';\n case 'streaming':\n return 'Thinking...';\n default:\n return '';\n }\n}\n\nexport const AssistantChatArea: FC = () => {\n const tr = useTr();\n const apollo = useGatewayClient('workspace');\n const { getAccessToken, getWorkspaceToken, userId } = useAuthToken();\n\n const {\n conversations,\n activeConversationId,\n mode,\n processingByConversationId,\n setMode,\n setConversationMode,\n setActiveConversation,\n createConversation,\n addMessage,\n updateMessage,\n patchMessage,\n setConversationProcessing,\n clearConversationProcessing,\n setBackendSessionId,\n clearBackendSessionId,\n setSandbox,\n clearSandbox,\n } = useAssistantStore();\n\n const activeConversation = conversations.find(\n (conversation) => conversation.id === activeConversationId\n );\n const messages = activeConversation?.messages ?? [];\n const activeProcessing = activeConversationId\n ? processingByConversationId[activeConversationId]\n : undefined;\n const activeStreamingMessageId = activeProcessing?.messageId ?? null;\n const activeIsProcessing =\n activeProcessing?.status === 'provisioning' ||\n activeProcessing?.status === 'connecting' ||\n activeProcessing?.status === 'streaming';\n\n const updateConversationStage = useCallback(\n (\n conversationId: string,\n assistantMessageId: string,\n status: AssistantConversationStatus,\n content?: string\n ) => {\n setConversationProcessing(conversationId, status, assistantMessageId, null);\n if (content) {\n updateMessage(conversationId, assistantMessageId, content);\n } else {\n updateMessage(conversationId, assistantMessageId, getConversationStatusMessage(status));\n }\n },\n [setConversationProcessing, updateMessage]\n );\n\n const ensureAssistantRuntime = useCallback(\n async (\n conversationId: string,\n assistantMessageId: string,\n currentMode: AssistantMode,\n workspaceId: string,\n authContext: AssistantSandboxAuthContext\n ): Promise<{ sandboxId: string; sessionId: string }> => {\n let currentSandboxId =\n useAssistantStore.getState().sandboxesByMode[currentMode]?.sandboxId ?? null;\n\n if (!currentSandboxId) {\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Looking for existing environment...'\n );\n const existingId = await findExistingSandbox(apollo, workspaceId, currentMode, authContext);\n\n if (existingId) {\n currentSandboxId = existingId;\n setSandbox(currentMode, currentSandboxId, 'provisioning');\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Reusing assistant environment...'\n );\n await waitForSandboxReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 300_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'provisioning', message)\n );\n await waitForAssistantServiceReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 90_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'connecting', message)\n );\n setSandbox(currentMode, currentSandboxId, 'running');\n } else {\n if (currentMode === 'api-calls' && !getAccessToken()) {\n throw new Error(\n 'API Calls mode requires an authenticated session. Please sign in again.'\n );\n }\n\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Starting assistant environment...'\n );\n setSandbox(currentMode, null, 'provisioning');\n currentSandboxId = await createAssistantSandbox(\n apollo,\n currentMode,\n workspaceId,\n authContext\n );\n\n setSandbox(currentMode, currentSandboxId, 'provisioning');\n await waitForSandboxReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 300_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'provisioning', message)\n );\n await waitForAssistantServiceReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 90_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'connecting', message)\n );\n setSandbox(currentMode, currentSandboxId, 'running');\n }\n }\n\n let sessionId =\n useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === conversationId)\n ?.backendSessionId ?? null;\n\n if (sessionId) {\n try {\n const session = await getAssistantSession(\n apollo,\n currentSandboxId,\n workspaceId,\n sessionId,\n authContext\n );\n if (session.mode !== currentMode) {\n throw new Error(\n `Session mode mismatch: expected ${currentMode}, received ${session.mode}`\n );\n }\n } catch {\n clearBackendSessionId(conversationId);\n sessionId = null;\n }\n }\n\n if (!sessionId) {\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'connecting',\n 'Preparing session...'\n );\n const result = await createAssistantSession(\n apollo,\n currentSandboxId,\n workspaceId,\n currentMode,\n authContext\n );\n sessionId = result.sessionId;\n setBackendSessionId(conversationId, sessionId);\n }\n\n return {\n sandboxId: currentSandboxId,\n sessionId,\n };\n },\n [\n apollo,\n clearBackendSessionId,\n getAccessToken,\n setBackendSessionId,\n setSandbox,\n updateConversationStage,\n ]\n );\n\n const handleSend = useCallback(\n async (content: string) => {\n // Only block re-entry while a turn is genuinely in-flight. Earlier code\n // checked for *any* entry in the map, which stuck around as `complete`\n // / `error` after the first turn — making every subsequent message\n // silently disappear (the input cleared but handleSend bailed before\n // adding the user message to the conversation).\n if (activeConversationId) {\n const inflightStatus = processingByConversationId[activeConversationId]?.status;\n if (\n inflightStatus === 'provisioning' ||\n inflightStatus === 'connecting' ||\n inflightStatus === 'streaming'\n ) {\n return;\n }\n }\n\n const selectedMode = useAssistantStore.getState().mode;\n let conversationId = activeConversationId;\n const currentConversation = conversationId\n ? useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === conversationId)\n : undefined;\n\n if (!conversationId) {\n conversationId = createConversation(undefined, selectedMode);\n } else if (\n currentConversation &&\n currentConversation.mode !== selectedMode &&\n (currentConversation.messages.length > 0 || currentConversation.backendSessionId)\n ) {\n conversationId = createConversation(undefined, selectedMode);\n } else if (currentConversation && currentConversation.mode !== selectedMode) {\n setConversationMode(conversationId, selectedMode);\n }\n\n if (!conversationId) return;\n\n const workspaceId = getWorkspaceId();\n const authContext: AssistantSandboxAuthContext = {\n accessToken: getAccessToken(),\n workspaceToken: getWorkspaceToken(),\n userId,\n organizationId: getOrganizationId(),\n };\n\n const userMessage: Message = {\n id: generateId(),\n role: 'user',\n content,\n timestamp: new Date().toISOString(),\n };\n addMessage(conversationId, userMessage);\n\n const assistantMessageId = generateId();\n addMessage(conversationId, {\n id: assistantMessageId,\n role: 'assistant',\n content: '',\n timestamp: new Date().toISOString(),\n });\n\n const applyAssistantResponse = (\n response: string,\n artifacts?: AssistantArtifact[],\n sessionId?: string,\n sandboxId?: string\n ) => {\n patchMessage(conversationId, assistantMessageId, {\n content: sanitizeAssistantResponse(response),\n metadata:\n artifacts?.length || sessionId || sandboxId\n ? {\n artifacts,\n mode: selectedMode,\n sessionId,\n sandboxId,\n }\n : undefined,\n });\n };\n\n const sendWithRealBackend = async (attempt: 1 | 2 = 1): Promise<void> => {\n // Pre-flight AI-credit gate. The wspace gateway's @usage(checkQuota)\n // rejects with 429 QUOTA_EXHAUSTED when the monthly token budget is\n // depleted — block the send (don't provision a sandbox or dispatch the\n // prompt) and surface the upgrade dialog instead of a generic error.\n try {\n await checkAssistantAiCredits(apollo, workspaceId, authContext);\n } catch (creditError) {\n if (isQuotaExhaustedError(creditError)) {\n dispatchAssistantQuotaExhausted('ai-credits');\n applyAssistantResponse(\n \"You've run out of AI credits for this billing cycle. Upgrade your plan to keep chatting.\"\n );\n setConversationProcessing(conversationId, 'error', assistantMessageId, null);\n return;\n }\n throw creditError;\n }\n\n try {\n const { sandboxId, sessionId } = await ensureAssistantRuntime(\n conversationId,\n assistantMessageId,\n selectedMode,\n workspaceId,\n authContext\n );\n\n updateConversationStage(conversationId, assistantMessageId, 'streaming', 'Thinking...');\n const promptStartedAt = Date.now();\n await sendAssistantPromptAsync(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n content,\n selectedMode,\n gatherPageContext(),\n authContext\n );\n\n let result = buildAssistantProgress([], promptStartedAt);\n const timeoutAt =\n Date.now() + (STREAM_BUDGET_MS[selectedMode] ?? DEFAULT_STREAM_BUDGET_MS);\n let lastTtlExtensionAt = Date.now();\n\n while (Date.now() < timeoutAt) {\n const sessionMessages = await getAssistantMessages(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n authContext,\n 50\n );\n\n result = buildAssistantProgress(sessionMessages, promptStartedAt);\n patchMessage(conversationId, assistantMessageId, {\n content: sanitizeAssistantResponse(result.content),\n });\n\n if (result.done) break;\n\n if (Date.now() - lastTtlExtensionAt > 30_000) {\n await extendAssistantSandboxTTL(\n apollo,\n sandboxId,\n workspaceId,\n 600,\n authContext\n ).catch(() => undefined);\n lastTtlExtensionAt = Date.now();\n }\n\n await new Promise((resolve) => setTimeout(resolve, 1500));\n }\n\n // For non-build modes a timeout is a hard failure. For vibe-plugins the\n // prompt runs async server-side (/sessions/:id/prompt-async) and keeps\n // writing artifacts after the poll budget elapses, so we surface whatever\n // it has produced instead of discarding the turn.\n if (!result.done && selectedMode !== 'vibe-plugins') {\n throw new Error('Assistant response timed out while streaming progress');\n }\n\n const artifacts =\n selectedMode === 'vibe-plugins'\n ? await getAssistantArtifacts(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n authContext\n ).catch(() => [])\n : undefined;\n\n if (!result.done) {\n // vibe-plugins build still running in the background. Append a note so\n // the user knows the answer is partial and the artifacts will finish.\n result = {\n ...result,\n content: `${result.content}\\n\\n_Still building in the background — any artifacts below will finish shortly. Re-open this conversation to pick up the completed plugin._`,\n };\n }\n\n applyAssistantResponse(result.content, artifacts, sessionId, sandboxId);\n\n if (result.tokens) {\n patchMessage(conversationId, assistantMessageId, { tokens: result.tokens });\n // Charge the real token usage against the AI-credits quota (the\n // gateway records it via @usage(trackUsage)). We AWAIT it before\n // marking the turn complete / re-enabling input: otherwise a rapid\n // follow-up send would run its pre-flight checkAiCredits against a\n // stale balance (this turn's charge still in flight) and could\n // dispatch a prompt that should have been blocked, letting fast\n // sends bypass the credit cap. Still best-effort — a charge failure\n // never blocks the displayed answer.\n try {\n await reportAssistantAiTokenUsage(\n apollo,\n workspaceId,\n sessionId,\n result.tokens,\n authContext\n );\n notifyAiCreditsUpdated();\n } catch {\n // ignore — server-side @usage tracking is the source of truth\n }\n }\n setConversationProcessing(conversationId, 'complete', assistantMessageId, null);\n } catch (error) {\n if (attempt === 1 && isRecoverableAssistantError(error)) {\n clearBackendSessionId(conversationId);\n clearSandbox(selectedMode);\n return sendWithRealBackend(2);\n }\n\n throw error;\n }\n };\n\n try {\n if (!workspaceId) {\n throw new Error('Assistant backend is unavailable without an active workspace context.');\n }\n await sendWithRealBackend();\n } catch (error) {\n // A quota error reaching here is the sandbox-environment wall hit\n // during runtime provisioning (createSandbox @usage). Surface the\n // upgrade dialog instead of a generic failure (the AI-credit wall is\n // already handled by the pre-flight gate above).\n if (isQuotaExhaustedError(error)) {\n dispatchAssistantQuotaExhausted('sandbox');\n applyAssistantResponse(\n \"You've reached your sandbox limit on the current plan. Upgrade to start the AI assistant.\"\n );\n setConversationProcessing(conversationId, 'error', assistantMessageId, null);\n return;\n }\n const errorMessage = error instanceof Error ? error.message : 'Something went wrong';\n setConversationProcessing(conversationId, 'error', assistantMessageId, errorMessage);\n updateMessage(\n conversationId,\n assistantMessageId,\n `Sorry, an error occurred: ${errorMessage}. Please try again.`\n );\n }\n },\n [\n activeConversationId,\n addMessage,\n apollo,\n clearBackendSessionId,\n clearSandbox,\n conversations,\n createConversation,\n ensureAssistantRuntime,\n getAccessToken,\n getWorkspaceToken,\n patchMessage,\n processingByConversationId,\n setBackendSessionId,\n setConversationMode,\n setConversationProcessing,\n updateConversationStage,\n updateMessage,\n userId,\n ]\n );\n\n const handleSuggestionClick = useCallback(\n (suggestion: string) => {\n void handleSend(suggestion);\n },\n [handleSend]\n );\n\n // Auto-send any prompt that was queued by `useOpenAssistantHelp` (from\n // the shared GetHelpButton). We keep the latest handleSend in a ref so\n // the subscription effect below doesn't need to tear down on every\n // render — the store subscription lives for the component lifetime and\n // always calls the freshest handler.\n const handleSendRef = useRef(handleSend);\n useEffect(() => {\n handleSendRef.current = handleSend;\n }, [handleSend]);\n\n useEffect(() => {\n // Flush any prompt that was queued before this component mounted.\n const initial = useAssistantStore.getState().consumePendingPrompt();\n if (initial) {\n void handleSendRef.current(initial);\n }\n\n // Watch for new prompts queued while we're already mounted.\n const unsubscribe = useAssistantStore.subscribe((state, prev) => {\n if (state.pendingPrompt && state.pendingPrompt !== prev.pendingPrompt) {\n const queued = useAssistantStore.getState().consumePendingPrompt();\n if (queued) {\n void handleSendRef.current(queued);\n }\n }\n });\n return unsubscribe;\n }, []);\n\n const handleModeChange = useCallback(\n (newMode: AssistantMode) => {\n const currentConversation = activeConversationId\n ? useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === activeConversationId)\n : undefined;\n\n if (\n currentConversation &&\n (currentConversation.messages.length > 0 || currentConversation.backendSessionId)\n ) {\n if (currentConversation.mode !== newMode) {\n const reusableDraft = useAssistantStore\n .getState()\n .conversations.find(\n (conversation) =>\n conversation.mode === newMode &&\n conversation.messages.length === 0 &&\n !conversation.backendSessionId &&\n !useAssistantStore.getState().processingByConversationId[conversation.id]\n );\n\n if (reusableDraft) {\n setActiveConversation(reusableDraft.id);\n } else {\n createConversation(undefined, newMode);\n }\n }\n } else if (activeConversationId) {\n setConversationMode(activeConversationId, newMode);\n }\n\n setMode(newMode);\n },\n [activeConversationId, createConversation, setActiveConversation, setConversationMode, setMode]\n );\n\n return (\n <div className=\"flex flex-col flex-1 overflow-hidden pt-3\">\n {messages.length === 0 ? (\n <AssistantWelcome mode={mode} onSuggestionClick={handleSuggestionClick} />\n ) : (\n <AssistantMessageList messages={messages} streamingMessageId={activeStreamingMessageId} />\n )}\n\n <div>\n <div className=\"px-3 pb-1\">\n <AssistantModeSelector mode={mode} onModeChange={handleModeChange} />\n </div>\n <AssistantChatInput\n onSend={(value) => void handleSend(value)}\n disabled={activeIsProcessing}\n placeholder={tr(\n 'vibecontrols.assistant.chatArea.placeholder',\n `Ask about ${mode.replace(/-/g, ' ')}...`\n )}\n />\n </div>\n </div>\n );\n};\n"],"mappings":"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"}