@burdenoff/microfe-vibecontrols 2026.829.1 → 2026.905.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/components/assistant/AIAssistantPanel.js +34 -67
  2. package/dist/components/assistant/AIAssistantPanel.js.map +1 -1
  3. package/dist/components/assistant/AssistantChatArea.js +11 -281
  4. package/dist/components/assistant/AssistantChatInput.js +4 -44
  5. package/dist/components/assistant/AssistantConversationList.js +6 -155
  6. package/dist/components/assistant/AssistantExportMenu.js +5 -52
  7. package/dist/components/assistant/AssistantMessageItem.js +9 -99
  8. package/dist/components/assistant/AssistantMessageList.js +4 -28
  9. package/dist/components/assistant/AssistantModeSelector.js +5 -60
  10. package/dist/components/assistant/AssistantWelcome.js +4 -42
  11. package/dist/components/assistant/index.js +1 -1
  12. package/dist/hooks/useAiCreditsQuota.js +8 -8
  13. package/dist/hooks/useWorkspaceWriteAccess.js +14 -0
  14. package/dist/hooks/useWorkspaceWriteAccess.js.map +1 -0
  15. package/dist/index.js +3 -3
  16. package/dist/pages/SessionsPage.js +6 -6
  17. package/dist/plugin.js +82 -64
  18. package/dist/plugin.js.map +1 -1
  19. package/dist/services/assistantApi.js +17 -48
  20. package/dist/services/assistantApi.js.map +1 -1
  21. package/dist/utils/assistantPrompt.js +34 -0
  22. package/dist/utils/assistantPrompt.js.map +1 -0
  23. package/dist/utils/pageContext.js +34 -15
  24. package/dist/utils/pageContext.js.map +1 -1
  25. package/package.json +2 -2
  26. package/dist/components/assistant/AssistantChatArea.js.map +0 -1
  27. package/dist/components/assistant/AssistantChatInput.js.map +0 -1
  28. package/dist/components/assistant/AssistantConversationList.js.map +0 -1
  29. package/dist/components/assistant/AssistantExportMenu.js.map +0 -1
  30. package/dist/components/assistant/AssistantMessageItem.js.map +0 -1
  31. package/dist/components/assistant/AssistantMessageList.js.map +0 -1
  32. package/dist/components/assistant/AssistantModeSelector.js.map +0 -1
  33. package/dist/components/assistant/AssistantWelcome.js.map +0 -1
  34. package/dist/hooks/useAssistantHeartbeat.js +0 -40
  35. package/dist/hooks/useAssistantHeartbeat.js.map +0 -1
  36. package/dist/hooks/useAssistantKeyboardShortcuts.js +0 -37
  37. package/dist/hooks/useAssistantKeyboardShortcuts.js.map +0 -1
  38. package/dist/services/assistantExportService.js +0 -44
  39. package/dist/services/assistantExportService.js.map +0 -1
@@ -1,73 +1,40 @@
1
- import { useTr as e } from "../../shared/hooks/useTr.js";
2
- import { useAssistantStore as t } from "../../store/assistantStore.js";
3
- import { useAssistantKeyboardShortcuts as n } from "../../hooks/useAssistantKeyboardShortcuts.js";
4
- import { useAssistantHeartbeat as r } from "../../hooks/useAssistantHeartbeat.js";
5
- import { AssistantChatArea as i } from "./AssistantChatArea.js";
6
- import { AiCreditsMeter as a } from "./AiCreditsMeter.js";
7
- import { AssistantConversationList as o } from "./AssistantConversationList.js";
8
- import { AssistantExportMenu as s } from "./AssistantExportMenu.js";
9
- import { useRef as c } from "react";
10
- import { Eraser as l, MessageSquare as u, X as d } from "lucide-react";
11
- import { jsx as f, jsxs as p } from "react/jsx-runtime";
1
+ import { ASSISTANT_MODES as e } from "../../types/assistant.js";
2
+ import { PRODUCT as t, reportAssistantTokenUsage as n } from "../../services/assistantApi.js";
3
+ import { useWorkspaceWriteAccess as r } from "../../hooks/useWorkspaceWriteAccess.js";
4
+ import { describeCurrentPage as i, gatherPageContext as a } from "../../utils/pageContext.js";
5
+ import { buildPromptForMode as o } from "../../utils/assistantPrompt.js";
6
+ import { AiCreditsMeter as s } from "./AiCreditsMeter.js";
7
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
8
+ import { AIAssistantPanel as u } from "@burdenoff/fe-libs/shared/assistant/ui/AIAssistantPanel";
9
+ import { AssistantProductProvider as d } from "@burdenoff/fe-libs/shared/assistant/product";
10
+ import { getActorId as f, getWorkspaceId as p } from "@burdenoff/fe-libs/shared/assistant/context";
12
11
  //#region src/components/assistant/AIAssistantPanel.tsx
13
- var m = ({ onClose: m, visible: h }) => {
14
- let g = e(), _ = c(null), { conversations: v, activeConversationId: y, conversationListOpen: b, toggleConversationList: x, clearConversation: S } = t(), C = v.find((e) => e.id === y);
15
- r(C?.backendSessionId ?? null, C?.mode ?? null, h ?? !0);
16
- let w = (C?.messages.length ?? 0) > 0;
17
- return n({
18
- panelRef: _,
19
- onClose: m
20
- }), /* @__PURE__ */ p("div", {
21
- ref: _,
22
- className: "flex flex-col h-full w-full bg-bg-surface relative",
23
- role: "complementary",
24
- "aria-label": g("vibecontrols.assistant.panel.ariaLabel", "AI Assistant"),
25
- "data-testid": "ai-assistant-panel",
26
- tabIndex: -1,
27
- children: [
28
- /* @__PURE__ */ p("div", {
29
- className: "flex items-center justify-between px-3 py-2 border-b border-border-default bg-bg-elevated flex-shrink-0",
30
- children: [/* @__PURE__ */ p("div", {
31
- className: "flex items-center gap-2 min-w-0",
32
- children: [/* @__PURE__ */ f("button", {
33
- type: "button",
34
- onClick: x,
35
- className: "p-1 hover:bg-bg-sunken rounded transition-colors",
36
- "aria-label": g("vibecontrols.assistant.panel.toggleList", "Toggle conversation list"),
37
- "data-testid": "toggle-conversation-list",
38
- children: /* @__PURE__ */ f(u, { className: "size-3.5 text-text-secondary" })
39
- }), /* @__PURE__ */ f("span", {
40
- className: "text-xs font-semibold text-text-primary truncate",
41
- children: C?.title ?? g("vibecontrols.assistant.panel.title", "AI Assistant")
42
- })]
43
- }), /* @__PURE__ */ p("div", {
44
- className: "flex items-center gap-0.5 flex-shrink-0",
45
- children: [
46
- w && /* @__PURE__ */ f("button", {
47
- type: "button",
48
- onClick: () => y && S(y),
49
- className: "p-1 hover:bg-bg-sunken rounded transition-colors",
50
- "aria-label": g("vibecontrols.assistant.panel.clearConversation", "Clear conversation"),
51
- children: /* @__PURE__ */ f(l, { className: "size-3.5 text-text-secondary" })
52
- }),
53
- /* @__PURE__ */ f(s, {}),
54
- /* @__PURE__ */ f("button", {
55
- type: "button",
56
- onClick: m,
57
- className: "p-1 hover:bg-bg-sunken rounded transition-colors md:hidden",
58
- "aria-label": g("vibecontrols.assistant.panel.close", "Close assistant"),
59
- children: /* @__PURE__ */ f(d, { className: "size-3.5 text-text-secondary" })
60
- })
61
- ]
62
- })]
63
- }),
64
- /* @__PURE__ */ f(a, {}),
65
- b && /* @__PURE__ */ f(o, {}),
66
- /* @__PURE__ */ f(i, {})
67
- ]
12
+ var m = {
13
+ attachments: "/VibeControls/AI Assistant/Attachments",
14
+ artifacts: "/VibeControls/AI Assistant/Artifacts"
15
+ }, h = (h) => {
16
+ let g = r(f(), p() || null);
17
+ return /* @__PURE__ */ c(d, {
18
+ product: t,
19
+ modes: e,
20
+ describeCurrentPage: i,
21
+ gatherPageContext: a,
22
+ buildPrompt: o,
23
+ reportTokenUsage: n,
24
+ storageFolders: m,
25
+ children: /* @__PURE__ */ l("div", {
26
+ className: "flex flex-col h-full w-full min-h-0",
27
+ children: [/* @__PURE__ */ c(s, {}), /* @__PURE__ */ c("div", {
28
+ className: "flex-1 min-h-0",
29
+ children: /* @__PURE__ */ c(u, {
30
+ ...h,
31
+ writeAccess: g
32
+ })
33
+ })]
34
+ })
68
35
  });
69
36
  };
70
37
  //#endregion
71
- export { m as AIAssistantPanel };
38
+ export { h as AIAssistantPanel };
72
39
 
73
40
  //# sourceMappingURL=AIAssistantPanel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AIAssistantPanel.js","names":[],"sources":["../../../src/components/assistant/AIAssistantPanel.tsx"],"sourcesContent":["/**\n * AI Assistant Panel top-level component for the right-pane assistant.\n *\n * Receives PanelProps from the plugin panel system. Orchestrates the\n * conversation list, chat area, and export menu.\n *\n * Responsive behavior:\n * - Desktop (>= 768px): Renders inline within PanelContainer (right panel)\n * - Mobile (< 768px): Panel container handles sizing; panel fills available space\n */\n\nimport { useRef, type FC } from 'react';\nimport { MessageSquare, Eraser, X } from 'lucide-react';\nimport type { PanelProps } from '@burdenoff/fe-libs/shared/plugins';\nimport { useAssistantStore } from '@/store/assistantStore';\nimport { useAssistantKeyboardShortcuts } from '@/hooks/useAssistantKeyboardShortcuts';\nimport { useAssistantHeartbeat } from '@/hooks/useAssistantHeartbeat';\nimport { AssistantChatArea } from './AssistantChatArea';\nimport { AiCreditsMeter } from './AiCreditsMeter';\nimport { AssistantConversationList } from './AssistantConversationList';\nimport { AssistantExportMenu } from './AssistantExportMenu';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport const AIAssistantPanel: FC<PanelProps> = ({ onClose, visible }) => {\n const tr = useTr();\n const panelRef = useRef<HTMLDivElement>(null);\n\n const {\n conversations,\n activeConversationId,\n conversationListOpen,\n toggleConversationList,\n clearConversation,\n } = useAssistantStore();\n\n const activeConversation = conversations.find((c) => c.id === activeConversationId);\n const activeSessionId = activeConversation?.backendSessionId ?? null;\n\n // Keep sandbox alive while panel is open\n useAssistantHeartbeat(activeSessionId, activeConversation?.mode ?? null, visible ?? true);\n\n const hasMessages = (activeConversation?.messages.length ?? 0) > 0;\n\n useAssistantKeyboardShortcuts({\n panelRef,\n onClose,\n });\n\n return (\n <div\n ref={panelRef}\n className=\"flex flex-col h-full w-full bg-bg-surface relative\"\n role=\"complementary\"\n aria-label={tr('vibecontrols.assistant.panel.ariaLabel', 'AI Assistant')}\n data-testid=\"ai-assistant-panel\"\n tabIndex={-1}\n >\n {/* Panel header */}\n <div className=\"flex items-center justify-between px-3 py-2 border-b border-border-default bg-bg-elevated flex-shrink-0\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <button\n type=\"button\"\n onClick={toggleConversationList}\n className=\"p-1 hover:bg-bg-sunken rounded transition-colors\"\n aria-label={tr('vibecontrols.assistant.panel.toggleList', 'Toggle conversation list')}\n data-testid=\"toggle-conversation-list\"\n >\n <MessageSquare className=\"size-3.5 text-text-secondary\" />\n </button>\n <span className=\"text-xs font-semibold text-text-primary truncate\">\n {activeConversation?.title ?? tr('vibecontrols.assistant.panel.title', 'AI Assistant')}\n </span>\n </div>\n\n <div className=\"flex items-center gap-0.5 flex-shrink-0\">\n {hasMessages && (\n <button\n type=\"button\"\n onClick={() => activeConversationId && clearConversation(activeConversationId)}\n className=\"p-1 hover:bg-bg-sunken rounded transition-colors\"\n aria-label={tr(\n 'vibecontrols.assistant.panel.clearConversation',\n 'Clear conversation'\n )}\n >\n <Eraser className=\"size-3.5 text-text-secondary\" />\n </button>\n )}\n <AssistantExportMenu />\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 hover:bg-bg-sunken rounded transition-colors md:hidden\"\n aria-label={tr('vibecontrols.assistant.panel.close', 'Close assistant')}\n >\n <X className=\"size-3.5 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n {/* Live AI-credits usage meter (hidden when there's no quota to show) */}\n <AiCreditsMeter />\n\n {/* Conversation list overlay */}\n {conversationListOpen && <AssistantConversationList />}\n\n {/* Chat area */}\n <AssistantChatArea />\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;AAuBA,IAAa,KAAoC,EAAE,YAAS,iBAAc;CACxE,IAAM,IAAK,GAAO,EACZ,IAAW,EAAuB,KAAK,EAEvC,EACJ,kBACA,yBACA,yBACA,2BACA,yBACE,GAAmB,EAEjB,IAAqB,EAAc,MAAM,MAAM,EAAE,OAAO,EAAqB;AAInF,GAHwB,GAAoB,oBAAoB,MAGzB,GAAoB,QAAQ,MAAM,KAAW,GAAK;CAEzF,IAAM,KAAe,GAAoB,SAAS,UAAU,KAAK;AAOjE,QALA,EAA8B;EAC5B;EACA;EACD,CAAC,EAGA,kBAAC,OAAD;EACE,KAAK;EACL,WAAU;EACV,MAAK;EACL,cAAY,EAAG,0CAA0C,eAAe;EACxE,eAAY;EACZ,UAAU;YANZ;GASE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAY,EAAG,2CAA2C,2BAA2B;MACrF,eAAY;gBAEZ,kBAAC,GAAD,EAAe,WAAU,gCAAiC,CAAA;MACnD,CAAA,EACT,kBAAC,QAAD;MAAM,WAAU;gBACb,GAAoB,SAAS,EAAG,sCAAsC,eAAe;MACjF,CAAA,CACH;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACG,KACC,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,KAAwB,EAAkB,EAAqB;OAC9E,WAAU;OACV,cAAY,EACV,kDACA,qBACD;iBAED,kBAAC,GAAD,EAAQ,WAAU,gCAAiC,CAAA;OAC5C,CAAA;MAEX,kBAAC,GAAD,EAAuB,CAAA;MACvB,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,WAAU;OACV,cAAY,EAAG,sCAAsC,kBAAkB;iBAEvE,kBAAC,GAAD,EAAG,WAAU,gCAAiC,CAAA;OACvC,CAAA;MACL;OACF;;GAGN,kBAAC,GAAD,EAAkB,CAAA;GAGjB,KAAwB,kBAAC,GAAD,EAA6B,CAAA;GAGtD,kBAAC,GAAD,EAAqB,CAAA;GACjB"}
1
+ {"version":3,"file":"AIAssistantPanel.js","names":[],"sources":["../../../src/components/assistant/AIAssistantPanel.tsx"],"sourcesContent":["/**\n * VibeControls' AI assistant.\n *\n * ★★ Everything below this file is now shared. The panel, the orchestrator, the\n * transport, the voice session, the composer and every component in them live in\n * `fe-libs/shared/assistant` and answer to any product that mounts them. What is\n * left here is the things that are genuinely VibeControls', and nothing else:\n *\n * modes which use cases this product offers.\n * describeCurrentPage what to CALL the page the user is on.\n * gatherPageContext the same page, as structured context for the agent.\n * buildPrompt the confirm-before-mutate guardrail, the language\n * directive and the dictation instructions.\n * reportTokenUsage the charge, against VibeControls' OWN reporter.\n * storageFolders where this product's attachments and artifacts live.\n *\n * Plus `writeAccess`, which is a PROP rather than a seventh seam field: which\n * resource types prove \"this user can write here\" is a product answer, but the\n * product can simply compute it and hand it down. A capability a product can\n * work out for itself does not belong in an interface every product must\n * satisfy.\n *\n * The provider wraps from OUTSIDE the shared panel rather than sitting inside\n * its tree: the panel calls `useAssistantProduct()` and `useAssistantTr()` in its\n * own body, so a provider nested in its JSX would be below the consumer and read\n * as null — which does not throw, it silently renders English with no modes.\n *\n * ★ `AiCreditsMeter` is composed AROUND the shared panel, not inside it. The\n * shared panel has no slot for it and should not grow one: a credits strip is a\n * VibeControls billing decision, not something every product wants above its\n * assistant. It listens to the `notifyAiCreditsUpdated` event the shared chat\n * area already dispatches, so it keeps working untouched.\n */\n\nimport type { FC } from 'react';\nimport {\n AIAssistantPanel as SharedAIAssistantPanel,\n type AIAssistantPanelProps as SharedProps,\n} from '@burdenoff/fe-libs/shared/assistant/ui/AIAssistantPanel';\nimport { AssistantProductProvider } from '@burdenoff/fe-libs/shared/assistant/product';\nimport { getActorId, getWorkspaceId } from '@burdenoff/fe-libs/shared/assistant/context';\nimport { ASSISTANT_MODES } from '@/types/assistant';\nimport { PRODUCT, reportAssistantTokenUsage } from '@/services/assistantApi';\nimport { useWorkspaceWriteAccess } from '@/hooks/useWorkspaceWriteAccess';\nimport { describeCurrentPage, gatherPageContext } from '@/utils/pageContext';\nimport { buildPromptForMode } from '@/utils/assistantPrompt';\nimport { AiCreditsMeter } from './AiCreditsMeter';\n\n/**\n * Where this product's assistant files live.\n *\n * ★★ These strings are LOAD-BEARING once users have saved anything. They are the\n * real folders in the workspace file library holding every attachment and\n * artifact. Changing them does not fail — it starts a second folder beside the\n * first and orphans everything in the original.\n *\n * Note the capitalisation: `/VibeControls/…`, while the product id is\n * `vibecontrols`. That mismatch is exactly why fe-libs cannot derive this and\n * why it is supplied here.\n */\nconst STORAGE_FOLDERS = {\n attachments: '/VibeControls/AI Assistant/Attachments',\n artifacts: '/VibeControls/AI Assistant/Artifacts',\n} as const;\n\nexport type AIAssistantPanelProps = Omit<SharedProps, 'writeAccess'>;\n\nexport const AIAssistantPanel: FC<AIAssistantPanelProps> = (props) => {\n /**\n * Advisory, never a gate see `useWorkspaceWriteAccess`. Gating on it\n * would remove api-calls from the product entirely until RBAC rows are\n * seeded. It warns before the effort instead of after it, and self-corrects\n * with no code change the moment those rows exist.\n */\n const writeAccess = useWorkspaceWriteAccess(getActorId(), getWorkspaceId() || null);\n\n return (\n <AssistantProductProvider\n product={PRODUCT}\n modes={ASSISTANT_MODES}\n describeCurrentPage={describeCurrentPage}\n gatherPageContext={gatherPageContext}\n buildPrompt={buildPromptForMode}\n reportTokenUsage={reportAssistantTokenUsage}\n storageFolders={STORAGE_FOLDERS}\n >\n <div className=\"flex flex-col h-full w-full min-h-0\">\n <AiCreditsMeter />\n <div className=\"flex-1 min-h-0\">\n <SharedAIAssistantPanel {...props} writeAccess={writeAccess} />\n </div>\n </div>\n </AssistantProductProvider>\n );\n};\n"],"mappings":";;;;;;;;;;;AA4DA,IAAM,IAAkB;CACtB,aAAa;CACb,WAAW;CACZ,EAIY,KAA+C,MAAU;CAOpE,IAAM,IAAc,EAAwB,GAAY,EAAE,GAAgB,IAAI,KAAK;AAEnF,QACE,kBAAC,GAAD;EACE,SAAS;EACT,OAAO;EACc;EACF;EACnB,aAAa;EACb,kBAAkB;EAClB,gBAAgB;YAEhB,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAkB,CAAA,EAClB,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KAAwB,GAAI;KAAoB;KAAe,CAAA;IAC3D,CAAA,CACF;;EACmB,CAAA"}
@@ -1,281 +1,11 @@
1
- import { useTr as e } from "../../shared/hooks/useTr.js";
2
- import { isQuotaExhaustedError as t } from "../../utils/quotaUtils.js";
3
- import { useAssistantStore as n } from "../../store/assistantStore.js";
4
- import { checkAssistantAiCredits as r, createAssistantSandbox as i, createAssistantSession as a, extendAssistantSandboxTTL as o, findExistingSandbox as s, getAssistantArtifacts as c, getAssistantMessages as l, getAssistantSession as u, reportAssistantAiTokenUsage as d, sendAssistantPromptAsync as ee, waitForAssistantServiceReady as f, waitForSandboxReady as p } from "../../services/assistantApi.js";
5
- import { AssistantChatInput as m } from "./AssistantChatInput.js";
6
- import { AssistantMessageList as h } from "./AssistantMessageList.js";
7
- import { AssistantModeSelector as g } from "./AssistantModeSelector.js";
8
- import { AssistantWelcome as _ } from "./AssistantWelcome.js";
9
- import { gatherPageContext as te } from "../../utils/pageContext.js";
10
- import { dispatchAssistantQuotaExhausted as v, notifyAiCreditsUpdated as ne } from "../../utils/aiCredits.js";
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
- //#region src/components/assistant/AssistantChatArea.tsx
16
- var oe = {
17
- assistant: 18e4,
18
- general: 18e4,
19
- "api-calls": 18e4,
20
- "vibe-plugins": 6e5
21
- }, C = 18e4;
22
- function w() {
23
- return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
24
- }
25
- function T(e) {
26
- try {
27
- let t = e === "workspaceId" ? "burdenoff-active-context-workspace" : "burdenoff-active-context-organization", n = localStorage.getItem(t);
28
- if (n) return n;
29
- let r = sessionStorage.getItem("bf-active-profile");
30
- if (!r) return "";
31
- let i = localStorage.getItem(`bf-p-${r}-context`);
32
- return i ? JSON.parse(i)[e] ?? "" : "";
33
- } catch {
34
- return "";
35
- }
36
- }
37
- function se() {
38
- return new URLSearchParams(window.location.search).get("workspace") ?? T("workspaceId");
39
- }
40
- function ce() {
41
- return new URLSearchParams(window.location.search).get("org") ?? T("organizationId");
42
- }
43
- function le(e) {
44
- let t = e instanceof Error ? e.message.toLowerCase() : String(e).toLowerCase();
45
- return t.includes("rate limit exceeded") || t.includes("unauthorized") || t.includes("k8s api error 401") ? !1 : [
46
- "sandbox not found",
47
- "sandbox is not running",
48
- "sandbox service not available yet",
49
- "sandbox failed",
50
- "sandbox startup timed out",
51
- "assistant service did not become healthy",
52
- "proxy error: 404",
53
- "proxy error: 502",
54
- "proxy error: 503",
55
- "session mode mismatch"
56
- ].some((e) => t.includes(e));
57
- }
58
- function E(e) {
59
- return e.replace(/(Authorization\s*:\s*Bearer\s+)[^\s\n]+/gi, "$1[REDACTED]").replace(/(X-Workspace-Authorization\s*:\s*Bearer\s+)[^\s\n]+/gi, "$1[REDACTED]").replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+\b/g, "[REDACTED_JWT]").replace(/\bsk-ant-[A-Za-z0-9-]+\b/g, "[REDACTED_API_KEY]");
60
- }
61
- function D(e) {
62
- return e.info?.time?.created ?? 0;
63
- }
64
- function O(e) {
65
- return (e ?? []).filter((e) => e.type === "text" && typeof e.text == "string").map((e) => e.text?.trim() ?? "").filter(Boolean).join("\n");
66
- }
67
- function k(e) {
68
- return (e ?? []).filter((e) => e.type === "tool" && e.tool).map((e) => {
69
- let t = e.state?.status ?? "running", n = e.tool ?? "tool";
70
- return t === "completed" ? `Completed: ${n}` : t === "failed" ? `Failed: ${n}` : `Running: ${n}`;
71
- });
72
- }
73
- function A(e, t) {
74
- let n = e.filter((e) => e.info?.role === "assistant" && D(e) >= t).sort((e, t) => D(e) - D(t));
75
- if (n.length === 0) return {
76
- content: "Thinking...",
77
- done: !1
78
- };
79
- let r = n[n.length - 1], i = O(r.parts), a = k(r.parts);
80
- return {
81
- content: [i, ...a].filter(Boolean).join("\n\n") || "Thinking...",
82
- done: !!r.info?.time?.completed && (!!i || a.length > 0),
83
- tokens: r.info?.tokens,
84
- model: r.info?.providerID && r.info?.modelID ? `${r.info.providerID}/${r.info.modelID}` : void 0
85
- };
86
- }
87
- function ue(e) {
88
- switch (e) {
89
- case "provisioning": return "Starting assistant environment...";
90
- case "connecting": return "Connecting to assistant...";
91
- case "streaming": return "Thinking...";
92
- default: return "";
93
- }
94
- }
95
- var j = () => {
96
- 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) => {
97
- U(e, n, t, null), r ? V(e, t, r) : V(e, t, ue(n));
98
- }, [U, V]), Z = y(async (e, t, r, o, c) => {
99
- let l = n.getState().sandboxesByMode[r]?.sandboxId ?? null;
100
- if (!l) {
101
- X(e, t, "provisioning", "Looking for existing environment...");
102
- let n = await s(D, o, r, c);
103
- if (n) l = n, K(r, l, "provisioning"), X(e, t, "provisioning", "Reusing assistant environment..."), await p(D, l, o, c, 3e5, (n) => X(e, t, "provisioning", n)), await f(D, l, o, c, 9e4, (n) => X(e, t, "connecting", n)), K(r, l, "running");
104
- else {
105
- if (r === "api-calls" && !O()) throw Error("API Calls mode requires an authenticated session. Please sign in again.");
106
- X(e, t, "provisioning", "Starting assistant environment..."), K(r, null, "provisioning"), l = await i(D, r, o, c), K(r, l, "provisioning"), await p(D, l, o, c, 3e5, (n) => X(e, t, "provisioning", n)), await f(D, l, o, c, 9e4, (n) => X(e, t, "connecting", n)), K(r, l, "running");
107
- }
108
- }
109
- let d = n.getState().conversations.find((t) => t.id === e)?.backendSessionId ?? null;
110
- if (d) try {
111
- let e = await u(D, l, o, d, c);
112
- if (e.mode !== r) throw Error(`Session mode mismatch: expected ${r}, received ${e.mode}`);
113
- } catch {
114
- G(e), d = null;
115
- }
116
- return d || (X(e, t, "connecting", "Preparing session..."), d = (await a(D, l, o, r, c)).sessionId, W(e, d)), {
117
- sandboxId: l,
118
- sessionId: d
119
- };
120
- }, [
121
- D,
122
- G,
123
- O,
124
- W,
125
- K,
126
- X
127
- ]), Q = y(async (e) => {
128
- if (N) {
129
- let e = F[N]?.status;
130
- if (e === "provisioning" || e === "connecting" || e === "streaming") return;
131
- }
132
- let i = n.getState().mode, a = N, s = a ? n.getState().conversations.find((e) => e.id === a) : void 0;
133
- 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;
134
- let u = se(), f = {
135
- accessToken: O(),
136
- workspaceToken: k(),
137
- userId: j,
138
- organizationId: ce()
139
- }, p = {
140
- id: w(),
141
- role: "user",
142
- content: e,
143
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
144
- };
145
- B(a, p);
146
- let m = w();
147
- B(a, {
148
- id: m,
149
- role: "assistant",
150
- content: "",
151
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
152
- });
153
- let h = (e, t, n, r) => {
154
- H(a, m, {
155
- content: E(e),
156
- metadata: t?.length || n || r ? {
157
- artifacts: t,
158
- mode: i,
159
- sessionId: n,
160
- sandboxId: r
161
- } : void 0
162
- });
163
- }, g = async (n = 1) => {
164
- try {
165
- await r(D, u, f);
166
- } catch (e) {
167
- if (t(e)) {
168
- v("ai-credits"), h("You've run out of AI credits for this billing cycle. Upgrade your plan to keep chatting."), U(a, "error", m, null);
169
- return;
170
- }
171
- console.warn("[assistant] AI-credit pre-flight check failed with a non-quota error; proceeding — gateway @usage(checkQuota) still enforces the real budget downstream", e);
172
- }
173
- try {
174
- let { sandboxId: t, sessionId: n } = await Z(a, m, i, u, f);
175
- X(a, m, "streaming", "Thinking...");
176
- let r = Date.now();
177
- await ee(D, t, u, n, e, i, te(), f);
178
- let s = A([], r), p = Date.now() + (oe[i] ?? C), g = Date.now();
179
- 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));
180
- if (!s.done && i !== "vibe-plugins") throw Error("Assistant response timed out while streaming progress");
181
- let _ = i === "vibe-plugins" ? await c(D, t, u, n, f).catch(() => []) : void 0;
182
- if (s.done || (s = {
183
- ...s,
184
- 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._`
185
- }), h(s.content, _, n, t), s.tokens) {
186
- H(a, m, { tokens: s.tokens });
187
- try {
188
- await d(D, u, n, m, s.model, s.tokens, f), ne();
189
- } catch {}
190
- }
191
- U(a, "complete", m, null);
192
- } catch (e) {
193
- if (n === 1 && le(e)) return G(a), q(i), g(2);
194
- throw e;
195
- }
196
- };
197
- try {
198
- if (!u) throw Error("Assistant backend is unavailable without an active workspace context.");
199
- await g();
200
- } catch (e) {
201
- if (t(e)) {
202
- v("sandbox"), h("You've reached your sandbox limit on the current plan. Upgrade to start the AI assistant."), U(a, "error", m, null);
203
- return;
204
- }
205
- let n = e instanceof Error ? e.message : "Something went wrong";
206
- U(a, "error", m, n), V(a, m, `Sorry, an error occurred: ${n}. Please try again.`);
207
- }
208
- }, [
209
- N,
210
- B,
211
- D,
212
- G,
213
- q,
214
- M,
215
- z,
216
- Z,
217
- O,
218
- k,
219
- H,
220
- F,
221
- W,
222
- L,
223
- U,
224
- X,
225
- V,
226
- j
227
- ]), me = y((e) => {
228
- Q(e);
229
- }, [Q]), $ = re(Q);
230
- b(() => {
231
- $.current = Q;
232
- }, [Q]), b(() => {
233
- let e = n.getState().consumePendingPrompt();
234
- return e && $.current(e), n.subscribe((e, t) => {
235
- if (e.pendingPrompt && e.pendingPrompt !== t.pendingPrompt) {
236
- let e = n.getState().consumePendingPrompt();
237
- e && $.current(e);
238
- }
239
- });
240
- }, []);
241
- let he = y((e) => {
242
- let t = N ? n.getState().conversations.find((e) => e.id === N) : void 0;
243
- if (t && (t.messages.length > 0 || t.backendSessionId)) {
244
- if (t.mode !== e) {
245
- let t = n.getState().conversations.find((t) => t.mode === e && t.messages.length === 0 && !t.backendSessionId && !n.getState().processingByConversationId[t.id]);
246
- t ? R(t.id) : z(void 0, e);
247
- }
248
- } else N && L(N, e);
249
- I(e);
250
- }, [
251
- N,
252
- z,
253
- R,
254
- L,
255
- I
256
- ]);
257
- return /* @__PURE__ */ S("div", {
258
- className: "flex flex-col flex-1 overflow-hidden pt-3",
259
- children: [J.length === 0 ? /* @__PURE__ */ x(_, {
260
- mode: P,
261
- onSuggestionClick: me
262
- }) : /* @__PURE__ */ x(h, {
263
- messages: J,
264
- streamingMessageId: fe
265
- }), /* @__PURE__ */ S("div", { children: [/* @__PURE__ */ x("div", {
266
- className: "px-3 pb-1",
267
- children: /* @__PURE__ */ x(g, {
268
- mode: P,
269
- onModeChange: he
270
- })
271
- }), /* @__PURE__ */ x(m, {
272
- onSend: (e) => void Q(e),
273
- disabled: pe,
274
- placeholder: T("vibecontrols.assistant.chatArea.placeholder", `Ask about ${P.replace(/-/g, " ")}...`)
275
- })] })]
276
- });
277
- };
278
- //#endregion
279
- export { j as AssistantChatArea };
280
-
281
- //# sourceMappingURL=AssistantChatArea.js.map
1
+ import "../../shared/hooks/useTr.js";
2
+ import "../../services/assistantApi.js";
3
+ import "./AssistantChatInput.js";
4
+ import "./AssistantMessageList.js";
5
+ import "./AssistantModeSelector.js";
6
+ import "./AssistantWelcome.js";
7
+ import "../../store/assistantStore.js";
8
+ import "react";
9
+ import "@burdenoff/fe-libs/shared/graphql";
10
+ import "@burdenoff/fe-libs/shared/providers/shell";
11
+ import "react/jsx-runtime";
@@ -1,44 +1,4 @@
1
- import { useTr as e } from "../../shared/hooks/useTr.js";
2
- import { useCallback as t, useRef as n } from "react";
3
- import { Send as r } from "lucide-react";
4
- import { jsx as i, jsxs as a } from "react/jsx-runtime";
5
- //#region src/components/assistant/AssistantChatInput.tsx
6
- var o = ({ onSend: o, disabled: s = !1, placeholder: c = "Ask anything..." }) => {
7
- let l = e(), u = n(null), d = t(() => {
8
- let e = u.current;
9
- if (!e) return;
10
- let t = e.value.trim();
11
- !t || s || (o(t), e.value = "", e.style.height = "auto");
12
- }, [o, s]);
13
- return /* @__PURE__ */ a("div", {
14
- className: "flex items-end gap-2 px-3 py-2.5 border-t border-border-default bg-bg-surface",
15
- children: [/* @__PURE__ */ i("textarea", {
16
- ref: u,
17
- onKeyDown: t((e) => {
18
- e.key === "Enter" && !e.shiftKey && (e.preventDefault(), d());
19
- }, [d]),
20
- onInput: t(() => {
21
- let e = u.current;
22
- e && (e.style.height = "auto", e.style.height = `${Math.min(e.scrollHeight, 120)}px`);
23
- }, []),
24
- disabled: s,
25
- placeholder: c,
26
- rows: 1,
27
- className: "flex-1 resize-none bg-bg-sunken border border-border-default rounded-lg px-3 py-2 text-xs text-text-primary placeholder:text-text-muted focus:outline-none focus:border-action-primary-bg disabled:opacity-50 leading-relaxed",
28
- "aria-label": l("vibecontrols.assistant.chatInput.ariaLabel", "Message input"),
29
- "data-testid": "assistant-input"
30
- }), /* @__PURE__ */ i("button", {
31
- type: "button",
32
- onClick: d,
33
- disabled: s,
34
- className: "flex-shrink-0 p-2 bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50",
35
- "aria-label": l("vibecontrols.assistant.chatInput.send", "Send message"),
36
- "data-testid": "assistant-send",
37
- children: /* @__PURE__ */ i(r, { className: "size-3.5" })
38
- })]
39
- });
40
- };
41
- //#endregion
42
- export { o as AssistantChatInput };
43
-
44
- //# sourceMappingURL=AssistantChatInput.js.map
1
+ import "../../shared/hooks/useTr.js";
2
+ import "react";
3
+ import "lucide-react";
4
+ import "react/jsx-runtime";
@@ -1,155 +1,6 @@
1
- import { useTr as e } from "../../shared/hooks/useTr.js";
2
- import { useAssistantStore as t } from "../../store/assistantStore.js";
3
- import { ASSISTANT_MODES as n } from "../../types/assistant.js";
4
- import { useEffect as r, useState as i } from "react";
5
- import { Check as a, MessageSquare as o, Pencil as s, Plus as c, Trash2 as l, X as u } from "lucide-react";
6
- import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
7
- //#region src/components/assistant/AssistantConversationList.tsx
8
- var m = () => {
9
- let m = e(), { conversations: h, activeConversationId: g, processingByConversationId: _, setActiveConversation: v, setMode: y, createConversation: b, deleteConversation: x, renameConversation: S, setConversationListOpen: C } = t(), [w, T] = i(null), [E, D] = i("");
10
- r(() => {
11
- g && C(!1);
12
- }, [g, C]);
13
- let O = () => {
14
- b(), C(!1);
15
- }, k = (e) => {
16
- let t = h.find((t) => t.id === e);
17
- v(e), t && y(t.mode), C(!1);
18
- }, A = (e, t) => {
19
- T(e), D(t);
20
- }, j = () => {
21
- w && E.trim() && S(w, E.trim()), T(null);
22
- }, M = (e, t) => {
23
- e.stopPropagation(), x(t);
24
- };
25
- return /* @__PURE__ */ p("div", {
26
- className: "absolute inset-0 z-10 flex flex-col bg-bg-surface",
27
- "data-testid": "conversation-list",
28
- children: [/* @__PURE__ */ p("div", {
29
- className: "flex items-center justify-between px-3 py-2 border-b border-border-default",
30
- children: [/* @__PURE__ */ f("span", {
31
- className: "text-xs font-semibold text-text-primary",
32
- children: m("vibecontrols.assistant.conversations.title", "Conversations")
33
- }), /* @__PURE__ */ p("div", {
34
- className: "flex items-center gap-1",
35
- children: [/* @__PURE__ */ f("button", {
36
- type: "button",
37
- onClick: O,
38
- className: "p-1 hover:bg-bg-elevated rounded transition-colors",
39
- "aria-label": m("vibecontrols.assistant.conversations.new", "New conversation"),
40
- "data-testid": "new-conversation",
41
- children: /* @__PURE__ */ f(c, { className: "size-3.5 text-text-secondary" })
42
- }), /* @__PURE__ */ f("button", {
43
- type: "button",
44
- onClick: () => C(!1),
45
- className: "p-1 hover:bg-bg-elevated rounded transition-colors",
46
- "aria-label": "Close conversation list",
47
- children: /* @__PURE__ */ f(u, { className: "size-3.5 text-text-secondary" })
48
- })]
49
- })]
50
- }), /* @__PURE__ */ f("div", {
51
- className: "flex-1 overflow-y-auto",
52
- children: h.length === 0 ? /* @__PURE__ */ p("div", {
53
- className: "flex flex-col items-center justify-center h-full text-center px-4",
54
- children: [
55
- /* @__PURE__ */ f(o, { className: "size-6 text-text-muted mb-2" }),
56
- /* @__PURE__ */ f("p", {
57
- className: "text-xs text-text-muted",
58
- children: m("vibecontrols.assistant.conversations.empty", "No conversations yet")
59
- }),
60
- /* @__PURE__ */ f("button", {
61
- type: "button",
62
- onClick: O,
63
- className: "mt-2 text-xs text-action-primary-bg hover:underline",
64
- children: m("vibecontrols.assistant.conversations.startNew", "Start a new conversation")
65
- })
66
- ]
67
- }) : /* @__PURE__ */ f("div", {
68
- className: "py-1",
69
- children: h.map((e) => {
70
- let t = e.id === g, r = e.id === w, i = n.find((t) => t.id === e.mode), o = e.messages[e.messages.length - 1], c = _[e.id];
71
- return /* @__PURE__ */ f("div", {
72
- role: "button",
73
- tabIndex: 0,
74
- onKeyDown: (t) => {
75
- (t.key === "Enter" || t.key === " ") && (t.preventDefault(), !r && k(e.id));
76
- },
77
- onClick: () => !r && k(e.id),
78
- className: `group px-3 py-2 cursor-pointer hover:bg-bg-elevated transition-colors ${t ? "bg-bg-elevated border-l-2 border-action-primary-bg" : ""}`,
79
- children: r ? /* @__PURE__ */ p("div", {
80
- className: "flex items-center gap-1",
81
- children: [/* @__PURE__ */ f("input", {
82
- "aria-label": "control",
83
- value: E,
84
- onChange: (e) => D(e.target.value),
85
- onKeyDown: (e) => {
86
- e.key === "Enter" && j(), e.key === "Escape" && T(null);
87
- },
88
- className: "flex-1 text-xs bg-bg-sunken border border-border-default rounded px-2 py-1 text-text-primary focus:outline-none focus:border-action-primary-bg",
89
- onClick: (e) => e.stopPropagation()
90
- }), /* @__PURE__ */ f("button", {
91
- type: "button",
92
- onClick: (e) => {
93
- e.stopPropagation(), j();
94
- },
95
- className: "p-1 hover:bg-bg-sunken rounded",
96
- children: /* @__PURE__ */ f(a, { className: "size-3 text-status-success-text" })
97
- })]
98
- }) : /* @__PURE__ */ p(d, { children: [
99
- /* @__PURE__ */ p("div", {
100
- className: "flex items-center justify-between",
101
- children: [/* @__PURE__ */ f("span", {
102
- className: "text-xs font-medium text-text-primary truncate flex-1",
103
- children: e.title
104
- }), /* @__PURE__ */ p("div", {
105
- className: "hidden group-hover:flex items-center gap-0.5 ml-1",
106
- children: [/* @__PURE__ */ f("button", {
107
- type: "button",
108
- onClick: (t) => {
109
- t.stopPropagation(), A(e.id, e.title);
110
- },
111
- className: "p-0.5 hover:bg-bg-sunken rounded",
112
- "aria-label": "Rename conversation",
113
- children: /* @__PURE__ */ f(s, { className: "size-3 text-text-muted" })
114
- }), /* @__PURE__ */ f("button", {
115
- type: "button",
116
- onClick: (t) => M(t, e.id),
117
- className: "p-0.5 hover:bg-bg-sunken rounded",
118
- "aria-label": "Delete conversation",
119
- "data-testid": "delete-conversation",
120
- children: /* @__PURE__ */ f(l, { className: "size-3 text-status-error-text" })
121
- })]
122
- })]
123
- }),
124
- /* @__PURE__ */ p("div", {
125
- className: "flex items-center gap-1.5 mt-0.5",
126
- children: [
127
- /* @__PURE__ */ f("span", {
128
- className: "text-[10px] text-text-muted px-1 py-0.5 bg-bg-sunken rounded",
129
- children: i?.label ?? e.mode
130
- }),
131
- /* @__PURE__ */ p("span", {
132
- className: "text-[10px] text-text-muted",
133
- children: [e.messages.length, " msgs"]
134
- }),
135
- c && c.status !== "idle" && /* @__PURE__ */ f("span", {
136
- className: "text-[10px] text-action-primary-bg px-1 py-0.5 bg-bg-sunken rounded",
137
- children: c.status
138
- })
139
- ]
140
- }),
141
- o && /* @__PURE__ */ f("p", {
142
- className: "text-[10px] text-text-muted truncate mt-0.5",
143
- children: o.content.slice(0, 60)
144
- })
145
- ] })
146
- }, e.id);
147
- })
148
- })
149
- })]
150
- });
151
- };
152
- //#endregion
153
- export { m as AssistantConversationList };
154
-
155
- //# sourceMappingURL=AssistantConversationList.js.map
1
+ import "../../shared/hooks/useTr.js";
2
+ import "../../types/assistant.js";
3
+ import "../../store/assistantStore.js";
4
+ import "react";
5
+ import "lucide-react";
6
+ import "react/jsx-runtime";