@burdenoff/microfe-vibecontrols 2026.905.4 → 2026.915.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.
- package/dist/components/agent-manager/AgentManagerMessageItem.js +1 -1
- package/dist/components/agent-manager/AgentManagerMessageItem.js.map +1 -1
- package/dist/components/assistant/AssistantMessageItem.js +1 -1
- package/dist/components/assistant/ProductMarkdown.js +30 -0
- package/dist/components/assistant/ProductMarkdown.js.map +1 -0
- package/dist/components/assistant/index.js +1 -1
- package/dist/components/shared/GetHelpButton.js.map +1 -1
- package/dist/hooks/useOpenAssistantHelp.js +5 -5
- package/dist/hooks/useOpenAssistantHelp.js.map +1 -1
- package/dist/hooks/useWorkspaceWriteAccess.js +3 -3
- package/dist/hooks/useWorkspaceWriteAccess.js.map +1 -1
- package/dist/services/assistantApi.js.map +1 -1
- package/dist/store/assistantStore.js.map +1 -1
- package/package.json +4 -3
- package/dist/components/assistant/AssistantMarkdownRenderer.js +0 -205
- package/dist/components/assistant/AssistantMarkdownRenderer.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ProductMarkdown as t } from "../assistant/ProductMarkdown.js";
|
|
3
3
|
import { relativeTime as n } from "../../utils/relativeTime.js";
|
|
4
4
|
import { useState as r } from "react";
|
|
5
5
|
import { Bookmark as i, BookmarkCheck as a, Bot as o, Check as s, Copy as c, User as l } from "lucide-react";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentManagerMessageItem.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerMessageItem.tsx"],"sourcesContent":["/**\n * Single message bubble for the Agent Manager chat.\n *\n * User messages are right-aligned; assistant messages are left-aligned.\n * Uses
|
|
1
|
+
{"version":3,"file":"AgentManagerMessageItem.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerMessageItem.tsx"],"sourcesContent":["/**\n * Single message bubble for the Agent Manager chat.\n *\n * User messages are right-aligned; assistant messages are left-aligned.\n * Uses the SHARED assistant markdown renderer (via ProductMarkdown) for\n * rich content in assistant messages, so links here follow the same origin\n * policy as the assistant panel's.\n * Supports optional search query highlighting.\n */\n\nimport { useState, type FC, type ReactNode } from 'react';\nimport { Bot, Bookmark, BookmarkCheck, Copy, Check, User } from 'lucide-react';\nimport { ProductMarkdown } from '@/components/assistant/ProductMarkdown';\nimport { relativeTime } from '@/utils/relativeTime';\nimport type { AgentManagerMessage } from '@/types/agentManager';\nimport { useTr } from '../../shared/hooks/useTr';\n\nconst ANSI_ESCAPE_RE = new RegExp(\n `${String.fromCharCode(27)}(?:[@-Z\\\\-_]|\\\\[[0-?]*[ -/]*[@-~])`,\n 'g'\n);\n\nfunction stripAnsi(value: string): string {\n return value.replace(ANSI_ESCAPE_RE, '');\n}\n\n/** Highlight occurrences of `query` within `text`. */\nfunction highlightText(text: string, query: string): ReactNode {\n if (!query) return text;\n\n const lowerText = text.toLowerCase();\n const lowerQuery = query.toLowerCase();\n const parts: ReactNode[] = [];\n let lastIndex = 0;\n let matchIndex = lowerText.indexOf(lowerQuery, lastIndex);\n\n while (matchIndex !== -1) {\n if (matchIndex > lastIndex) {\n parts.push(text.slice(lastIndex, matchIndex));\n }\n parts.push(\n <mark\n key={matchIndex}\n className=\"bg-status-warning-bg-subtle text-status-warning-text rounded-sm px-0.5\"\n >\n {text.slice(matchIndex, matchIndex + query.length)}\n </mark>\n );\n lastIndex = matchIndex + query.length;\n matchIndex = lowerText.indexOf(lowerQuery, lastIndex);\n }\n\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n\n return parts.length > 0 ? <>{parts}</> : text;\n}\n\ninterface AgentManagerMessageItemProps {\n message: AgentManagerMessage;\n searchQuery?: string;\n isBookmarked?: boolean;\n onToggleBookmark?: (messageId: string) => void;\n flashMessageId?: string | null;\n}\n\nexport const AgentManagerMessageItem: FC<AgentManagerMessageItemProps> = ({\n message,\n searchQuery = '',\n isBookmarked = false,\n onToggleBookmark,\n flashMessageId,\n}) => {\n const [copied, setCopied] = useState(false);\n const [hovered, setHovered] = useState(false);\n const isUser = message.role === 'user';\n const tr = useTr();\n const displayContent = stripAnsi(message.content);\n const isFlashing = flashMessageId === message.id;\n\n const handleCopy = () => {\n navigator.clipboard.writeText(displayContent);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n const handleToggleBookmark = () => {\n if (onToggleBookmark) onToggleBookmark(message.id);\n };\n\n const totalTokens = message.tokens ? message.tokens.input + message.tokens.output : null;\n\n return (\n <div\n data-message-id={message.id}\n className={`flex gap-2.5 scroll-mt-16 transition-shadow duration-300 ${\n isUser ? 'flex-row-reverse' : 'flex-row'\n } ${isFlashing ? 'ring-2 ring-status-warning-bg/60 rounded-lg' : ''}`}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n >\n {/* Avatar */}\n <div\n className={`flex-shrink-0 size-6 rounded-full flex items-center justify-center mt-0.5 ${\n isUser ? 'bg-action-primary-bg' : 'bg-bg-elevated border border-border-default'\n }`}\n >\n {isUser ? (\n <User className=\"size-3.5 text-action-primary-text\" />\n ) : (\n <Bot className=\"size-3.5 text-text-secondary\" />\n )}\n </div>\n\n {/* Message bubble */}\n <div\n className={`relative max-w-[85%] rounded-lg px-3 py-2 ${\n isUser\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-elevated border border-border-subtle'\n }`}\n >\n {/* Content */}\n {isUser ? (\n <p className=\"text-xs whitespace-pre-wrap leading-relaxed break-words\">\n {searchQuery ? highlightText(displayContent, searchQuery) : displayContent}\n </p>\n ) : searchQuery ? (\n <p className=\"text-xs whitespace-pre-wrap leading-relaxed break-words text-text-primary\">\n {highlightText(displayContent, searchQuery)}\n </p>\n ) : (\n <ProductMarkdown content={displayContent} />\n )}\n\n {/* Footer: timestamp, tokens, bookmark, copy */}\n <div\n className={`flex items-center gap-2 text-[10px] mt-1 ${\n isUser ? 'text-action-primary-text/60' : 'text-text-muted'\n }`}\n >\n <span>{relativeTime(message.timestamp)}</span>\n\n {totalTokens !== null && !isUser && (\n <span>\n {totalTokens} {tr('vibecontrols.agentManager.messageItem.tokens', 'tokens')}\n </span>\n )}\n\n {message.sdk && (\n <span className=\"px-1 py-0.5 rounded bg-bg-elevated text-text-muted uppercase font-mono text-[9px]\">\n {message.sdk}\n </span>\n )}\n\n {message.model && !isUser && <span>{message.model}</span>}\n\n {/* Bookmark + Copy buttons on hover (or when bookmarked) */}\n {(hovered || isBookmarked) && onToggleBookmark && (\n <button\n type=\"button\"\n onClick={handleToggleBookmark}\n className={`ml-auto p-0.5 rounded transition-colors ${\n isUser ? 'hover:bg-action-primary-bg-hover' : 'hover:bg-bg-sunken'\n }`}\n aria-label={\n isBookmarked\n ? tr('vibecontrols.agentManager.messageItem.removeBookmark', 'Remove bookmark')\n : tr('vibecontrols.agentManager.messageItem.bookmarkMessage', 'Bookmark message')\n }\n title={\n isBookmarked\n ? tr('vibecontrols.agentManager.messageItem.removeBookmark', 'Remove bookmark')\n : tr('vibecontrols.agentManager.messageItem.bookmarkMessage', 'Bookmark message')\n }\n data-testid=\"agent-manager-bookmark-toggle\"\n >\n {isBookmarked ? (\n <BookmarkCheck className=\"size-3 text-status-warning-text\" />\n ) : (\n <Bookmark className=\"size-3\" />\n )}\n </button>\n )}\n\n {hovered && (\n <button\n type=\"button\"\n onClick={handleCopy}\n className={`${onToggleBookmark ? '' : 'ml-auto'} p-0.5 rounded transition-colors ${\n isUser ? 'hover:bg-action-primary-bg-hover' : 'hover:bg-bg-sunken'\n }`}\n aria-label={tr('vibecontrols.agentManager.messageItem.copyMessage', 'Copy message')}\n title={tr('vibecontrols.agentManager.messageItem.copyMessage', 'Copy message')}\n >\n {copied ? (\n <Check className=\"size-3 text-status-success-text\" />\n ) : (\n <Copy className=\"size-3\" />\n )}\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useCallback as e } from "react";
|
|
2
|
+
import { useNavigate as t } from "react-router";
|
|
3
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
4
|
+
import { AssistantI18nProvider as r } from "@burdenoff/fe-libs/shared/assistant/i18n";
|
|
5
|
+
import { AssistantMarkdownRenderer as i, AssistantNavigateProvider as a } from "@burdenoff/fe-libs/shared/assistant/ui/AssistantMarkdownRenderer";
|
|
6
|
+
import { isShellLessRoute as o } from "@burdenoff/fe-libs/shared/config/shellRoutes";
|
|
7
|
+
//#region src/components/assistant/ProductMarkdown.tsx
|
|
8
|
+
var s = ({ content: s }) => {
|
|
9
|
+
let c = t();
|
|
10
|
+
return /* @__PURE__ */ n("div", {
|
|
11
|
+
className: "text-xs",
|
|
12
|
+
children: /* @__PURE__ */ n(r, {
|
|
13
|
+
namespace: "vibecontrols",
|
|
14
|
+
children: /* @__PURE__ */ n(a, {
|
|
15
|
+
value: e((e) => {
|
|
16
|
+
if (o(e.split(/[?#]/)[0] || "/")) {
|
|
17
|
+
window.open(e, "_blank", "noopener,noreferrer");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
c(e);
|
|
21
|
+
}, [c]),
|
|
22
|
+
children: /* @__PURE__ */ n(i, { content: s })
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
export { s as ProductMarkdown };
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=ProductMarkdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductMarkdown.js","names":[],"sources":["../../../src/components/assistant/ProductMarkdown.tsx"],"sourcesContent":["/**\n * VibeControls markdown — rendered by the SHARED assistant renderer.\n *\n * ★★ This REPLACES a 335-line hand-rolled regex renderer (deleted in this change) that forced\n * `target=\"_blank\" rel=\"noopener noreferrer\"` onto EVERY link with no origin\n * check and no scheme allow-list. Agent Manager content is model output relayed\n * from a user-registered remote agent, so it is doubly untrusted — and an in-app\n * path in it opened a second copy of the whole app in a new tab, the exact\n * behaviour fe-libs#202 removed from the shared assistant. This page never\n * inherited that fix because it never used the shared renderer.\n *\n * The shared renderer brings, for free:\n * - origin-based internal/external classification, so an in-app path routes\n * in-app and only genuinely external links get `_blank` + `noopener`;\n * - a real CommonMark+GFM parser with rehype-sanitize, in place of a regex\n * that emitted `<li>` with no `<ul>` parent, one `<p>` per SOURCE line, and\n * dropped empty table cells (shifting every later column out of alignment);\n * - no lookbehind regex, which threw at PARSE time on Safari < 16.4 and sat in\n * the app's ENTRY chunk.\n *\n * ★ The i18n keys are unchanged. fe-libs calls `tr('markdown.copyCode', …)`,\n * which under `namespace=\"vibecontrols\"` resolves to\n * `vibecontrols.assistant.markdown.copyCode` — the exact key the fork used, so\n * every already-seeded translation still lands.\n *\n * ★ Used by BOTH the Agent Manager bubble and the (currently unmounted)\n * assistant bubble, so the two surfaces cannot diverge again.\n *\n * ★ `text-xs` is supplied here because the shared renderer deliberately inherits\n * its font size from the message bubble that mounts it.\n */\nimport { useCallback, type FC } from 'react';\nimport { useNavigate } from 'react-router';\n\nimport { AssistantI18nProvider } from '@burdenoff/fe-libs/shared/assistant/i18n';\nimport {\n AssistantMarkdownRenderer,\n AssistantNavigateProvider,\n} from '@burdenoff/fe-libs/shared/assistant/ui/AssistantMarkdownRenderer';\nimport { isShellLessRoute } from '@burdenoff/fe-libs/shared/config/shellRoutes';\n\nexport interface ProductMarkdownProps {\n content: string;\n}\n\nexport const ProductMarkdown: FC<ProductMarkdownProps> = ({ content }) => {\n const navigate = useNavigate();\n\n /**\n * ★ fe-libs hands over a router-ready PATH, already resolved from the href and\n * proven same-origin — an off-origin link never reaches here, it opens in a new\n * tab. So this is a plain in-app route change.\n *\n * ★★ Except for a SHELL-LESS destination. `ConditionalShell` renders those bare\n * (the app's `App.tsx`), which unmounts `AppShell` — and this page with it, chat\n * state included. Those are unauthenticated pages that bring their own chrome,\n * so a new tab is what a link to one wants anyway. Same fe-libs predicate the\n * shell itself uses, so the two cannot disagree about what \"shell-less\" means.\n */\n const navigateFromMarkdown = useCallback(\n (path: string) => {\n // fe-libs sends `pathname + search + hash`; the predicate matches a pathname.\n const pathname = path.split(/[?#]/)[0] || '/';\n if (isShellLessRoute(pathname)) {\n window.open(path, '_blank', 'noopener,noreferrer');\n return;\n }\n navigate(path);\n },\n [navigate]\n );\n\n return (\n <div className=\"text-xs\">\n <AssistantI18nProvider namespace=\"vibecontrols\">\n <AssistantNavigateProvider value={navigateFromMarkdown}>\n <AssistantMarkdownRenderer content={content} />\n </AssistantNavigateProvider>\n </AssistantI18nProvider>\n </div>\n );\n};\n"],"mappings":";;;;;;;AA6CA,IAAa,KAA6C,EAAE,iBAAc;CACxE,IAAM,IAAW,GAAa;AA0B9B,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GAAuB,WAAU;aAC/B,kBAAC,GAAD;IAA2B,OAhBJ,GAC1B,MAAiB;AAGhB,SAAI,EADa,EAAK,MAAM,OAAO,CAAC,MAAM,IACZ,EAAE;AAC9B,aAAO,KAAK,GAAM,UAAU,sBAAsB;AAClD;;AAEF,OAAS,EAAK;OAEhB,CAAC,EAAS,CACX;cAMO,kBAAC,GAAD,EAAoC,YAAW,CAAA;IACrB,CAAA;GACN,CAAA;EACpB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GetHelpButton.js","names":[],"sources":["../../../src/components/shared/GetHelpButton.tsx"],"sourcesContent":["import { HelpCircle } from 'lucide-react';\nimport { useOpenAssistantHelp } from '@/hooks/useOpenAssistantHelp';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface GetHelpButtonProps {\n /** The prompt to auto-send to the assistant when clicked. */\n prompt: string;\n /** Optional label override. Defaults to \"Get Help\". */\n label?: string;\n /** Visual variant. `ghost` (default) is a subtle outlined button; `link` is text-only. */\n variant?: 'ghost' | 'link';\n /** Additional CSS class names. */\n className?: string;\n}\n\n/**\n * Shared \"Get Help\" button. Placed next to \"Add X\" / \"Create X\" buttons in\n * empty states across microfe-vibecontrols so users can ask the assistant\n * to explain the concept without leaving the current page.\n *\n * Clicking the button:\n * 1.
|
|
1
|
+
{"version":3,"file":"GetHelpButton.js","names":[],"sources":["../../../src/components/shared/GetHelpButton.tsx"],"sourcesContent":["import { HelpCircle } from 'lucide-react';\nimport { useOpenAssistantHelp } from '@/hooks/useOpenAssistantHelp';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface GetHelpButtonProps {\n /** The prompt to auto-send to the assistant when clicked. */\n prompt: string;\n /** Optional label override. Defaults to \"Get Help\". */\n label?: string;\n /** Visual variant. `ghost` (default) is a subtle outlined button; `link` is text-only. */\n variant?: 'ghost' | 'link';\n /** Additional CSS class names. */\n className?: string;\n}\n\n/**\n * Shared \"Get Help\" button. Placed next to \"Add X\" / \"Create X\" buttons in\n * empty states across microfe-vibecontrols so users can ask the assistant\n * to explain the concept without leaving the current page.\n *\n * Clicking the button:\n * 1. Dispatches `vc:toggle-ai-assistant` with `detail.open=true` so the\n * shell panel opens (not toggles)\n * 2. Queues the provided prompt via the SHARED assistant store's\n * `submitPrompt`, on the next macrotask\n *\n * The mode is left as the user set it. The shared chat area picks up the\n * queued prompt and auto-sends it. See `useOpenAssistantHelp`.\n */\nexport function GetHelpButton({\n prompt,\n label,\n variant = 'ghost',\n className = '',\n}: GetHelpButtonProps) {\n const tr = useTr();\n const resolvedLabel = label ?? tr('vibecontrols.shared.getHelpButton.label', 'Get Help');\n const openHelp = useOpenAssistantHelp();\n\n const baseClass =\n variant === 'ghost'\n ? 'inline-flex items-center gap-1.5 px-3 py-1.5 text-sm border border-border-default text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-md transition-colors'\n : 'inline-flex items-center gap-1.5 text-sm text-accent-purple hover:underline';\n\n return (\n <button\n type=\"button\"\n onClick={() => openHelp(prompt)}\n className={`${baseClass} ${className}`.trim()}\n title={tr('vibecontrols.shared.getHelpButton.tooltip', 'Ask the assistant to explain')}\n >\n <HelpCircle className=\"size-4\" />\n {resolvedLabel}\n </button>\n );\n}\n"],"mappings":";;;;;AA6BA,SAAgB,EAAc,EAC5B,WACA,UACA,aAAU,SACV,eAAY,MACS;CACrB,IAAM,IAAK,GAAO,EACZ,IAAgB,KAAS,EAAG,2CAA2C,WAAW,EAClF,IAAW,GAAsB;AAOvC,QACE,kBAAC,UAAD;EACE,MAAK;EACL,eAAe,EAAS,EAAO;EAC/B,WAAW,GARb,MAAY,UACR,kLACA,8EAMsB,GAAG,IAAY,MAAM;EAC7C,OAAO,EAAG,6CAA6C,+BAA+B;YAJxF,CAME,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA,EAChC,EACM"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useCallback as e } from "react";
|
|
2
|
+
import { useAssistantStore as t } from "@burdenoff/fe-libs/shared/assistant/store";
|
|
3
3
|
//#region src/hooks/useOpenAssistantHelp.ts
|
|
4
4
|
function n() {
|
|
5
|
-
return
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
return e((e) => {
|
|
6
|
+
window.dispatchEvent(new CustomEvent("vc:toggle-ai-assistant", { detail: { open: !0 } })), setTimeout(() => {
|
|
7
|
+
t.getState().submitPrompt(e);
|
|
8
8
|
}, 0);
|
|
9
9
|
}, []);
|
|
10
10
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOpenAssistantHelp.js","names":[],"sources":["../../src/hooks/useOpenAssistantHelp.ts"],"sourcesContent":["/**\n * useOpenAssistantHelp\n *\n * Returns a callback that opens the AI assistant panel (via the shell's\n * global `vc:toggle-ai-assistant` event)
|
|
1
|
+
{"version":3,"file":"useOpenAssistantHelp.js","names":[],"sources":["../../src/hooks/useOpenAssistantHelp.ts"],"sourcesContent":["/**\n * useOpenAssistantHelp\n *\n * Returns a callback that opens the AI assistant panel (via the shell's\n * global `vc:toggle-ai-assistant` event) and queues a prompt that the chat\n * area will auto-send on next render. It deliberately leaves the MODE alone —\n * see the note in the callback.\n *\n * Used by the shared `GetHelpButton` that appears next to \"Add X\" buttons\n * in empty states so users can ask the assistant to explain concepts like\n * \"What is a Target?\" without any typing.\n */\nimport { useCallback } from 'react';\n/**\n * ★★ The SHARED store, not `@/store/assistantStore`.\n *\n * The panel this product mounts is fe-libs' `AIAssistantPanel`, and its chat\n * area consumes `pendingPrompt` from `fe-libs/shared/assistant/store`. The local\n * store is the pre-migration fork's: a prompt queued there was persisted under\n * `vc-ai-assistant` and read by nobody, so every Get Help button opened an\n * empty panel.\n *\n * ★ Imported from the DEEP subpath, not the barrel. The barrel star-exports the\n * panel components and pulls react-markdown in with them.\n */\nimport { useAssistantStore } from '@burdenoff/fe-libs/shared/assistant/store';\n\nexport function useOpenAssistantHelp() {\n return useCallback((prompt: string) => {\n // ★ Do NOT change the mode.\n //\n // This used to force `general`. That call went to the fork's store, so it\n // never reached the shared panel; moved across with the queue it would take\n // effect for the first time. The shared store PERSISTS `mode`, so one press\n // of a Get Help button would silently move the user out of the mode they\n // chose — Assistant, API Calls, Vibe Plugins — for every later turn too.\n //\n // Whatever mode the user is in already answers \"what is this page for\".\n //\n // Open FIRST, queue on the next macrotask. This is a contract, pinned by the\n // test \"the prompt is not queued until after the open event\", not a race\n // guard. The chat area picks the prompt up in either order: its\n // `pendingPrompt` subscriber if it is mounted, its mount-time\n // `consumePendingPrompt()` if it mounts later. `pendingPrompt` is not\n // persisted, so rehydration cannot clear it.\n //\n // The ordering matters only for a shell that toggles and ignores `open`.\n // Pressed with the panel open, that shell closes it; a prompt queued in the\n // same tick would reach the still-mounted chat area and start a turn in a\n // panel about to unmount. Queued a macrotask later, it waits in the store\n // and is sent on the next open.\n //\n // `open: true` OPENS; it does not toggle. vibecontrols-app's overlay honours\n // it from BOFF-7292, so pressed with the panel already open it stays open.\n window.dispatchEvent(\n new CustomEvent('vc:toggle-ai-assistant', {\n detail: { open: true },\n })\n );\n setTimeout(() => {\n useAssistantStore.getState().submitPrompt(prompt);\n }, 0);\n }, []);\n}\n"],"mappings":";;;AA2BA,SAAgB,IAAuB;AACrC,QAAO,GAAa,MAAmB;AA+BrC,EALA,OAAO,cACL,IAAI,YAAY,0BAA0B,EACxC,QAAQ,EAAE,MAAM,IAAM,EACvB,CAAC,CACH,EACD,iBAAiB;AACf,KAAkB,UAAU,CAAC,aAAa,EAAO;KAChD,EAAE;IACJ,EAAE,CAAC"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useWorkspaceWriteAccess as e } from "@burdenoff/fe-libs/shared/assistant/useWorkspaceWriteAccess";
|
|
2
2
|
//#region src/hooks/useWorkspaceWriteAccess.ts
|
|
3
3
|
var t = [
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
4
|
+
"vibe",
|
|
5
|
+
"session",
|
|
6
|
+
"agent"
|
|
7
7
|
];
|
|
8
8
|
function n(n, r) {
|
|
9
9
|
return e(n, r, t);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWorkspaceWriteAccess.js","names":[],"sources":["../../src/hooks/useWorkspaceWriteAccess.ts"],"sourcesContent":["/**\n * VibeControls' write-access probe.\n *\n * ★ The mechanism lives in `fe-libs/shared/assistant`; the RESOURCE TYPES stay\n * here, because which resources prove \"this user can write in this workspace\"\n * is the product's answer. The shared hook takes them with no default, so\n * another product cannot accidentally probe VibeControls' vibes — or the other\n * way round.\n */\nimport { useWorkspaceWriteAccess as useShared } from '@burdenoff/fe-libs/shared/assistant/useWorkspaceWriteAccess';\nexport type { WorkspaceWriteAccess } from '@burdenoff/fe-libs/shared/assistant/useWorkspaceWriteAccess';\n\n/**\n * Resources an api-calls turn is most likely to write. Not exhaustive — the\n * assistant can reach far more — but a representative probe.\n *\n *
|
|
1
|
+
{"version":3,"file":"useWorkspaceWriteAccess.js","names":[],"sources":["../../src/hooks/useWorkspaceWriteAccess.ts"],"sourcesContent":["/**\n * VibeControls' write-access probe.\n *\n * ★ The mechanism lives in `fe-libs/shared/assistant`; the RESOURCE TYPES stay\n * here, because which resources prove \"this user can write in this workspace\"\n * is the product's answer. The shared hook takes them with no default, so\n * another product cannot accidentally probe VibeControls' vibes — or the other\n * way round.\n */\nimport { useWorkspaceWriteAccess as useShared } from '@burdenoff/fe-libs/shared/assistant/useWorkspaceWriteAccess';\nexport type { WorkspaceWriteAccess } from '@burdenoff/fe-libs/shared/assistant/useWorkspaceWriteAccess';\n\n/**\n * Resources an api-calls turn is most likely to write. Not exhaustive — the\n * assistant can reach far more — but a representative probe. The shared hook\n * asks `checkPermissions` for `action: \"create\"` on each.\n *\n * ★★ These are the names the BACKEND enforces, which are unprefixed:\n * vibe `createVibe` @rbac(action: \"create\", resource: \"vibe\")\n * session `createSession` @rbac(action: \"create\", resource: \"session\")\n * agent `createVibecontrolsAgent` @rbac(action: \"create\", resource: \"agent\")\n * (wspace-vibecontrols-svc, and the composed `graphql/wspace.graphql` here) —\n * and the RBAC service seeds exactly these resource types.\n *\n * They were `vibecontrols_vibe` / `_session` / `_agent`, copied from\n * HealthyBowl's `healthybowl_crop` convention. No such resource type exists in\n * VibeControls' schema or RBAC seed, so no seeded role grants them, RBAC falls\n * through to default-DENY, and the composer's warning said nothing about the\n * permissions a Take-action turn actually needs.\n *\n * Still advisory, never a gate: the shared panel only shows a warning.\n * `useWorkspaceWriteAccess.test.ts` checks these against the schema.\n */\nexport const PROBE_RESOURCES = ['vibe', 'session', 'agent'] as const;\n\nexport function useWorkspaceWriteAccess(actorId: string | null, workspaceId: string | null) {\n return useShared(actorId, workspaceId, PROBE_RESOURCES);\n}\n"],"mappings":";;AAiCA,IAAa,IAAkB;CAAC;CAAQ;CAAW;CAAQ;AAE3D,SAAgB,EAAwB,GAAwB,GAA4B;AAC1F,QAAO,EAAU,GAAS,GAAa,EAAgB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistantApi.js","names":[],"sources":["../../src/services/assistantApi.ts"],"sourcesContent":["/**\n * AI Assistant API — thin adapter over @burdenoff/fe-libs/shared/assistant.\n *\n * The legacy slide-in panel (AssistantChatArea / AssistantMessageItem /\n * useAssistantHeartbeat) calls these with the historical (apollo-first)\n * signatures. The real implementation now lives once in fe-libs; this file only\n * maps the old signatures onto it, retiring ~775 lines of duplicated logic. The\n * `apollo` argument is vestigial (the shared layer uses graphqlFetch) and is\n * ignored; the product id + prod-runtime + TTL the shared functions need are\n * supplied here.\n */\n\nimport {\n findExistingSandbox as sharedFindExisting,\n createAssistantSandbox as sharedCreate,\n waitForSandboxRunning as sharedWaitRunning,\n waitForAssistantServiceReady as sharedWaitService,\n createAssistantSession as sharedCreateSession,\n getAssistantSession as sharedGetSession,\n sendAssistantPromptAsync as sharedSendAsync,\n getAssistantMessages as sharedGetMessages,\n extendAssistantSandboxTTL as sharedExtendTtl,\n sendHeartbeat as sharedHeartbeat,\n getAssistantArtifacts as sharedGetArtifacts,\n downloadAssistantArtifact as sharedDownloadArtifact,\n checkAssistantAiCredits as sharedCheckCredits,\n reportAssistantAiTokenUsage as sharedReportUsage,\n} from '@burdenoff/fe-libs/shared/assistant';\nimport type {\n AssistantRawMessage as SharedRawMessage,\n AssistantSandboxAuthContext as SharedAuthContext,\n AssistantSessionInfo as SharedSessionInfo,\n DownloadedAssistantArtifact,\n} from '@burdenoff/fe-libs/shared/assistant';\nimport type { AssistantArtifact as LocalAssistantArtifact, AssistantMode } from '@/types/assistant';\nimport type { PageContext } from '@/utils/pageContext';\n\n// Re-export the shared types under the names the panel already imports.\nexport type AssistantRawMessage = SharedRawMessage;\nexport type AssistantSandboxAuthContext = SharedAuthContext;\nexport type AssistantSessionInfo = SharedSessionInfo;\n\n// Vestigial Apollo arg — accepted for signature compatibility, ignored.\ntype ApolloClientLike = unknown;\n\nexport const PRODUCT = 'vibecontrols';\n// Matches the original panel behavior: create at 600s, kept warm while open by\n// useAssistantHeartbeat's +600s extend. (The floating widget keeps its own\n// longer-TTL keep-warm via the shared transport.)\nconst DEFAULT_TTL_SECONDS = 600;\n\n// Build-time env parity with the original `assistantApi.ts` image-tag selection\n// (VITE_APP_ENV === 'prod' → prod image tag). Drives getImageTagForMode.\nfunction isProdRuntime(): boolean {\n return import.meta.env.VITE_APP_ENV === 'prod';\n}\n\nconst ctx = (a?: SharedAuthContext): SharedAuthContext => a ?? {};\n\nexport function findExistingSandbox(\n _apollo: ApolloClientLike,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: SharedAuthContext\n): Promise<string | null> {\n return sharedFindExisting(workspaceId, mode, isProdRuntime(), ctx(authContext));\n}\n\nexport function createAssistantSandbox(\n _apollo: ApolloClientLike,\n mode: AssistantMode,\n workspaceId: string,\n authContext?: SharedAuthContext\n): Promise<string> {\n return sharedCreate(\n PRODUCT,\n mode,\n workspaceId,\n isProdRuntime(),\n DEFAULT_TTL_SECONDS,\n ctx(authContext)\n );\n}\n\nexport function waitForSandboxReady(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n authContext?: SharedAuthContext,\n maxWaitMs = 300_000,\n _onProgress?: (message: string) => void\n): Promise<void> {\n return sharedWaitRunning(sandboxId, workspaceId, ctx(authContext), maxWaitMs);\n}\n\nexport function waitForAssistantServiceReady(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n authContext?: SharedAuthContext,\n maxWaitMs = 90_000,\n _onProgress?: (message: string) => void\n): Promise<void> {\n return sharedWaitService(sandboxId, workspaceId, ctx(authContext), maxWaitMs);\n}\n\nexport function createAssistantSession(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: SharedAuthContext\n): Promise<{ sessionId: string }> {\n return sharedCreateSession(sandboxId, workspaceId, mode, ctx(authContext));\n}\n\nexport function getAssistantSession(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: SharedAuthContext\n): Promise<SharedSessionInfo> {\n return sharedGetSession(sandboxId, workspaceId, sessionId, ctx(authContext));\n}\n\nexport function sendAssistantPromptAsync(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n prompt: string,\n mode: AssistantMode,\n pageContext?: PageContext,\n authContext?: SharedAuthContext\n): Promise<{ messageID?: string }> {\n return sharedSendAsync(\n sandboxId,\n workspaceId,\n sessionId,\n prompt,\n mode,\n pageContext as Record<string, unknown> | undefined,\n ctx(authContext)\n );\n}\n\nexport function getAssistantMessages(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: SharedAuthContext,\n limit = 50\n): Promise<SharedRawMessage[]> {\n return sharedGetMessages(sandboxId, workspaceId, sessionId, ctx(authContext), { limit });\n}\n\nexport function checkAssistantAiCredits(\n _apollo: ApolloClientLike,\n workspaceId: string,\n authContext?: SharedAuthContext\n): Promise<boolean> {\n return sharedCheckCredits(workspaceId, ctx(authContext));\n}\n\nexport function reportAssistantAiTokenUsage(\n _apollo: ApolloClientLike,\n workspaceId: string,\n sessionId: string,\n messageId: string,\n model: string | undefined,\n tokens: { input: number; output: number },\n authContext?: SharedAuthContext\n): Promise<void> {\n return sharedReportUsage(workspaceId, sessionId, messageId, model, tokens, ctx(authContext));\n}\n\n/**\n * Object-shaped adapter for the shared product seam.\n *\n * ★ The seam's `reportTokenUsage` takes ONE object; VibeControls' own reporter\n * is positional and leads with an unused Apollo client. Rather than change a\n * signature the existing chat area still calls, adapt here — the seam gets the\n * shape it declares and nothing else moves.\n *\n * ★★ A missing `messageId` or `model` is logged, not swallowed. This meter\n * exists because assistant spend was invisible; a silently skipped charge would\n * recreate exactly that blind spot, one turn at a time. Metering must never\n * break a turn, so this resolves either way.\n */\nexport async function reportAssistantTokenUsage(usage: {\n workspaceId: string;\n sessionId: string;\n messageId?: string;\n model?: string;\n tokens: { input: number; output: number };\n}): Promise<void> {\n const { workspaceId, sessionId, messageId, model, tokens } = usage;\n if (!messageId || !model) {\n console.warn('[assistant] skipping AI-credit charge — turn is missing messageId or model', {\n sessionId,\n hasMessageId: Boolean(messageId),\n hasModel: Boolean(model),\n });\n return;\n }\n await reportAssistantAiTokenUsage(\n null as never, // the positional Apollo client is unused (`_apollo`)\n workspaceId,\n sessionId,\n messageId,\n model,\n tokens\n );\n}\n\nexport function getAssistantArtifacts(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: SharedAuthContext\n): Promise<LocalAssistantArtifact[]> {\n // The shared surface widens `type` to `string`; at runtime the agent only ever\n // returns 'file' | 'directory', so the array is structurally the local type.\n return sharedGetArtifacts(sandboxId, workspaceId, sessionId, ctx(authContext)).then(\n (artifacts) => artifacts as unknown as LocalAssistantArtifact[]\n );\n}\n\nexport function downloadAssistantArtifact(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n artifactPath: string,\n authContext?: SharedAuthContext\n): Promise<DownloadedAssistantArtifact> {\n return sharedDownloadArtifact(sandboxId, workspaceId, sessionId, artifactPath, ctx(authContext));\n}\n\nexport function sendHeartbeat(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: SharedAuthContext\n): Promise<void> {\n return sharedHeartbeat(sandboxId, workspaceId, sessionId, ctx(authContext));\n}\n\nexport function extendAssistantSandboxTTL(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n additionalSeconds = 600,\n authContext?: SharedAuthContext\n): Promise<void> {\n return sharedExtendTtl(sandboxId, workspaceId, additionalSeconds, ctx(authContext));\n}\n"],"mappings":";;AA6CA,IAAa,IAAU,gBAYjB,KAAO,MAA6C,KAAK,EAAE;
|
|
1
|
+
{"version":3,"file":"assistantApi.js","names":[],"sources":["../../src/services/assistantApi.ts"],"sourcesContent":["/**\n * AI Assistant API — thin adapter over @burdenoff/fe-libs/shared/assistant.\n *\n * The legacy slide-in panel (AssistantChatArea / AssistantMessageItem /\n * useAssistantHeartbeat) calls these with the historical (apollo-first)\n * signatures. The real implementation now lives once in fe-libs; this file only\n * maps the old signatures onto it, retiring ~775 lines of duplicated logic. The\n * `apollo` argument is vestigial (the shared layer uses graphqlFetch) and is\n * ignored; the product id + prod-runtime + TTL the shared functions need are\n * supplied here.\n */\n\nimport {\n findExistingSandbox as sharedFindExisting,\n createAssistantSandbox as sharedCreate,\n waitForSandboxRunning as sharedWaitRunning,\n waitForAssistantServiceReady as sharedWaitService,\n createAssistantSession as sharedCreateSession,\n getAssistantSession as sharedGetSession,\n sendAssistantPromptAsync as sharedSendAsync,\n getAssistantMessages as sharedGetMessages,\n extendAssistantSandboxTTL as sharedExtendTtl,\n sendHeartbeat as sharedHeartbeat,\n getAssistantArtifacts as sharedGetArtifacts,\n downloadAssistantArtifact as sharedDownloadArtifact,\n checkAssistantAiCredits as sharedCheckCredits,\n reportAssistantAiTokenUsage as sharedReportUsage,\n} from '@burdenoff/fe-libs/shared/assistant';\nimport type {\n AssistantRawMessage as SharedRawMessage,\n AssistantSandboxAuthContext as SharedAuthContext,\n AssistantSessionInfo as SharedSessionInfo,\n DownloadedAssistantArtifact,\n} from '@burdenoff/fe-libs/shared/assistant';\nimport type { AssistantArtifact as LocalAssistantArtifact, AssistantMode } from '@/types/assistant';\nimport type { PageContext } from '@/utils/pageContext';\n\n// Re-export the shared types under the names the panel already imports.\nexport type AssistantRawMessage = SharedRawMessage;\nexport type AssistantSandboxAuthContext = SharedAuthContext;\nexport type AssistantSessionInfo = SharedSessionInfo;\n\n// Vestigial Apollo arg — accepted for signature compatibility, ignored.\ntype ApolloClientLike = unknown;\n\nexport const PRODUCT = 'vibecontrols';\n// Matches the original panel behavior: create at 600s, kept warm while open by\n// useAssistantHeartbeat's +600s extend. (The floating widget keeps its own\n// longer-TTL keep-warm via the shared transport.)\nconst DEFAULT_TTL_SECONDS = 600;\n\n// Build-time env parity with the original `assistantApi.ts` image-tag selection\n// (VITE_APP_ENV === 'prod' → prod image tag). Drives getImageTagForMode.\nfunction isProdRuntime(): boolean {\n return import.meta.env.VITE_APP_ENV === 'prod';\n}\n\nconst ctx = (a?: SharedAuthContext): SharedAuthContext => a ?? {};\n\nexport function findExistingSandbox(\n _apollo: ApolloClientLike,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: SharedAuthContext\n): Promise<string | null> {\n // ★ fe-libs added `product` as the SECOND argument when sandbox reuse became\n // product-isolated — without it this shim would ask for any product's sandbox.\n // `createAssistantSandbox` below already passed PRODUCT; this one had not been\n // updated, and the stale fe-libs pin hid the mismatch.\n return sharedFindExisting(workspaceId, PRODUCT, mode, isProdRuntime(), ctx(authContext));\n}\n\nexport function createAssistantSandbox(\n _apollo: ApolloClientLike,\n mode: AssistantMode,\n workspaceId: string,\n authContext?: SharedAuthContext\n): Promise<string> {\n return sharedCreate(\n PRODUCT,\n mode,\n workspaceId,\n isProdRuntime(),\n DEFAULT_TTL_SECONDS,\n ctx(authContext)\n );\n}\n\nexport function waitForSandboxReady(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n authContext?: SharedAuthContext,\n maxWaitMs = 300_000,\n _onProgress?: (message: string) => void\n): Promise<void> {\n return sharedWaitRunning(sandboxId, workspaceId, ctx(authContext), maxWaitMs);\n}\n\nexport function waitForAssistantServiceReady(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n authContext?: SharedAuthContext,\n maxWaitMs = 90_000,\n _onProgress?: (message: string) => void\n): Promise<void> {\n return sharedWaitService(sandboxId, workspaceId, ctx(authContext), maxWaitMs);\n}\n\nexport function createAssistantSession(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: SharedAuthContext\n): Promise<{ sessionId: string }> {\n return sharedCreateSession(sandboxId, workspaceId, mode, ctx(authContext));\n}\n\nexport function getAssistantSession(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: SharedAuthContext\n): Promise<SharedSessionInfo> {\n return sharedGetSession(sandboxId, workspaceId, sessionId, ctx(authContext));\n}\n\nexport function sendAssistantPromptAsync(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n prompt: string,\n mode: AssistantMode,\n pageContext?: PageContext,\n authContext?: SharedAuthContext\n): Promise<{ messageID?: string }> {\n return sharedSendAsync(\n sandboxId,\n workspaceId,\n sessionId,\n prompt,\n mode,\n pageContext as Record<string, unknown> | undefined,\n ctx(authContext)\n );\n}\n\nexport function getAssistantMessages(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: SharedAuthContext,\n limit = 50\n): Promise<SharedRawMessage[]> {\n return sharedGetMessages(sandboxId, workspaceId, sessionId, ctx(authContext), { limit });\n}\n\nexport function checkAssistantAiCredits(\n _apollo: ApolloClientLike,\n workspaceId: string,\n authContext?: SharedAuthContext\n): Promise<boolean> {\n return sharedCheckCredits(workspaceId, ctx(authContext));\n}\n\nexport function reportAssistantAiTokenUsage(\n _apollo: ApolloClientLike,\n workspaceId: string,\n sessionId: string,\n messageId: string,\n model: string | undefined,\n tokens: { input: number; output: number },\n authContext?: SharedAuthContext\n): Promise<void> {\n return sharedReportUsage(workspaceId, sessionId, messageId, model, tokens, ctx(authContext));\n}\n\n/**\n * Object-shaped adapter for the shared product seam.\n *\n * ★ The seam's `reportTokenUsage` takes ONE object; VibeControls' own reporter\n * is positional and leads with an unused Apollo client. Rather than change a\n * signature the existing chat area still calls, adapt here — the seam gets the\n * shape it declares and nothing else moves.\n *\n * ★★ A missing `messageId` or `model` is logged, not swallowed. This meter\n * exists because assistant spend was invisible; a silently skipped charge would\n * recreate exactly that blind spot, one turn at a time. Metering must never\n * break a turn, so this resolves either way.\n */\nexport async function reportAssistantTokenUsage(usage: {\n workspaceId: string;\n sessionId: string;\n messageId?: string;\n model?: string;\n tokens: { input: number; output: number };\n}): Promise<void> {\n const { workspaceId, sessionId, messageId, model, tokens } = usage;\n if (!messageId || !model) {\n console.warn('[assistant] skipping AI-credit charge — turn is missing messageId or model', {\n sessionId,\n hasMessageId: Boolean(messageId),\n hasModel: Boolean(model),\n });\n return;\n }\n await reportAssistantAiTokenUsage(\n null as never, // the positional Apollo client is unused (`_apollo`)\n workspaceId,\n sessionId,\n messageId,\n model,\n tokens\n );\n}\n\nexport function getAssistantArtifacts(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: SharedAuthContext\n): Promise<LocalAssistantArtifact[]> {\n // The shared surface widens `type` to `string`; at runtime the agent only ever\n // returns 'file' | 'directory', so the array is structurally the local type.\n return sharedGetArtifacts(sandboxId, workspaceId, sessionId, ctx(authContext)).then(\n (artifacts) => artifacts as unknown as LocalAssistantArtifact[]\n );\n}\n\nexport function downloadAssistantArtifact(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n artifactPath: string,\n authContext?: SharedAuthContext\n): Promise<DownloadedAssistantArtifact> {\n return sharedDownloadArtifact(sandboxId, workspaceId, sessionId, artifactPath, ctx(authContext));\n}\n\nexport function sendHeartbeat(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: SharedAuthContext\n): Promise<void> {\n return sharedHeartbeat(sandboxId, workspaceId, sessionId, ctx(authContext));\n}\n\nexport function extendAssistantSandboxTTL(\n _apollo: ApolloClientLike,\n sandboxId: string,\n workspaceId: string,\n additionalSeconds = 600,\n authContext?: SharedAuthContext\n): Promise<void> {\n return sharedExtendTtl(sandboxId, workspaceId, additionalSeconds, ctx(authContext));\n}\n"],"mappings":";;AA6CA,IAAa,IAAU,gBAYjB,KAAO,MAA6C,KAAK,EAAE;AAiHjE,SAAgB,EACd,GACA,GACA,GACA,GACA,GACA,GACA,GACe;AACf,QAAO,EAAkB,GAAa,GAAW,GAAW,GAAO,GAAQ,EAAI,EAAY,CAAC;;AAgB9F,eAAsB,EAA0B,GAM9B;CAChB,IAAM,EAAE,gBAAa,cAAW,cAAW,UAAO,cAAW;AAC7D,KAAI,CAAC,KAAa,CAAC,GAAO;AACxB,UAAQ,KAAK,8EAA8E;GACzF;GACA,cAAc,EAAQ;GACtB,UAAU,EAAQ;GACnB,CAAC;AACF;;AAEF,OAAM,EACJ,MACA,GACA,GACA,GACA,GACA,EACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistantStore.js","names":[],"sources":["../../src/store/assistantStore.ts"],"sourcesContent":["/**\n * AI Assistant state management.\n *\n * Zustand store with profile-scoped persistence for conversations,\n * active mode, and streaming state. Follows the same pattern as\n * vibeControlsStore.ts.\n */\n\nimport { create } from 'zustand';\nimport { persist } from 'zustand/middleware';\nimport { createProfileScopedStorage } from '@burdenoff/fe-libs/shared/utils';\nimport type {\n AssistantConversationProcessing,\n AssistantConversationStatus,\n AssistantMode,\n AssistantSandboxRuntime,\n AssistantSandboxStatus,\n Conversation,\n Message,\n} from '@/types/assistant';\n\ninterface AssistantState {\n conversations: Conversation[];\n activeConversationId: string | null;\n mode: AssistantMode;\n processingByConversationId: Record<string, AssistantConversationProcessing>;\n conversationListOpen: boolean;\n /** Active sandboxes keyed by assistant mode */\n sandboxesByMode: Partial<Record<AssistantMode, AssistantSandboxRuntime>>;\n /**\n * A prompt queued up for auto-submit by the chat area. Used by the\n * `GetHelpButton` / `useOpenAssistantHelp` hook so pages can prompt the\n * assistant contextually from an empty state button without coupling to\n * the chat area internals.\n */\n pendingPrompt: string | null;\n\n // Actions\n createConversation: (title?: string, mode?: AssistantMode) => string;\n deleteConversation: (id: string) => void;\n renameConversation: (id: string, title: string) => void;\n setActiveConversation: (id: string | null) => void;\n setMode: (mode: AssistantMode) => void;\n setConversationMode: (conversationId: string, mode: AssistantMode) => void;\n addMessage: (conversationId: string, message: Message) => void;\n updateMessage: (conversationId: string, messageId: string, content: string) => void;\n patchMessage: (conversationId: string, messageId: string, patch: Partial<Message>) => void;\n setConversationProcessing: (\n conversationId: string,\n status: AssistantConversationStatus,\n messageId?: string | null,\n error?: string | null\n ) => void;\n clearConversationProcessing: (conversationId: string) => void;\n toggleConversationList: () => void;\n setConversationListOpen: (open: boolean) => void;\n clearConversation: (id: string) => void;\n setBackendSessionId: (conversationId: string, sessionId: string) => void;\n clearBackendSessionId: (conversationId: string) => void;\n setSandbox: (\n mode: AssistantMode,\n sandboxId: string | null,\n status: AssistantSandboxStatus\n ) => void;\n clearSandbox: (mode: AssistantMode) => void;\n resetStore: () => void;\n /** Queue a prompt for the chat area to auto-send on next render. */\n submitPrompt: (text: string) => void;\n /** Returns and clears any queued prompt. */\n consumePendingPrompt: () => string | null;\n}\n\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nexport const useAssistantStore = create<AssistantState>()(\n persist(\n (set, get) => ({\n conversations: [],\n activeConversationId: null,\n mode: 'assistant',\n processingByConversationId: {},\n conversationListOpen: false,\n sandboxesByMode: {},\n pendingPrompt: null,\n\n createConversation: (title?: string, mode?: AssistantMode) => {\n const id = generateId();\n const now = new Date().toISOString();\n const conversationMode = mode ?? get().mode;\n const conversation: Conversation = {\n id,\n title: title || 'New conversation',\n mode: conversationMode,\n messages: [],\n backendSessionId: null,\n createdAt: now,\n updatedAt: now,\n };\n set((state) => ({\n conversations: [conversation, ...state.conversations],\n activeConversationId: id,\n conversationListOpen: false,\n }));\n return id;\n },\n\n deleteConversation: (id) =>\n set((state) => {\n const filtered = state.conversations.filter((c) => c.id !== id);\n const nextProcessing = { ...state.processingByConversationId };\n delete nextProcessing[id];\n return {\n conversations: filtered,\n processingByConversationId: nextProcessing,\n activeConversationId:\n state.activeConversationId === id\n ? (filtered[0]?.id ?? null)\n : state.activeConversationId,\n };\n }),\n\n renameConversation: (id, title) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === id ? { ...c, title, updatedAt: new Date().toISOString() } : c\n ),\n })),\n\n setActiveConversation: (id) =>\n set((state) => {\n const selectedConversation = state.conversations.find(\n (conversation) => conversation.id === id\n );\n return {\n activeConversationId: id,\n mode: selectedConversation?.mode ?? state.mode,\n conversationListOpen: false,\n };\n }),\n\n setMode: (mode) => set({ mode }),\n\n setConversationMode: (conversationId, mode) =>\n set((state) => ({\n conversations: state.conversations.map((conversation) =>\n conversation.id === conversationId\n ? {\n ...conversation,\n mode,\n updatedAt: new Date().toISOString(),\n }\n : conversation\n ),\n mode: state.activeConversationId === conversationId ? mode : state.mode,\n })),\n\n addMessage: (conversationId, message) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId\n ? {\n ...c,\n messages: [...c.messages, message],\n updatedAt: new Date().toISOString(),\n // Auto-title from first user message\n title:\n c.messages.length === 0 && message.role === 'user'\n ? message.content.slice(0, 50) + (message.content.length > 50 ? '...' : '')\n : c.title,\n }\n : c\n ),\n })),\n\n updateMessage: (conversationId, messageId, content) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId\n ? {\n ...c,\n messages: c.messages.map((m) => (m.id === messageId ? { ...m, content } : m)),\n updatedAt: new Date().toISOString(),\n }\n : c\n ),\n })),\n\n patchMessage: (conversationId, messageId, patch) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId\n ? {\n ...c,\n messages: c.messages.map((m) => (m.id === messageId ? { ...m, ...patch } : m)),\n updatedAt: new Date().toISOString(),\n }\n : c\n ),\n })),\n\n setConversationProcessing: (conversationId, status, messageId = null, error = null) =>\n set((state) => ({\n processingByConversationId: {\n ...state.processingByConversationId,\n [conversationId]: {\n status,\n messageId,\n error,\n updatedAt: new Date().toISOString(),\n },\n },\n })),\n\n clearConversationProcessing: (conversationId) =>\n set((state) => {\n const next = { ...state.processingByConversationId };\n delete next[conversationId];\n return { processingByConversationId: next };\n }),\n\n toggleConversationList: () =>\n set((state) => ({ conversationListOpen: !state.conversationListOpen })),\n\n setConversationListOpen: (open) => set({ conversationListOpen: open }),\n\n clearConversation: (id) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === id\n ? {\n ...c,\n messages: [],\n backendSessionId: null,\n updatedAt: new Date().toISOString(),\n }\n : c\n ),\n processingByConversationId: {\n ...state.processingByConversationId,\n [id]: {\n status: 'idle',\n messageId: null,\n error: null,\n updatedAt: new Date().toISOString(),\n },\n },\n })),\n\n setBackendSessionId: (conversationId, sessionId) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId\n ? {\n ...c,\n backendSessionId: sessionId,\n }\n : c\n ),\n })),\n\n clearBackendSessionId: (conversationId) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId ? { ...c, backendSessionId: null } : c\n ),\n })),\n\n setSandbox: (mode, sandboxId, status) =>\n set((state) => {\n if (!sandboxId) {\n const nextSandboxesByMode = { ...state.sandboxesByMode };\n delete nextSandboxesByMode[mode];\n return { sandboxesByMode: nextSandboxesByMode };\n }\n\n return {\n sandboxesByMode: {\n ...state.sandboxesByMode,\n [mode]: {\n sandboxId,\n status,\n },\n },\n };\n }),\n\n clearSandbox: (mode) =>\n set((state) => {\n const nextSandboxesByMode = { ...state.sandboxesByMode };\n delete nextSandboxesByMode[mode];\n return { sandboxesByMode: nextSandboxesByMode };\n }),\n\n resetStore: () =>\n set({\n conversations: [],\n activeConversationId: null,\n mode: 'general',\n processingByConversationId: {},\n conversationListOpen: false,\n sandboxesByMode: {},\n pendingPrompt: null,\n }),\n\n submitPrompt: (text: string) => set({ pendingPrompt: text }),\n\n consumePendingPrompt: () => {\n const current = get().pendingPrompt;\n if (current !== null) set({ pendingPrompt: null });\n return current;\n },\n }),\n {\n name: 'vc-ai-assistant',\n storage: createProfileScopedStorage(),\n partialize: (state) => ({\n conversations: state.conversations.map(({ ...conversation }) => conversation),\n activeConversationId: state.activeConversationId,\n mode: state.mode,\n conversationListOpen: state.conversationListOpen,\n }),\n }\n )\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"assistantStore.js","names":[],"sources":["../../src/store/assistantStore.ts"],"sourcesContent":["/**\n * AI Assistant state management.\n *\n * Zustand store with profile-scoped persistence for conversations,\n * active mode, and streaming state. Follows the same pattern as\n * vibeControlsStore.ts.\n */\n\nimport { create } from 'zustand';\nimport { persist } from 'zustand/middleware';\nimport { createProfileScopedStorage } from '@burdenoff/fe-libs/shared/utils';\nimport type {\n AssistantConversationProcessing,\n AssistantConversationStatus,\n AssistantMode,\n AssistantSandboxRuntime,\n AssistantSandboxStatus,\n Conversation,\n Message,\n} from '@/types/assistant';\n\ninterface AssistantState {\n conversations: Conversation[];\n activeConversationId: string | null;\n mode: AssistantMode;\n processingByConversationId: Record<string, AssistantConversationProcessing>;\n conversationListOpen: boolean;\n /** Active sandboxes keyed by assistant mode */\n sandboxesByMode: Partial<Record<AssistantMode, AssistantSandboxRuntime>>;\n /**\n * A prompt queued up for auto-submit by this fork's chat area.\n *\n * ★ NOT what `GetHelpButton` / `useOpenAssistantHelp` write to: the mounted\n * panel is fe-libs' shared one, which reads `pendingPrompt` from\n * `@burdenoff/fe-libs/shared/assistant/store`. A prompt queued here reaches\n * no mounted component.\n */\n pendingPrompt: string | null;\n\n // Actions\n createConversation: (title?: string, mode?: AssistantMode) => string;\n deleteConversation: (id: string) => void;\n renameConversation: (id: string, title: string) => void;\n setActiveConversation: (id: string | null) => void;\n setMode: (mode: AssistantMode) => void;\n setConversationMode: (conversationId: string, mode: AssistantMode) => void;\n addMessage: (conversationId: string, message: Message) => void;\n updateMessage: (conversationId: string, messageId: string, content: string) => void;\n patchMessage: (conversationId: string, messageId: string, patch: Partial<Message>) => void;\n setConversationProcessing: (\n conversationId: string,\n status: AssistantConversationStatus,\n messageId?: string | null,\n error?: string | null\n ) => void;\n clearConversationProcessing: (conversationId: string) => void;\n toggleConversationList: () => void;\n setConversationListOpen: (open: boolean) => void;\n clearConversation: (id: string) => void;\n setBackendSessionId: (conversationId: string, sessionId: string) => void;\n clearBackendSessionId: (conversationId: string) => void;\n setSandbox: (\n mode: AssistantMode,\n sandboxId: string | null,\n status: AssistantSandboxStatus\n ) => void;\n clearSandbox: (mode: AssistantMode) => void;\n resetStore: () => void;\n /** Queue a prompt for the chat area to auto-send on next render. */\n submitPrompt: (text: string) => void;\n /** Returns and clears any queued prompt. */\n consumePendingPrompt: () => string | null;\n}\n\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nexport const useAssistantStore = create<AssistantState>()(\n persist(\n (set, get) => ({\n conversations: [],\n activeConversationId: null,\n mode: 'assistant',\n processingByConversationId: {},\n conversationListOpen: false,\n sandboxesByMode: {},\n pendingPrompt: null,\n\n createConversation: (title?: string, mode?: AssistantMode) => {\n const id = generateId();\n const now = new Date().toISOString();\n const conversationMode = mode ?? get().mode;\n const conversation: Conversation = {\n id,\n title: title || 'New conversation',\n mode: conversationMode,\n messages: [],\n backendSessionId: null,\n createdAt: now,\n updatedAt: now,\n };\n set((state) => ({\n conversations: [conversation, ...state.conversations],\n activeConversationId: id,\n conversationListOpen: false,\n }));\n return id;\n },\n\n deleteConversation: (id) =>\n set((state) => {\n const filtered = state.conversations.filter((c) => c.id !== id);\n const nextProcessing = { ...state.processingByConversationId };\n delete nextProcessing[id];\n return {\n conversations: filtered,\n processingByConversationId: nextProcessing,\n activeConversationId:\n state.activeConversationId === id\n ? (filtered[0]?.id ?? null)\n : state.activeConversationId,\n };\n }),\n\n renameConversation: (id, title) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === id ? { ...c, title, updatedAt: new Date().toISOString() } : c\n ),\n })),\n\n setActiveConversation: (id) =>\n set((state) => {\n const selectedConversation = state.conversations.find(\n (conversation) => conversation.id === id\n );\n return {\n activeConversationId: id,\n mode: selectedConversation?.mode ?? state.mode,\n conversationListOpen: false,\n };\n }),\n\n setMode: (mode) => set({ mode }),\n\n setConversationMode: (conversationId, mode) =>\n set((state) => ({\n conversations: state.conversations.map((conversation) =>\n conversation.id === conversationId\n ? {\n ...conversation,\n mode,\n updatedAt: new Date().toISOString(),\n }\n : conversation\n ),\n mode: state.activeConversationId === conversationId ? mode : state.mode,\n })),\n\n addMessage: (conversationId, message) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId\n ? {\n ...c,\n messages: [...c.messages, message],\n updatedAt: new Date().toISOString(),\n // Auto-title from first user message\n title:\n c.messages.length === 0 && message.role === 'user'\n ? message.content.slice(0, 50) + (message.content.length > 50 ? '...' : '')\n : c.title,\n }\n : c\n ),\n })),\n\n updateMessage: (conversationId, messageId, content) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId\n ? {\n ...c,\n messages: c.messages.map((m) => (m.id === messageId ? { ...m, content } : m)),\n updatedAt: new Date().toISOString(),\n }\n : c\n ),\n })),\n\n patchMessage: (conversationId, messageId, patch) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId\n ? {\n ...c,\n messages: c.messages.map((m) => (m.id === messageId ? { ...m, ...patch } : m)),\n updatedAt: new Date().toISOString(),\n }\n : c\n ),\n })),\n\n setConversationProcessing: (conversationId, status, messageId = null, error = null) =>\n set((state) => ({\n processingByConversationId: {\n ...state.processingByConversationId,\n [conversationId]: {\n status,\n messageId,\n error,\n updatedAt: new Date().toISOString(),\n },\n },\n })),\n\n clearConversationProcessing: (conversationId) =>\n set((state) => {\n const next = { ...state.processingByConversationId };\n delete next[conversationId];\n return { processingByConversationId: next };\n }),\n\n toggleConversationList: () =>\n set((state) => ({ conversationListOpen: !state.conversationListOpen })),\n\n setConversationListOpen: (open) => set({ conversationListOpen: open }),\n\n clearConversation: (id) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === id\n ? {\n ...c,\n messages: [],\n backendSessionId: null,\n updatedAt: new Date().toISOString(),\n }\n : c\n ),\n processingByConversationId: {\n ...state.processingByConversationId,\n [id]: {\n status: 'idle',\n messageId: null,\n error: null,\n updatedAt: new Date().toISOString(),\n },\n },\n })),\n\n setBackendSessionId: (conversationId, sessionId) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId\n ? {\n ...c,\n backendSessionId: sessionId,\n }\n : c\n ),\n })),\n\n clearBackendSessionId: (conversationId) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId ? { ...c, backendSessionId: null } : c\n ),\n })),\n\n setSandbox: (mode, sandboxId, status) =>\n set((state) => {\n if (!sandboxId) {\n const nextSandboxesByMode = { ...state.sandboxesByMode };\n delete nextSandboxesByMode[mode];\n return { sandboxesByMode: nextSandboxesByMode };\n }\n\n return {\n sandboxesByMode: {\n ...state.sandboxesByMode,\n [mode]: {\n sandboxId,\n status,\n },\n },\n };\n }),\n\n clearSandbox: (mode) =>\n set((state) => {\n const nextSandboxesByMode = { ...state.sandboxesByMode };\n delete nextSandboxesByMode[mode];\n return { sandboxesByMode: nextSandboxesByMode };\n }),\n\n resetStore: () =>\n set({\n conversations: [],\n activeConversationId: null,\n mode: 'general',\n processingByConversationId: {},\n conversationListOpen: false,\n sandboxesByMode: {},\n pendingPrompt: null,\n }),\n\n submitPrompt: (text: string) => set({ pendingPrompt: text }),\n\n consumePendingPrompt: () => {\n const current = get().pendingPrompt;\n if (current !== null) set({ pendingPrompt: null });\n return current;\n },\n }),\n {\n name: 'vc-ai-assistant',\n storage: createProfileScopedStorage(),\n partialize: (state) => ({\n conversations: state.conversations.map(({ ...conversation }) => conversation),\n activeConversationId: state.activeConversationId,\n mode: state.mode,\n conversationListOpen: state.conversationListOpen,\n }),\n }\n )\n);\n"],"mappings":";;;;AA0EA,SAAS,IAAqB;AAC5B,QAAO,GAAG,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE;;AAGhE,IAAa,IAAoB,GAAwB,CACvD,GACG,GAAK,OAAS;CACb,eAAe,EAAE;CACjB,sBAAsB;CACtB,MAAM;CACN,4BAA4B,EAAE;CAC9B,sBAAsB;CACtB,iBAAiB,EAAE;CACnB,eAAe;CAEf,qBAAqB,GAAgB,MAAyB;EAC5D,IAAM,IAAK,GAAY,EACjB,qBAAM,IAAI,MAAM,EAAC,aAAa,EAC9B,IAAmB,KAAQ,GAAK,CAAC,MACjC,IAA6B;GACjC;GACA,OAAO,KAAS;GAChB,MAAM;GACN,UAAU,EAAE;GACZ,kBAAkB;GAClB,WAAW;GACX,WAAW;GACZ;AAMD,SALA,GAAK,OAAW;GACd,eAAe,CAAC,GAAc,GAAG,EAAM,cAAc;GACrD,sBAAsB;GACtB,sBAAsB;GACvB,EAAE,EACI;;CAGT,qBAAqB,MACnB,GAAK,MAAU;EACb,IAAM,IAAW,EAAM,cAAc,QAAQ,MAAM,EAAE,OAAO,EAAG,EACzD,IAAiB,EAAE,GAAG,EAAM,4BAA4B;AAE9D,SADA,OAAO,EAAe,IACf;GACL,eAAe;GACf,4BAA4B;GAC5B,sBACE,EAAM,yBAAyB,IAC1B,EAAS,IAAI,MAAM,OACpB,EAAM;GACb;GACD;CAEJ,qBAAqB,GAAI,MACvB,GAAK,OAAW,EACd,eAAe,EAAM,cAAc,KAAK,MACtC,EAAE,OAAO,IAAK;EAAE,GAAG;EAAG;EAAO,4BAAW,IAAI,MAAM,EAAC,aAAa;EAAE,GAAG,EACtE,EACF,EAAE;CAEL,wBAAwB,MACtB,GAAK,OAII;EACL,sBAAsB;EACtB,MAL2B,EAAM,cAAc,MAC9C,MAAiB,EAAa,OAAO,EACvC,EAG6B,QAAQ,EAAM;EAC1C,sBAAsB;EACvB,EACD;CAEJ,UAAU,MAAS,EAAI,EAAE,SAAM,CAAC;CAEhC,sBAAsB,GAAgB,MACpC,GAAK,OAAW;EACd,eAAe,EAAM,cAAc,KAAK,MACtC,EAAa,OAAO,IAChB;GACE,GAAG;GACH;GACA,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC,GACD,EACL;EACD,MAAM,EAAM,yBAAyB,IAAiB,IAAO,EAAM;EACpE,EAAE;CAEL,aAAa,GAAgB,MAC3B,GAAK,OAAW,EACd,eAAe,EAAM,cAAc,KAAK,MACtC,EAAE,OAAO,IACL;EACE,GAAG;EACH,UAAU,CAAC,GAAG,EAAE,UAAU,EAAQ;EAClC,4BAAW,IAAI,MAAM,EAAC,aAAa;EAEnC,OACE,EAAE,SAAS,WAAW,KAAK,EAAQ,SAAS,SACxC,EAAQ,QAAQ,MAAM,GAAG,GAAG,IAAI,EAAQ,QAAQ,SAAS,KAAK,QAAQ,MACtE,EAAE;EACT,GACD,EACL,EACF,EAAE;CAEL,gBAAgB,GAAgB,GAAW,MACzC,GAAK,OAAW,EACd,eAAe,EAAM,cAAc,KAAK,MACtC,EAAE,OAAO,IACL;EACE,GAAG;EACH,UAAU,EAAE,SAAS,KAAK,MAAO,EAAE,OAAO,IAAY;GAAE,GAAG;GAAG;GAAS,GAAG,EAAG;EAC7E,4BAAW,IAAI,MAAM,EAAC,aAAa;EACpC,GACD,EACL,EACF,EAAE;CAEL,eAAe,GAAgB,GAAW,MACxC,GAAK,OAAW,EACd,eAAe,EAAM,cAAc,KAAK,MACtC,EAAE,OAAO,IACL;EACE,GAAG;EACH,UAAU,EAAE,SAAS,KAAK,MAAO,EAAE,OAAO,IAAY;GAAE,GAAG;GAAG,GAAG;GAAO,GAAG,EAAG;EAC9E,4BAAW,IAAI,MAAM,EAAC,aAAa;EACpC,GACD,EACL,EACF,EAAE;CAEL,4BAA4B,GAAgB,GAAQ,IAAY,MAAM,IAAQ,SAC5E,GAAK,OAAW,EACd,4BAA4B;EAC1B,GAAG,EAAM;GACR,IAAiB;GAChB;GACA;GACA;GACA,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC;EACF,EACF,EAAE;CAEL,8BAA8B,MAC5B,GAAK,MAAU;EACb,IAAM,IAAO,EAAE,GAAG,EAAM,4BAA4B;AAEpD,SADA,OAAO,EAAK,IACL,EAAE,4BAA4B,GAAM;GAC3C;CAEJ,8BACE,GAAK,OAAW,EAAE,sBAAsB,CAAC,EAAM,sBAAsB,EAAE;CAEzE,0BAA0B,MAAS,EAAI,EAAE,sBAAsB,GAAM,CAAC;CAEtE,oBAAoB,MAClB,GAAK,OAAW;EACd,eAAe,EAAM,cAAc,KAAK,MACtC,EAAE,OAAO,IACL;GACE,GAAG;GACH,UAAU,EAAE;GACZ,kBAAkB;GAClB,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC,GACD,EACL;EACD,4BAA4B;GAC1B,GAAG,EAAM;IACR,IAAK;IACJ,QAAQ;IACR,WAAW;IACX,OAAO;IACP,4BAAW,IAAI,MAAM,EAAC,aAAa;IACpC;GACF;EACF,EAAE;CAEL,sBAAsB,GAAgB,MACpC,GAAK,OAAW,EACd,eAAe,EAAM,cAAc,KAAK,MACtC,EAAE,OAAO,IACL;EACE,GAAG;EACH,kBAAkB;EACnB,GACD,EACL,EACF,EAAE;CAEL,wBAAwB,MACtB,GAAK,OAAW,EACd,eAAe,EAAM,cAAc,KAAK,MACtC,EAAE,OAAO,IAAiB;EAAE,GAAG;EAAG,kBAAkB;EAAM,GAAG,EAC9D,EACF,EAAE;CAEL,aAAa,GAAM,GAAW,MAC5B,GAAK,MAAU;AACb,MAAI,CAAC,GAAW;GACd,IAAM,IAAsB,EAAE,GAAG,EAAM,iBAAiB;AAExD,UADA,OAAO,EAAoB,IACpB,EAAE,iBAAiB,GAAqB;;AAGjD,SAAO,EACL,iBAAiB;GACf,GAAG,EAAM;IACR,IAAO;IACN;IACA;IACD;GACF,EACF;GACD;CAEJ,eAAe,MACb,GAAK,MAAU;EACb,IAAM,IAAsB,EAAE,GAAG,EAAM,iBAAiB;AAExD,SADA,OAAO,EAAoB,IACpB,EAAE,iBAAiB,GAAqB;GAC/C;CAEJ,kBACE,EAAI;EACF,eAAe,EAAE;EACjB,sBAAsB;EACtB,MAAM;EACN,4BAA4B,EAAE;EAC9B,sBAAsB;EACtB,iBAAiB,EAAE;EACnB,eAAe;EAChB,CAAC;CAEJ,eAAe,MAAiB,EAAI,EAAE,eAAe,GAAM,CAAC;CAE5D,4BAA4B;EAC1B,IAAM,IAAU,GAAK,CAAC;AAEtB,SADI,MAAY,QAAM,EAAI,EAAE,eAAe,MAAM,CAAC,EAC3C;;CAEV,GACD;CACE,MAAM;CACN,SAAS,GAA4B;CACrC,aAAa,OAAW;EACtB,eAAe,EAAM,cAAc,KAAK,EAAE,GAAG,QAAmB,EAAa;EAC7E,sBAAsB,EAAM;EAC5B,MAAM,EAAM;EACZ,sBAAsB,EAAM;EAC7B;CACF,CACF,CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-vibecontrols",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.915.1",
|
|
4
4
|
"description": "VibeControls microfrontend for Burdenoff products",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
39
39
|
"format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
|
|
40
40
|
"type:check": "bash scripts/build-limits.sh tsc --noEmit",
|
|
41
|
-
"
|
|
41
|
+
"test": "bun test ./src",
|
|
42
|
+
"sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run lint:sanity && bun run format:check && bun run type:check && bun run test && bun run build && bun run build-storybook'",
|
|
42
43
|
"storybook": "storybook dev -p 6006",
|
|
43
44
|
"build-storybook": "storybook build",
|
|
44
45
|
"test:storybook": "test-storybook",
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
"license": "PROPRIETARY",
|
|
63
64
|
"dependencies": {
|
|
64
65
|
"@apollo/client": "4.1.9",
|
|
65
|
-
"@burdenoff/fe-libs": "2026.
|
|
66
|
+
"@burdenoff/fe-libs": "2026.910.4",
|
|
66
67
|
"@tanstack/react-query": "^5.90.16",
|
|
67
68
|
"@xyflow/react": "^12.10.2",
|
|
68
69
|
"clsx": "^2.1.1",
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
-
import { useMemo as t, useState as n } from "react";
|
|
3
|
-
import { Check as r, Copy as i } from "lucide-react";
|
|
4
|
-
import { Fragment as a, jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
5
|
-
//#region src/components/assistant/AssistantMarkdownRenderer.tsx
|
|
6
|
-
function c({ language: t, code: c }) {
|
|
7
|
-
let l = e(), [u, d] = n(!1);
|
|
8
|
-
return /* @__PURE__ */ s("div", {
|
|
9
|
-
className: "group relative my-2 rounded-md bg-bg-sunken border border-border-default overflow-hidden",
|
|
10
|
-
children: [/* @__PURE__ */ s("div", {
|
|
11
|
-
className: "flex items-center justify-between px-3 py-1.5 bg-bg-elevated border-b border-border-default",
|
|
12
|
-
children: [/* @__PURE__ */ o("span", {
|
|
13
|
-
className: "text-[10px] font-mono text-text-muted uppercase tracking-wider",
|
|
14
|
-
children: t || "code"
|
|
15
|
-
}), /* @__PURE__ */ o("button", {
|
|
16
|
-
type: "button",
|
|
17
|
-
onClick: () => {
|
|
18
|
-
navigator.clipboard.writeText(c), d(!0), setTimeout(() => d(!1), 2e3);
|
|
19
|
-
},
|
|
20
|
-
className: "flex items-center gap-1 px-1.5 py-0.5 text-[10px] text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded transition-colors",
|
|
21
|
-
"aria-label": l("vibecontrols.assistant.markdown.copyCode", "Copy code"),
|
|
22
|
-
children: u ? /* @__PURE__ */ s(a, { children: [/* @__PURE__ */ o(r, { className: "size-3 text-status-success-text" }), /* @__PURE__ */ o("span", { children: l("vibecontrols.assistant.markdown.copied", "Copied") })] }) : /* @__PURE__ */ s(a, { children: [/* @__PURE__ */ o(i, { className: "size-3" }), /* @__PURE__ */ o("span", { children: l("vibecontrols.assistant.markdown.copy", "Copy") })] })
|
|
23
|
-
})]
|
|
24
|
-
}), /* @__PURE__ */ o("pre", {
|
|
25
|
-
className: "p-3 overflow-x-auto text-xs leading-relaxed",
|
|
26
|
-
children: /* @__PURE__ */ o("code", {
|
|
27
|
-
className: "font-mono text-text-primary",
|
|
28
|
-
children: c
|
|
29
|
-
})
|
|
30
|
-
})]
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
function l(e) {
|
|
34
|
-
let t = [], n = 0, r = e.split(/(```[\s\S]*?```)/g);
|
|
35
|
-
for (let e of r) {
|
|
36
|
-
let r = e.match(/^```(\w*)\n?([\s\S]*?)```$/);
|
|
37
|
-
if (r) t.push({
|
|
38
|
-
type: "code",
|
|
39
|
-
language: r[1],
|
|
40
|
-
content: r[2].trimEnd(),
|
|
41
|
-
key: n++
|
|
42
|
-
});
|
|
43
|
-
else if (e.trim()) {
|
|
44
|
-
let r = e.split("\n"), i = 0, a = "";
|
|
45
|
-
for (; i < r.length;) if (r[i].includes("|") && i + 1 < r.length && /^\|?[\s-:|]+\|/.test(r[i + 1])) {
|
|
46
|
-
a.trim() && (t.push({
|
|
47
|
-
type: "text",
|
|
48
|
-
content: a.trim(),
|
|
49
|
-
key: n++
|
|
50
|
-
}), a = "");
|
|
51
|
-
let e = "";
|
|
52
|
-
for (; i < r.length && r[i].includes("|");) e += r[i] + "\n", i++;
|
|
53
|
-
t.push({
|
|
54
|
-
type: "table",
|
|
55
|
-
content: e.trim(),
|
|
56
|
-
key: n++
|
|
57
|
-
});
|
|
58
|
-
} else a += r[i] + "\n", i++;
|
|
59
|
-
a.trim() && t.push({
|
|
60
|
-
type: "text",
|
|
61
|
-
content: a.trim(),
|
|
62
|
-
key: n++
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return t;
|
|
67
|
-
}
|
|
68
|
-
function u(e) {
|
|
69
|
-
let t = [], n = e, r = 0;
|
|
70
|
-
for (; n.length > 0;) {
|
|
71
|
-
let e = n.match(/\*\*(.+?)\*\*/), i = n.match(/`([^`]+)`/), a = n.match(/\[([^\]]+)\]\(([^)]+)\)/), s = n.match(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/), c = [
|
|
72
|
-
e && {
|
|
73
|
-
type: "bold",
|
|
74
|
-
match: e,
|
|
75
|
-
index: e.index
|
|
76
|
-
},
|
|
77
|
-
i && {
|
|
78
|
-
type: "code",
|
|
79
|
-
match: i,
|
|
80
|
-
index: i.index
|
|
81
|
-
},
|
|
82
|
-
a && {
|
|
83
|
-
type: "link",
|
|
84
|
-
match: a,
|
|
85
|
-
index: a.index
|
|
86
|
-
},
|
|
87
|
-
s && {
|
|
88
|
-
type: "italic",
|
|
89
|
-
match: s,
|
|
90
|
-
index: s.index
|
|
91
|
-
}
|
|
92
|
-
].filter(Boolean);
|
|
93
|
-
if (c.length === 0) {
|
|
94
|
-
t.push(n);
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
c.sort((e, t) => e.index - t.index);
|
|
98
|
-
let l = c[0];
|
|
99
|
-
switch (l.index > 0 && t.push(n.slice(0, l.index)), l.type) {
|
|
100
|
-
case "bold":
|
|
101
|
-
t.push(/* @__PURE__ */ o("strong", {
|
|
102
|
-
className: "font-semibold",
|
|
103
|
-
children: l.match[1]
|
|
104
|
-
}, r++)), n = n.slice(l.index + l.match[0].length);
|
|
105
|
-
break;
|
|
106
|
-
case "code":
|
|
107
|
-
t.push(/* @__PURE__ */ o("code", {
|
|
108
|
-
className: "px-1 py-0.5 bg-bg-sunken text-text-primary rounded text-[0.85em] font-mono",
|
|
109
|
-
children: l.match[1]
|
|
110
|
-
}, r++)), n = n.slice(l.index + l.match[0].length);
|
|
111
|
-
break;
|
|
112
|
-
case "link":
|
|
113
|
-
t.push(/* @__PURE__ */ o("a", {
|
|
114
|
-
href: l.match[2],
|
|
115
|
-
target: "_blank",
|
|
116
|
-
rel: "noopener noreferrer",
|
|
117
|
-
className: "text-action-primary-bg hover:underline",
|
|
118
|
-
children: l.match[1]
|
|
119
|
-
}, r++)), n = n.slice(l.index + l.match[0].length);
|
|
120
|
-
break;
|
|
121
|
-
case "italic":
|
|
122
|
-
t.push(/* @__PURE__ */ o("em", {
|
|
123
|
-
className: "italic",
|
|
124
|
-
children: l.match[1]
|
|
125
|
-
}, r++)), n = n.slice(l.index + l.match[0].length);
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return t;
|
|
130
|
-
}
|
|
131
|
-
function d({ content: e }) {
|
|
132
|
-
return /* @__PURE__ */ o(a, { children: e.split("\n").map((e, t) => {
|
|
133
|
-
let n = e.trimStart();
|
|
134
|
-
if (n.startsWith("### ")) return /* @__PURE__ */ o("h4", {
|
|
135
|
-
className: "text-sm font-semibold text-text-primary mt-3 mb-1",
|
|
136
|
-
children: u(n.slice(4))
|
|
137
|
-
}, t);
|
|
138
|
-
if (n.startsWith("## ")) return /* @__PURE__ */ o("h3", {
|
|
139
|
-
className: "text-sm font-bold text-text-primary mt-3 mb-1",
|
|
140
|
-
children: u(n.slice(3))
|
|
141
|
-
}, t);
|
|
142
|
-
if (n.startsWith("# ")) return /* @__PURE__ */ o("h2", {
|
|
143
|
-
className: "text-base font-bold text-text-primary mt-3 mb-1",
|
|
144
|
-
children: u(n.slice(2))
|
|
145
|
-
}, t);
|
|
146
|
-
if (/^[-*_]{3,}$/.test(n)) return /* @__PURE__ */ o("hr", { className: "my-2 border-border-default" }, t);
|
|
147
|
-
if (n.startsWith("> ")) return /* @__PURE__ */ o("blockquote", {
|
|
148
|
-
className: "border-l-2 border-action-primary-bg pl-3 text-text-secondary italic my-1",
|
|
149
|
-
children: u(n.slice(2))
|
|
150
|
-
}, t);
|
|
151
|
-
if (/^[-*] /.test(n)) return /* @__PURE__ */ o("li", {
|
|
152
|
-
className: "ml-4 list-disc text-text-primary",
|
|
153
|
-
children: u(n.slice(2))
|
|
154
|
-
}, t);
|
|
155
|
-
let r = n.match(/^(\d+)\.\s/);
|
|
156
|
-
return r ? /* @__PURE__ */ o("li", {
|
|
157
|
-
className: "ml-4 list-decimal text-text-primary",
|
|
158
|
-
children: u(n.slice(r[0].length))
|
|
159
|
-
}, t) : n === "" ? /* @__PURE__ */ o("div", { className: "h-1.5" }, t) : /* @__PURE__ */ o("p", {
|
|
160
|
-
className: "text-text-primary leading-relaxed",
|
|
161
|
-
children: u(e)
|
|
162
|
-
}, t);
|
|
163
|
-
}) });
|
|
164
|
-
}
|
|
165
|
-
function f({ content: e }) {
|
|
166
|
-
let t = e.split("\n").filter(Boolean);
|
|
167
|
-
if (t.length < 2) return null;
|
|
168
|
-
let n = (e) => e.split("|").map((e) => e.trim()).filter(Boolean), r = n(t[0]), i = t.slice(2).map(n);
|
|
169
|
-
return /* @__PURE__ */ o("div", {
|
|
170
|
-
className: "my-2 overflow-x-auto",
|
|
171
|
-
children: /* @__PURE__ */ s("table", {
|
|
172
|
-
className: "text-xs w-full border-collapse",
|
|
173
|
-
children: [/* @__PURE__ */ o("thead", { children: /* @__PURE__ */ o("tr", {
|
|
174
|
-
className: "border-b border-border-default",
|
|
175
|
-
children: r.map((e, t) => /* @__PURE__ */ o("th", {
|
|
176
|
-
className: "text-left px-2 py-1.5 font-semibold text-text-primary",
|
|
177
|
-
children: u(e)
|
|
178
|
-
}, t))
|
|
179
|
-
}) }), /* @__PURE__ */ o("tbody", { children: i.map((e, t) => /* @__PURE__ */ o("tr", {
|
|
180
|
-
className: "border-b border-border-subtle",
|
|
181
|
-
children: e.map((e, t) => /* @__PURE__ */ o("td", {
|
|
182
|
-
className: "px-2 py-1.5 text-text-primary",
|
|
183
|
-
children: u(e)
|
|
184
|
-
}, t))
|
|
185
|
-
}, t)) })]
|
|
186
|
-
})
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
var p = ({ content: e }) => /* @__PURE__ */ o("div", {
|
|
190
|
-
className: "text-xs leading-relaxed space-y-0.5",
|
|
191
|
-
children: t(() => l(e), [e]).map((e) => {
|
|
192
|
-
switch (e.type) {
|
|
193
|
-
case "code": return /* @__PURE__ */ o(c, {
|
|
194
|
-
language: e.language || "",
|
|
195
|
-
code: e.content
|
|
196
|
-
}, e.key);
|
|
197
|
-
case "table": return /* @__PURE__ */ o(f, { content: e.content }, e.key);
|
|
198
|
-
default: return /* @__PURE__ */ o(d, { content: e.content }, e.key);
|
|
199
|
-
}
|
|
200
|
-
})
|
|
201
|
-
});
|
|
202
|
-
//#endregion
|
|
203
|
-
export { p as AssistantMarkdownRenderer };
|
|
204
|
-
|
|
205
|
-
//# sourceMappingURL=AssistantMarkdownRenderer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantMarkdownRenderer.js","names":[],"sources":["../../../src/components/assistant/AssistantMarkdownRenderer.tsx"],"sourcesContent":["/**\n * Lightweight markdown renderer for assistant messages.\n *\n * Handles: headings, bold, italic, code blocks, inline code, links,\n * lists, blockquotes, tables, and horizontal rules using regex parsing.\n * No external dependencies — avoids adding react-markdown to the MFE.\n */\n\nimport React, { useMemo, useState, type FC } from 'react';\nimport { Copy, Check } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface Props {\n content: string;\n}\n\nfunction CodeBlock({ language, code }: { language: string; code: string }) {\n const tr = useTr();\n const [copied, setCopied] = useState(false);\n\n const handleCopy = () => {\n navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n return (\n <div className=\"group relative my-2 rounded-md bg-bg-sunken border border-border-default overflow-hidden\">\n <div className=\"flex items-center justify-between px-3 py-1.5 bg-bg-elevated border-b border-border-default\">\n <span className=\"text-[10px] font-mono text-text-muted uppercase tracking-wider\">\n {language || 'code'}\n </span>\n <button\n type=\"button\"\n onClick={handleCopy}\n className=\"flex items-center gap-1 px-1.5 py-0.5 text-[10px] text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded transition-colors\"\n aria-label={tr('vibecontrols.assistant.markdown.copyCode', 'Copy code')}\n >\n {copied ? (\n <>\n <Check className=\"size-3 text-status-success-text\" />\n <span>{tr('vibecontrols.assistant.markdown.copied', 'Copied')}</span>\n </>\n ) : (\n <>\n <Copy className=\"size-3\" />\n <span>{tr('vibecontrols.assistant.markdown.copy', 'Copy')}</span>\n </>\n )}\n </button>\n </div>\n <pre className=\"p-3 overflow-x-auto text-xs leading-relaxed\">\n <code className=\"font-mono text-text-primary\">{code}</code>\n </pre>\n </div>\n );\n}\n\ninterface ParsedBlock {\n type: 'text' | 'code' | 'table';\n content: string;\n language?: string;\n key: number;\n}\n\nfunction parseBlocks(content: string): ParsedBlock[] {\n const blocks: ParsedBlock[] = [];\n let key = 0;\n\n // Split by code blocks first\n const parts = content.split(/(```[\\s\\S]*?```)/g);\n\n for (const part of parts) {\n const codeMatch = part.match(/^```(\\w*)\\n?([\\s\\S]*?)```$/);\n if (codeMatch) {\n blocks.push({\n type: 'code',\n language: codeMatch[1],\n content: codeMatch[2].trimEnd(),\n key: key++,\n });\n } else if (part.trim()) {\n // Check for tables\n const lines = part.split('\\n');\n let i = 0;\n let textBuffer = '';\n\n while (i < lines.length) {\n // Detect table: line with pipes followed by separator line\n if (lines[i].includes('|') && i + 1 < lines.length && /^\\|?[\\s-:|]+\\|/.test(lines[i + 1])) {\n if (textBuffer.trim()) {\n blocks.push({ type: 'text', content: textBuffer.trim(), key: key++ });\n textBuffer = '';\n }\n let tableContent = '';\n while (i < lines.length && lines[i].includes('|')) {\n tableContent += lines[i] + '\\n';\n i++;\n }\n blocks.push({ type: 'table', content: tableContent.trim(), key: key++ });\n } else {\n textBuffer += lines[i] + '\\n';\n i++;\n }\n }\n\n if (textBuffer.trim()) {\n blocks.push({ type: 'text', content: textBuffer.trim(), key: key++ });\n }\n }\n }\n\n return blocks;\n}\n\nfunction renderInlineMarkdown(text: string): (string | React.JSX.Element)[] {\n const result: (string | React.JSX.Element)[] = [];\n let remaining = text;\n let key = 0;\n\n while (remaining.length > 0) {\n // Bold\n const boldMatch = remaining.match(/\\*\\*(.+?)\\*\\*/);\n // Inline code\n const codeMatch = remaining.match(/`([^`]+)`/);\n // Link\n const linkMatch = remaining.match(/\\[([^\\]]+)\\]\\(([^)]+)\\)/);\n // Italic\n const italicMatch = remaining.match(/(?<!\\*)\\*(?!\\*)(.+?)(?<!\\*)\\*(?!\\*)/);\n\n // Find the earliest match\n const matches = [\n boldMatch && { type: 'bold', match: boldMatch, index: boldMatch.index! },\n codeMatch && { type: 'code', match: codeMatch, index: codeMatch.index! },\n linkMatch && { type: 'link', match: linkMatch, index: linkMatch.index! },\n italicMatch && { type: 'italic', match: italicMatch, index: italicMatch.index! },\n ].filter(Boolean) as Array<{ type: string; match: RegExpMatchArray; index: number }>;\n\n if (matches.length === 0) {\n result.push(remaining);\n break;\n }\n\n matches.sort((a, b) => a.index - b.index);\n const earliest = matches[0];\n\n // Add text before the match\n if (earliest.index > 0) {\n result.push(remaining.slice(0, earliest.index));\n }\n\n switch (earliest.type) {\n case 'bold':\n result.push(\n <strong key={key++} className=\"font-semibold\">\n {earliest.match[1]}\n </strong>\n );\n remaining = remaining.slice(earliest.index + earliest.match[0].length);\n break;\n case 'code':\n result.push(\n <code\n key={key++}\n className=\"px-1 py-0.5 bg-bg-sunken text-text-primary rounded text-[0.85em] font-mono\"\n >\n {earliest.match[1]}\n </code>\n );\n remaining = remaining.slice(earliest.index + earliest.match[0].length);\n break;\n case 'link':\n result.push(\n <a\n key={key++}\n href={earliest.match[2]}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-action-primary-bg hover:underline\"\n >\n {earliest.match[1]}\n </a>\n );\n remaining = remaining.slice(earliest.index + earliest.match[0].length);\n break;\n case 'italic':\n result.push(\n <em key={key++} className=\"italic\">\n {earliest.match[1]}\n </em>\n );\n remaining = remaining.slice(earliest.index + earliest.match[0].length);\n break;\n }\n }\n\n return result;\n}\n\nfunction TextBlock({ content }: { content: string }) {\n const lines = content.split('\\n');\n\n return (\n <>\n {lines.map((line, i) => {\n const trimmed = line.trimStart();\n\n // Headings\n if (trimmed.startsWith('### '))\n return (\n <h4 key={i} className=\"text-sm font-semibold text-text-primary mt-3 mb-1\">\n {renderInlineMarkdown(trimmed.slice(4))}\n </h4>\n );\n if (trimmed.startsWith('## '))\n return (\n <h3 key={i} className=\"text-sm font-bold text-text-primary mt-3 mb-1\">\n {renderInlineMarkdown(trimmed.slice(3))}\n </h3>\n );\n if (trimmed.startsWith('# '))\n return (\n <h2 key={i} className=\"text-base font-bold text-text-primary mt-3 mb-1\">\n {renderInlineMarkdown(trimmed.slice(2))}\n </h2>\n );\n\n // Horizontal rule\n if (/^[-*_]{3,}$/.test(trimmed))\n return <hr key={i} className=\"my-2 border-border-default\" />;\n\n // Blockquote\n if (trimmed.startsWith('> '))\n return (\n <blockquote\n key={i}\n className=\"border-l-2 border-action-primary-bg pl-3 text-text-secondary italic my-1\"\n >\n {renderInlineMarkdown(trimmed.slice(2))}\n </blockquote>\n );\n\n // Unordered list\n if (/^[-*] /.test(trimmed))\n return (\n <li key={i} className=\"ml-4 list-disc text-text-primary\">\n {renderInlineMarkdown(trimmed.slice(2))}\n </li>\n );\n\n // Ordered list\n const olMatch = trimmed.match(/^(\\d+)\\.\\s/);\n if (olMatch)\n return (\n <li key={i} className=\"ml-4 list-decimal text-text-primary\">\n {renderInlineMarkdown(trimmed.slice(olMatch[0].length))}\n </li>\n );\n\n // Empty line\n if (trimmed === '') return <div key={i} className=\"h-1.5\" />;\n\n // Regular paragraph\n return (\n <p key={i} className=\"text-text-primary leading-relaxed\">\n {renderInlineMarkdown(line)}\n </p>\n );\n })}\n </>\n );\n}\n\nfunction TableBlock({ content }: { content: string }) {\n const rows = content.split('\\n').filter(Boolean);\n if (rows.length < 2) return null;\n\n const parseRow = (row: string) =>\n row\n .split('|')\n .map((c) => c.trim())\n .filter(Boolean);\n\n const headers = parseRow(rows[0]);\n // Skip separator row (index 1)\n const bodyRows = rows.slice(2).map(parseRow);\n\n return (\n <div className=\"my-2 overflow-x-auto\">\n <table className=\"text-xs w-full border-collapse\">\n <thead>\n <tr className=\"border-b border-border-default\">\n {headers.map((h, i) => (\n <th key={i} className=\"text-left px-2 py-1.5 font-semibold text-text-primary\">\n {renderInlineMarkdown(h)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {bodyRows.map((row, ri) => (\n <tr key={ri} className=\"border-b border-border-subtle\">\n {row.map((cell, ci) => (\n <td key={ci} className=\"px-2 py-1.5 text-text-primary\">\n {renderInlineMarkdown(cell)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nexport const AssistantMarkdownRenderer: FC<Props> = ({ content }) => {\n const blocks = useMemo(() => parseBlocks(content), [content]);\n\n return (\n <div className=\"text-xs leading-relaxed space-y-0.5\">\n {blocks.map((block) => {\n switch (block.type) {\n case 'code':\n return (\n <CodeBlock key={block.key} language={block.language || ''} code={block.content} />\n );\n case 'table':\n return <TableBlock key={block.key} content={block.content} />;\n default:\n return <TextBlock key={block.key} content={block.content} />;\n }\n })}\n </div>\n );\n};\n"],"mappings":"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"}
|