@flanksource/clicky-ui 0.3.23 → 0.3.25
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/data/ai/AdvancedChatConfig.cjs +3 -0
- package/dist/data/ai/AdvancedChatConfig.cjs.map +1 -1
- package/dist/data/ai/AdvancedChatConfig.d.ts +2 -1
- package/dist/data/ai/AdvancedChatConfig.d.ts.map +1 -1
- package/dist/data/ai/AdvancedChatConfig.js +3 -0
- package/dist/data/ai/AdvancedChatConfig.js.map +1 -1
- package/dist/data/ai/ChatWindow.cjs +77 -4
- package/dist/data/ai/ChatWindow.cjs.map +1 -1
- package/dist/data/ai/ChatWindow.d.ts.map +1 -1
- package/dist/data/ai/ChatWindow.js +78 -5
- package/dist/data/ai/ChatWindow.js.map +1 -1
- package/dist/data/ai/ChatWindow.runtime.cjs +19 -4
- package/dist/data/ai/ChatWindow.runtime.cjs.map +1 -1
- package/dist/data/ai/ChatWindow.runtime.d.ts +2 -0
- package/dist/data/ai/ChatWindow.runtime.d.ts.map +1 -1
- package/dist/data/ai/ChatWindow.runtime.js +19 -4
- package/dist/data/ai/ChatWindow.runtime.js.map +1 -1
- package/dist/data/ai/ChatWindow.test-utils.d.ts +2 -1
- package/dist/data/ai/ChatWindow.test-utils.d.ts.map +1 -1
- package/dist/data/ai/ToolPreferences.cjs +8 -1
- package/dist/data/ai/ToolPreferences.cjs.map +1 -1
- package/dist/data/ai/ToolPreferences.d.ts +2 -1
- package/dist/data/ai/ToolPreferences.d.ts.map +1 -1
- package/dist/data/ai/ToolPreferences.js +8 -1
- package/dist/data/ai/ToolPreferences.js.map +1 -1
- package/dist/data/chat/Chat.cjs +34 -7
- package/dist/data/chat/Chat.cjs.map +1 -1
- package/dist/data/chat/Chat.d.ts +9 -1
- package/dist/data/chat/Chat.d.ts.map +1 -1
- package/dist/data/chat/Chat.js +34 -7
- package/dist/data/chat/Chat.js.map +1 -1
- package/dist/data/chat/ChatRuntimeToolbar.cjs +13 -7
- package/dist/data/chat/ChatRuntimeToolbar.cjs.map +1 -1
- package/dist/data/chat/ChatRuntimeToolbar.d.ts +2 -1
- package/dist/data/chat/ChatRuntimeToolbar.d.ts.map +1 -1
- package/dist/data/chat/ChatRuntimeToolbar.js +13 -7
- package/dist/data/chat/ChatRuntimeToolbar.js.map +1 -1
- package/dist/data/chat/ContextMeter.cjs +64 -27
- package/dist/data/chat/ContextMeter.cjs.map +1 -1
- package/dist/data/chat/ContextMeter.d.ts +11 -4
- package/dist/data/chat/ContextMeter.d.ts.map +1 -1
- package/dist/data/chat/ContextMeter.js +64 -27
- package/dist/data/chat/ContextMeter.js.map +1 -1
- package/dist/data/chat/Message.cjs +36 -1
- package/dist/data/chat/Message.cjs.map +1 -1
- package/dist/data/chat/Message.d.ts +1 -1
- package/dist/data/chat/Message.d.ts.map +1 -1
- package/dist/data/chat/Message.js +37 -2
- package/dist/data/chat/Message.js.map +1 -1
- package/dist/data/chat/approval.cjs +17 -0
- package/dist/data/chat/approval.cjs.map +1 -1
- package/dist/data/chat/approval.d.ts +5 -0
- package/dist/data/chat/approval.d.ts.map +1 -1
- package/dist/data/chat/approval.js +17 -0
- package/dist/data/chat/approval.js.map +1 -1
- package/dist/data/chat/fork-seed.cjs +22 -0
- package/dist/data/chat/fork-seed.cjs.map +1 -0
- package/dist/data/chat/fork-seed.d.ts +8 -0
- package/dist/data/chat/fork-seed.d.ts.map +1 -0
- package/dist/data/chat/fork-seed.js +22 -0
- package/dist/data/chat/fork-seed.js.map +1 -0
- package/dist/data/chat/types.cjs.map +1 -1
- package/dist/data/chat/types.d.ts +19 -0
- package/dist/data/chat/types.d.ts.map +1 -1
- package/dist/data/chat/types.js.map +1 -1
- package/dist/data/chat/usage-snapshot.cjs +10 -1
- package/dist/data/chat/usage-snapshot.cjs.map +1 -1
- package/dist/data/chat/usage-snapshot.d.ts.map +1 -1
- package/dist/data/chat/usage-snapshot.js +10 -1
- package/dist/data/chat/usage-snapshot.js.map +1 -1
- package/dist/data/runtime/RuntimeBar.cjs +3 -0
- package/dist/data/runtime/RuntimeBar.cjs.map +1 -1
- package/dist/data/runtime/RuntimeBar.d.ts +3 -1
- package/dist/data/runtime/RuntimeBar.d.ts.map +1 -1
- package/dist/data/runtime/RuntimeBar.js +3 -0
- package/dist/data/runtime/RuntimeBar.js.map +1 -1
- package/dist/data/runtime/RuntimeBarCombo.cjs +15 -1
- package/dist/data/runtime/RuntimeBarCombo.cjs.map +1 -1
- package/dist/data/runtime/RuntimeBarCombo.d.ts +2 -1
- package/dist/data/runtime/RuntimeBarCombo.d.ts.map +1 -1
- package/dist/data/runtime/RuntimeBarCombo.js +15 -1
- package/dist/data/runtime/RuntimeBarCombo.js.map +1 -1
- package/dist/data/runtime/RuntimeBarSegments.cjs +7 -0
- package/dist/data/runtime/RuntimeBarSegments.cjs.map +1 -1
- package/dist/data/runtime/RuntimeBarSegments.d.ts +2 -1
- package/dist/data/runtime/RuntimeBarSegments.d.ts.map +1 -1
- package/dist/data/runtime/RuntimeBarSegments.js +7 -0
- package/dist/data/runtime/RuntimeBarSegments.js.map +1 -1
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextMeter.js","sources":["../../../src/data/chat/ContextMeter.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { compactTokens, formatCost } from \"../../lib/tokens\";\nimport { UiCheck, UiCopy } from \"../../icons\";\nimport { Icon, type StaticIconComponent } from \"../Icon\";\nimport { HoverCard } from \"../../overlay/HoverCard\";\nimport {\n effortLevelColor,\n effortLevelIcon,\n effortLevelLabel,\n} from \"./effort-icons\";\n\nexport type ContextMeterMode = \"bar\" | \"gauge\";\n\nexport type ContextMeterTokens = {\n input?: number | undefined;\n output?: number | undefined;\n reasoning?: number | undefined;\n cacheRead?: number | undefined;\n cacheWrite?: number | undefined;\n total?: number | undefined;\n};\n\nexport type ContextMeterCost = {\n input?: number | undefined;\n output?: number | undefined;\n reasoning?: number | undefined;\n cacheRead?: number | undefined;\n cacheWrite?: number | undefined;\n total?: number | undefined;\n};\n\nexport type ContextMeterBudget = {\n used?: number | undefined;\n total?: number | undefined;\n remaining?: number | undefined;\n};\n\nexport type ContextMeterProps = {\n /** Trigger shape: a compact `bar` (session header) or a circular `gauge`\n * (chat toolbar). Both open the same hover popover. */\n mode?: ContextMeterMode;\n /** Share of the context window used, 0–100 — drives the fill and tone. */\n usedPercent: number;\n /** Context-window tokens shown in the popover. */\n usedTokens?: number | undefined;\n windowTokens?: number | undefined;\n /** Message count shown in the popover (chat). */\n messageCount?: number | undefined;\n /** Session identifier exposed as a click-to-copy value in the popover. */\n sessionId?: string | undefined;\n /** Runtime mechanism used to execute the session, such as api or cmux. */\n executionMode?: string | undefined;\n /** Model id/label and its brand glyph. */\n model?: string | undefined;\n modelIcon?: StaticIconComponent | undefined;\n modelIconClassName?: string | undefined;\n /** Reasoning effort shown with its semantic glyph and color in the hover card. */\n effort?: string | undefined;\n /** Per-bucket token counts for the popover breakdown. */\n tokens?: ContextMeterTokens | undefined;\n /** Per-bucket costs (USD) for the popover breakdown. */\n cost?: ContextMeterCost | undefined;\n budget?: ContextMeterBudget | undefined;\n className?: string | undefined;\n};\n\nconst COPY_STATUS_RESET_MS = 2000;\nconst GAUGE_RADIUS = 15;\nconst GAUGE_CIRCUMFERENCE = 2 * Math.PI * GAUGE_RADIUS;\n\nfunction barTone(pct: number): string {\n return pct > 80\n ? \"bg-red-600 [[data-theme=dark]_&]:bg-red-400\"\n : pct > 50\n ? \"bg-amber-700 [[data-theme=dark]_&]:bg-amber-400\"\n : \"bg-emerald-600 [[data-theme=dark]_&]:bg-emerald-400\";\n}\n\nfunction ringTone(pct: number): string {\n return pct > 80\n ? \"text-red-600 [[data-theme=dark]_&]:text-red-400\"\n : pct > 50\n ? \"text-amber-700 [[data-theme=dark]_&]:text-amber-400\"\n : \"text-emerald-600 [[data-theme=dark]_&]:text-emerald-400\";\n}\n\nfunction textTone(pct: number): string {\n return pct > 80\n ? \"text-red-700 [[data-theme=dark]_&]:text-red-400\"\n : pct > 50\n ? \"text-amber-700 [[data-theme=dark]_&]:text-amber-400\"\n : \"text-emerald-700 [[data-theme=dark]_&]:text-emerald-400\";\n}\n\nfunction Row({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"flex justify-between gap-4\">\n <span className=\"text-muted-foreground\">{label}</span>\n <span className=\"font-medium tabular-nums\">{value}</span>\n </div>\n );\n}\n\nfunction anyPositive(...values: (number | undefined)[]): boolean {\n return values.some((v) => v != null && v > 0);\n}\n\ntype BucketRow = {\n label: string;\n tokens?: number | undefined;\n cost?: number | undefined;\n};\n\n/** One row per token/cost bucket, kept only when it carries a token or cost\n * value. Cache stays split into read and write on both axes. */\nfunction bucketRows(\n tokens?: ContextMeterTokens,\n cost?: ContextMeterCost,\n): BucketRow[] {\n const rows: BucketRow[] = [\n { label: \"Input\", tokens: tokens?.input, cost: cost?.input },\n { label: \"Output\", tokens: tokens?.output, cost: cost?.output },\n { label: \"Reasoning\", tokens: tokens?.reasoning, cost: cost?.reasoning },\n { label: \"Cache read\", tokens: tokens?.cacheRead, cost: cost?.cacheRead },\n {\n label: \"Cache write\",\n tokens: tokens?.cacheWrite,\n cost: cost?.cacheWrite,\n },\n ];\n return rows.filter((row) => (row.tokens ?? 0) > 0 || (row.cost ?? 0) > 0);\n}\n\nfunction UsageTable({\n rows,\n totalTokens,\n totalCost,\n showTokens,\n showCost,\n}: {\n rows: BucketRow[];\n totalTokens: number;\n totalCost: number;\n showTokens: boolean;\n showCost: boolean;\n}) {\n const numCell = \"px-1 py-0.5 text-right tabular-nums\";\n return (\n <table className=\"w-full border-collapse\">\n <thead>\n <tr className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">\n <th className=\"px-1 py-0.5 text-left font-medium\" />\n {showTokens && (\n <th className=\"px-1 py-0.5 text-right font-medium\">Tokens</th>\n )}\n {showCost && (\n <th className=\"px-1 py-0.5 text-right font-medium\">Cost</th>\n )}\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => (\n <tr key={row.label}>\n <td className=\"px-1 py-0.5 text-left text-muted-foreground\">\n {row.label}\n </td>\n {showTokens && (\n <td className={numCell}>\n {row.tokens ? (\n compactTokens(row.tokens)\n ) : (\n <span className=\"text-muted-foreground/50\">—</span>\n )}\n </td>\n )}\n {showCost && (\n <td className={numCell}>\n {row.cost ? (\n formatCost(row.cost)\n ) : (\n <span className=\"text-muted-foreground/50\">—</span>\n )}\n </td>\n )}\n </tr>\n ))}\n <tr className=\"border-t border-border font-medium\">\n <td className=\"px-1 py-0.5 text-left\">Total</td>\n {showTokens && (\n <td className={numCell}>{compactTokens(totalTokens)}</td>\n )}\n {showCost && <td className={numCell}>{formatCost(totalCost)}</td>}\n </tr>\n </tbody>\n </table>\n );\n}\n\n/** Unified context-window meter. Renders as a compact progress `bar` or a\n * circular `gauge`; hovering or focusing it opens a popover with the model, the\n * copyable session id, execution mode, context-window breakdown, per-bucket\n * token usage and the cost + budget detail. Domain-agnostic — callers feed\n * plain values. */\nexport function ContextMeter({\n mode = \"bar\",\n usedPercent,\n usedTokens,\n windowTokens,\n messageCount,\n sessionId,\n executionMode,\n model,\n modelIcon: Glyph,\n modelIconClassName,\n effort,\n tokens,\n cost,\n budget,\n className,\n}: ContextMeterProps) {\n const [copyResult, setCopyResult] = useState<\n { sessionId: string; status: \"copied\" | \"failed\" } | undefined\n >();\n const copyResetTimer = useRef<number | undefined>(undefined);\n useEffect(\n () => () => {\n if (copyResetTimer.current !== undefined) {\n window.clearTimeout(copyResetTimer.current);\n }\n },\n [],\n );\n const pct = Math.min(100, Math.max(0, Math.round(usedPercent)));\n const totalTokens = tokens?.total ?? 0;\n const totalCost = cost?.total ?? 0;\n const EffortGlyph = effort ? effortLevelIcon(effort) : undefined;\n const effortColor = effort ? effortLevelColor(effort) : undefined;\n const copyStatus =\n copyResult && copyResult.sessionId === sessionId\n ? copyResult.status\n : undefined;\n // The status is transient feedback for the click that produced it — without\n // this it would greet every later hover of the same session.\n const copySessionId = async () => {\n if (!sessionId) return;\n if (copyResetTimer.current !== undefined) {\n window.clearTimeout(copyResetTimer.current);\n }\n try {\n await navigator.clipboard.writeText(sessionId);\n setCopyResult({ sessionId, status: \"copied\" });\n } catch {\n setCopyResult({ sessionId, status: \"failed\" });\n }\n copyResetTimer.current = window.setTimeout(\n () => setCopyResult(undefined),\n COPY_STATUS_RESET_MS,\n );\n };\n\n const trigger =\n mode === \"gauge\" ? (\n <span\n className={cn(\n \"inline-flex items-center rounded-md px-2.5 py-1.5 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground\",\n className,\n )}\n aria-label={`Context ${pct}% used`}\n >\n <span className=\"relative inline-flex size-9 shrink-0 items-center justify-center\">\n <svg\n data-context-gauge-ring\n aria-hidden=\"true\"\n width=\"36\"\n height=\"36\"\n viewBox=\"0 0 36 36\"\n className={cn(\"absolute inset-0\", ringTone(pct))}\n >\n <circle\n cx=\"18\"\n cy=\"18\"\n r={GAUGE_RADIUS}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n opacity=\"0.15\"\n />\n <circle\n cx=\"18\"\n cy=\"18\"\n r={GAUGE_RADIUS}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeDasharray={GAUGE_CIRCUMFERENCE}\n strokeDashoffset={(1 - pct / 100) * GAUGE_CIRCUMFERENCE}\n strokeLinecap=\"round\"\n transform=\"rotate(-90 18 18)\"\n />\n </svg>\n {Glyph ? (\n <Icon\n icon={Glyph}\n className={cn(\"relative size-5\", modelIconClassName)}\n />\n ) : (\n <span\n className={cn(\n \"relative text-[9px] font-semibold tabular-nums\",\n textTone(pct),\n )}\n >\n {pct}\n </span>\n )}\n </span>\n </span>\n ) : (\n <span\n className={cn(\n \"inline-flex items-center gap-1.5 rounded border border-border px-1.5 py-0.5 text-[11px] text-muted-foreground\",\n className,\n )}\n aria-label={`Context ${pct}% used`}\n >\n <span className=\"shrink-0\">ctx</span>\n <span className=\"h-1.5 w-16 overflow-hidden rounded-full bg-muted\">\n <span\n className={cn(\n \"block h-full rounded-full transition-all\",\n barTone(pct),\n )}\n style={{ width: `${pct}%` }}\n />\n </span>\n <span\n className={cn(\"shrink-0 font-medium tabular-nums\", textTone(pct))}\n >\n {pct}%\n </span>\n </span>\n );\n\n return (\n <HoverCard\n trigger={trigger}\n placement=\"bottom\"\n delay={200}\n cardClassName=\"w-72 p-3\"\n >\n <div className=\"space-y-2.5 text-xs\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-semibold\">Context usage</span>\n <span className={cn(\"font-semibold tabular-nums\", textTone(pct))}>\n {pct}%\n </span>\n </div>\n\n {(model || effort) && (\n <div className=\"flex items-center justify-between gap-3 border-b border-border pb-2\">\n {model ? (\n <div className=\"flex min-w-0 items-center gap-2\">\n {Glyph && (\n <Icon\n icon={Glyph}\n className={cn(\"size-4 shrink-0\", modelIconClassName)}\n />\n )}\n <span className=\"truncate font-medium\" title={model}>\n {model}\n </span>\n </div>\n ) : null}\n {effort ? (\n <span\n className={cn(\n \"inline-flex shrink-0 items-center gap-1 font-medium\",\n effortColor,\n )}\n >\n {EffortGlyph ? <EffortGlyph className=\"size-3.5\" /> : null}\n {effortLevelLabel(effort)} effort\n </span>\n ) : null}\n </div>\n )}\n\n {(sessionId || executionMode) && (\n <div className=\"space-y-1.5 border-b border-border pb-2\">\n {sessionId ? (\n <div className=\"flex items-center justify-between gap-4\">\n <span className=\"text-muted-foreground\">Session</span>\n <button\n type=\"button\"\n aria-label={\n copyStatus === \"copied\"\n ? \"Session ID copied\"\n : copyStatus === \"failed\"\n ? \"Session ID copy failed\"\n : \"Copy session ID\"\n }\n title={sessionId}\n className={cn(\n \"flex min-w-0 max-w-48 items-center gap-1.5 font-medium hover:text-foreground\",\n copyStatus === \"failed\" && \"text-destructive\",\n )}\n onClick={() => void copySessionId()}\n >\n <span className=\"truncate font-mono\">{sessionId}</span>\n <Icon\n icon={copyStatus === \"copied\" ? UiCheck : UiCopy}\n className=\"size-3.5 shrink-0\"\n />\n </button>\n </div>\n ) : null}\n {executionMode ? <Row label=\"Mode\" value={executionMode} /> : null}\n </div>\n )}\n\n <div className=\"space-y-1.5\">\n <div className=\"h-1.5 overflow-hidden rounded-full bg-muted\">\n <div\n className={cn(\"h-full rounded-full\", barTone(pct))}\n style={{ width: `${pct}%` }}\n />\n </div>\n {windowTokens ? (\n <>\n <Row\n label=\"Window\"\n value={`${compactTokens(usedTokens ?? 0)} / ${compactTokens(windowTokens)}`}\n />\n <Row label=\"Free\" value={`${100 - pct}%`} />\n </>\n ) : (\n <Row label=\"Used\" value={`${pct}%`} />\n )}\n {messageCount != null ? (\n <Row label=\"Messages\" value={String(messageCount)} />\n ) : null}\n </div>\n\n {(totalTokens > 0 || totalCost > 0 || budget) && (\n <div className=\"space-y-1.5 border-t border-border pt-2\">\n {(totalTokens > 0 || totalCost > 0) && (\n <UsageTable\n rows={bucketRows(tokens, cost)}\n totalTokens={totalTokens}\n totalCost={totalCost}\n showTokens={totalTokens > 0}\n showCost={totalCost > 0}\n />\n )}\n {budget?.total != null && budget.total > 0 ? (\n <Row\n label=\"Budget\"\n value={`${formatCost(budget.used ?? 0)} / ${formatCost(budget.total)}`}\n />\n ) : budget?.remaining != null ? (\n <Row label=\"Budget left\" value={formatCost(budget.remaining)} />\n ) : anyPositive(budget?.used) ? (\n <Row label=\"Budget\" value={formatCost(budget?.used ?? 0)} />\n ) : null}\n </div>\n )}\n </div>\n </HoverCard>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAmEA,MAAM,uBAAuB;AAC7B,MAAM,eAAe;AACrB,MAAM,sBAAsB,IAAI,KAAK,KAAK;AAE1C,SAAS,QAAQ,KAAqB;AACpC,SAAO,MAAM,KACT,gDACA,MAAM,KACJ,oDACA;AACR;AAEA,SAAS,SAAS,KAAqB;AACrC,SAAO,MAAM,KACT,oDACA,MAAM,KACJ,wDACA;AACR;AAEA,SAAS,SAAS,KAAqB;AACrC,SAAO,MAAM,KACT,oDACA,MAAM,KACJ,wDACA;AACR;AAEA,SAAS,IAAI,EAAE,OAAO,SAA2C;AAC/D,SACE,qBAAC,OAAA,EAAI,WAAU,8BACb,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAK,WAAU,yBAAyB,UAAA,OAAM;AAAA,IAC/C,oBAAC,QAAA,EAAK,WAAU,4BAA4B,UAAA,MAAA,CAAM;AAAA,EAAA,GACpD;AAEJ;AAEA,SAAS,eAAe,QAAyC;AAC/D,SAAO,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC;AAC9C;AAUA,SAAS,WACP,QACA,MACa;AACb,QAAM,OAAoB;AAAA,IACxB,EAAE,OAAO,SAAS,QAAQ,iCAAQ,OAAO,MAAM,6BAAM,MAAA;AAAA,IACrD,EAAE,OAAO,UAAU,QAAQ,iCAAQ,QAAQ,MAAM,6BAAM,OAAA;AAAA,IACvD,EAAE,OAAO,aAAa,QAAQ,iCAAQ,WAAW,MAAM,6BAAM,UAAA;AAAA,IAC7D,EAAE,OAAO,cAAc,QAAQ,iCAAQ,WAAW,MAAM,6BAAM,UAAA;AAAA,IAC9D;AAAA,MACE,OAAO;AAAA,MACP,QAAQ,iCAAQ;AAAA,MAChB,MAAM,6BAAM;AAAA,IAAA;AAAA,EACd;AAEF,SAAO,KAAK,OAAO,CAAC,SAAS,IAAI,UAAU,KAAK,MAAM,IAAI,QAAQ,KAAK,CAAC;AAC1E;AAEA,SAAS,WAAW;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,QAAM,UAAU;AAChB,SACE,qBAAC,SAAA,EAAM,WAAU,0BACf,UAAA;AAAA,IAAA,oBAAC,SAAA,EACC,UAAA,qBAAC,MAAA,EAAG,WAAU,6DACZ,UAAA;AAAA,MAAA,oBAAC,MAAA,EAAG,WAAU,oCAAA,CAAoC;AAAA,MACjD,cACC,oBAAC,MAAA,EAAG,WAAU,sCAAqC,UAAA,UAAM;AAAA,MAE1D,YACC,oBAAC,MAAA,EAAG,WAAU,sCAAqC,UAAA,OAAA,CAAI;AAAA,IAAA,EAAA,CAE3D,EAAA,CACF;AAAA,yBACC,SAAA,EACE,UAAA;AAAA,MAAA,KAAK,IAAI,CAAC,QACT,qBAAC,MAAA,EACC,UAAA;AAAA,QAAA,oBAAC,MAAA,EAAG,WAAU,+CACX,UAAA,IAAI,OACP;AAAA,QACC,cACC,oBAAC,MAAA,EAAG,WAAW,SACZ,cAAI,SACH,cAAc,IAAI,MAAM,IAExB,oBAAC,QAAA,EAAK,WAAU,4BAA2B,eAAC,GAEhD;AAAA,QAED,YACC,oBAAC,MAAA,EAAG,WAAW,SACZ,cAAI,OACH,WAAW,IAAI,IAAI,IAEnB,oBAAC,QAAA,EAAK,WAAU,4BAA2B,eAAC,EAAA,CAEhD;AAAA,MAAA,KApBK,IAAI,KAsBb,CACD;AAAA,MACD,qBAAC,MAAA,EAAG,WAAU,sCACZ,UAAA;AAAA,QAAA,oBAAC,MAAA,EAAG,WAAU,yBAAwB,UAAA,SAAK;AAAA,QAC1C,cACC,oBAAC,MAAA,EAAG,WAAW,SAAU,UAAA,cAAc,WAAW,GAAE;AAAA,QAErD,YAAY,oBAAC,MAAA,EAAG,WAAW,SAAU,UAAA,WAAW,SAAS,EAAA,CAAE;AAAA,MAAA,EAAA,CAC9D;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;AAOO,SAAS,aAAa;AAAA,EAC3B,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AACpB,QAAM,CAAC,YAAY,aAAa,IAAI,SAAA;AAGpC,QAAM,iBAAiB,OAA2B,MAAS;AAC3D;AAAA,IACE,MAAM,MAAM;AACV,UAAI,eAAe,YAAY,QAAW;AACxC,eAAO,aAAa,eAAe,OAAO;AAAA,MAC5C;AAAA,IACF;AAAA,IACA,CAAA;AAAA,EAAC;AAEH,QAAM,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,WAAW,CAAC,CAAC;AAC9D,QAAM,eAAc,iCAAQ,UAAS;AACrC,QAAM,aAAY,6BAAM,UAAS;AACjC,QAAM,cAAc,SAAS,gBAAgB,MAAM,IAAI;AACvD,QAAM,cAAc,SAAS,iBAAiB,MAAM,IAAI;AACxD,QAAM,aACJ,cAAc,WAAW,cAAc,YACnC,WAAW,SACX;AAGN,QAAM,gBAAgB,YAAY;AAChC,QAAI,CAAC,UAAW;AAChB,QAAI,eAAe,YAAY,QAAW;AACxC,aAAO,aAAa,eAAe,OAAO;AAAA,IAC5C;AACA,QAAI;AACF,YAAM,UAAU,UAAU,UAAU,SAAS;AAC7C,oBAAc,EAAE,WAAW,QAAQ,SAAA,CAAU;AAAA,IAC/C,QAAQ;AACN,oBAAc,EAAE,WAAW,QAAQ,SAAA,CAAU;AAAA,IAC/C;AACA,mBAAe,UAAU,OAAO;AAAA,MAC9B,MAAM,cAAc,MAAS;AAAA,MAC7B;AAAA,IAAA;AAAA,EAEJ;AAEA,QAAM,UACJ,SAAS,UACP;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,cAAY,WAAW,GAAG;AAAA,MAE1B,UAAA,qBAAC,QAAA,EAAK,WAAU,oEACd,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,2BAAuB;AAAA,YACvB,eAAY;AAAA,YACZ,OAAM;AAAA,YACN,QAAO;AAAA,YACP,SAAQ;AAAA,YACR,WAAW,GAAG,oBAAoB,SAAS,GAAG,CAAC;AAAA,YAE/C,UAAA;AAAA,cAAA;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,IAAG;AAAA,kBACH,IAAG;AAAA,kBACH,GAAG;AAAA,kBACH,MAAK;AAAA,kBACL,QAAO;AAAA,kBACP,aAAY;AAAA,kBACZ,SAAQ;AAAA,gBAAA;AAAA,cAAA;AAAA,cAEV;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,IAAG;AAAA,kBACH,IAAG;AAAA,kBACH,GAAG;AAAA,kBACH,MAAK;AAAA,kBACL,QAAO;AAAA,kBACP,aAAY;AAAA,kBACZ,iBAAiB;AAAA,kBACjB,mBAAmB,IAAI,MAAM,OAAO;AAAA,kBACpC,eAAc;AAAA,kBACd,WAAU;AAAA,gBAAA;AAAA,cAAA;AAAA,YACZ;AAAA,UAAA;AAAA,QAAA;AAAA,QAED,QACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAM;AAAA,YACN,WAAW,GAAG,mBAAmB,kBAAkB;AAAA,UAAA;AAAA,QAAA,IAGrD;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,SAAS,GAAG;AAAA,YAAA;AAAA,YAGb,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,MACH,EAAA,CAEJ;AAAA,IAAA;AAAA,EAAA,IAGF;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,cAAY,WAAW,GAAG;AAAA,MAE1B,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,WAAU,YAAW,UAAA,OAAG;AAAA,QAC9B,oBAAC,QAAA,EAAK,WAAU,oDACd,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,QAAQ,GAAG;AAAA,YAAA;AAAA,YAEb,OAAO,EAAE,OAAO,GAAG,GAAG,IAAA;AAAA,UAAI;AAAA,QAAA,GAE9B;AAAA,QACA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW,GAAG,qCAAqC,SAAS,GAAG,CAAC;AAAA,YAE/D,UAAA;AAAA,cAAA;AAAA,cAAI;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA;AAAA,MACP;AAAA,IAAA;AAAA,EAAA;AAIN,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,WAAU;AAAA,MACV,OAAO;AAAA,MACP,eAAc;AAAA,MAEd,UAAA,qBAAC,OAAA,EAAI,WAAU,uBACb,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,2CACb,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAK,WAAU,iBAAgB,UAAA,iBAAa;AAAA,UAC7C,qBAAC,UAAK,WAAW,GAAG,8BAA8B,SAAS,GAAG,CAAC,GAC5D,UAAA;AAAA,YAAA;AAAA,YAAI;AAAA,UAAA,EAAA,CACP;AAAA,QAAA,GACF;AAAA,SAEE,SAAS,WACT,qBAAC,OAAA,EAAI,WAAU,uEACZ,UAAA;AAAA,UAAA,QACC,qBAAC,OAAA,EAAI,WAAU,mCACZ,UAAA;AAAA,YAAA,SACC;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,MAAM;AAAA,gBACN,WAAW,GAAG,mBAAmB,kBAAkB;AAAA,cAAA;AAAA,YAAA;AAAA,gCAGtD,QAAA,EAAK,WAAU,wBAAuB,OAAO,OAC3C,UAAA,MAAA,CACH;AAAA,UAAA,EAAA,CACF,IACE;AAAA,UACH,SACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,cAAA;AAAA,cAGD,UAAA;AAAA,gBAAA,cAAc,oBAAC,aAAA,EAAY,WAAU,WAAA,CAAW,IAAK;AAAA,gBACrD,iBAAiB,MAAM;AAAA,gBAAE;AAAA,cAAA;AAAA,YAAA;AAAA,UAAA,IAE1B;AAAA,QAAA,GACN;AAAA,SAGA,aAAa,kBACb,qBAAC,OAAA,EAAI,WAAU,2CACZ,UAAA;AAAA,UAAA,YACC,qBAAC,OAAA,EAAI,WAAU,2CACb,UAAA;AAAA,YAAA,oBAAC,QAAA,EAAK,WAAU,yBAAwB,UAAA,WAAO;AAAA,YAC/C;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,MAAK;AAAA,gBACL,cACE,eAAe,WACX,sBACA,eAAe,WACb,2BACA;AAAA,gBAER,OAAO;AAAA,gBACP,WAAW;AAAA,kBACT;AAAA,kBACA,eAAe,YAAY;AAAA,gBAAA;AAAA,gBAE7B,SAAS,MAAM,KAAK,cAAA;AAAA,gBAEpB,UAAA;AAAA,kBAAA,oBAAC,QAAA,EAAK,WAAU,sBAAsB,UAAA,WAAU;AAAA,kBAChD;AAAA,oBAAC;AAAA,oBAAA;AAAA,sBACC,MAAM,eAAe,WAAW,UAAU;AAAA,sBAC1C,WAAU;AAAA,oBAAA;AAAA,kBAAA;AAAA,gBACZ;AAAA,cAAA;AAAA,YAAA;AAAA,UACF,EAAA,CACF,IACE;AAAA,UACH,gBAAgB,oBAAC,KAAA,EAAI,OAAM,QAAO,OAAO,eAAe,IAAK;AAAA,QAAA,GAChE;AAAA,QAGF,qBAAC,OAAA,EAAI,WAAU,eACb,UAAA;AAAA,UAAA,oBAAC,OAAA,EAAI,WAAU,+CACb,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW,GAAG,uBAAuB,QAAQ,GAAG,CAAC;AAAA,cACjD,OAAO,EAAE,OAAO,GAAG,GAAG,IAAA;AAAA,YAAI;AAAA,UAAA,GAE9B;AAAA,UACC,eACC,qBAAA,UAAA,EACE,UAAA;AAAA,YAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,OAAM;AAAA,gBACN,OAAO,GAAG,cAAc,cAAc,CAAC,CAAC,MAAM,cAAc,YAAY,CAAC;AAAA,cAAA;AAAA,YAAA;AAAA,YAE3E,oBAAC,OAAI,OAAM,QAAO,OAAO,GAAG,MAAM,GAAG,IAAA,CAAK;AAAA,UAAA,EAAA,CAC5C,wBAEC,KAAA,EAAI,OAAM,QAAO,OAAO,GAAG,GAAG,IAAA,CAAK;AAAA,UAErC,gBAAgB,OACf,oBAAC,KAAA,EAAI,OAAM,YAAW,OAAO,OAAO,YAAY,EAAA,CAAG,IACjD;AAAA,QAAA,GACN;AAAA,SAEE,cAAc,KAAK,YAAY,KAAK,WACpC,qBAAC,OAAA,EAAI,WAAU,2CACX,UAAA;AAAA,WAAA,cAAc,KAAK,YAAY,MAC/B;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAM,WAAW,QAAQ,IAAI;AAAA,cAC7B;AAAA,cACA;AAAA,cACA,YAAY,cAAc;AAAA,cAC1B,UAAU,YAAY;AAAA,YAAA;AAAA,UAAA;AAAA,WAGzB,iCAAQ,UAAS,QAAQ,OAAO,QAAQ,IACvC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,OAAM;AAAA,cACN,OAAO,GAAG,WAAW,OAAO,QAAQ,CAAC,CAAC,MAAM,WAAW,OAAO,KAAK,CAAC;AAAA,YAAA;AAAA,UAAA,KAEpE,iCAAQ,cAAa,OACvB,oBAAC,KAAA,EAAI,OAAM,eAAc,OAAO,WAAW,OAAO,SAAS,EAAA,CAAG,IAC5D,YAAY,iCAAQ,IAAI,IAC1B,oBAAC,KAAA,EAAI,OAAM,UAAS,OAAO,YAAW,iCAAQ,SAAQ,CAAC,EAAA,CAAG,IACxD;AAAA,QAAA,EAAA,CACN;AAAA,MAAA,EAAA,CAEJ;AAAA,IAAA;AAAA,EAAA;AAGN;"}
|
|
1
|
+
{"version":3,"file":"ContextMeter.js","sources":["../../../src/data/chat/ContextMeter.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { compactTokens, formatCost } from \"../../lib/tokens\";\nimport { UiCheck, UiCopy } from \"../../icons\";\nimport { Icon, type StaticIconComponent } from \"../Icon\";\nimport { HoverCard } from \"../../overlay/HoverCard\";\nimport {\n effortLevelColor,\n effortLevelIcon,\n effortLevelLabel,\n} from \"./effort-icons\";\n\nexport type ContextMeterMode = \"bar\" | \"gauge\";\n\nexport type ContextMeterTokens = {\n input?: number | undefined;\n output?: number | undefined;\n reasoning?: number | undefined;\n cacheRead?: number | undefined;\n cacheWrite?: number | undefined;\n total?: number | undefined;\n};\n\nexport type ContextMeterCost = {\n input?: number | undefined;\n output?: number | undefined;\n reasoning?: number | undefined;\n cacheRead?: number | undefined;\n cacheWrite?: number | undefined;\n total?: number | undefined;\n};\n\nexport type ContextMeterBudget = {\n used?: number | undefined;\n total?: number | undefined;\n remaining?: number | undefined;\n};\n\nexport type ContextMeterProps = {\n /** Trigger shape: a compact `bar` (session header) or a circular `gauge`\n * (chat toolbar). Both open the same hover popover. */\n mode?: ContextMeterMode;\n /** Share of the context window used, 0–100 — drives the fill and tone. */\n usedPercent: number;\n /** Context-window tokens shown in the popover. */\n usedTokens?: number | undefined;\n windowTokens?: number | undefined;\n /** Message count shown in the popover (chat). */\n messageCount?: number | undefined;\n /** Session identifier exposed as a click-to-copy value in the popover. */\n sessionId?: string | undefined;\n /** Terminal chat identities, kept distinct from the generic session id. */\n captainSessionId?: string | undefined;\n providerSessionId?: string | undefined;\n threadId?: string | undefined;\n turnId?: string | undefined;\n /** Resolved backend that produced the terminal chat turn. */\n backend?: string | undefined;\n /** Runtime mechanism used to execute the session, such as api or cmux. */\n executionMode?: string | undefined;\n /** Model id/label and its brand glyph. */\n model?: string | undefined;\n modelIcon?: StaticIconComponent | undefined;\n modelIconClassName?: string | undefined;\n /** Reasoning effort shown with its semantic glyph and color in the hover card. */\n effort?: string | undefined;\n /** Per-bucket token counts for the popover breakdown. */\n tokens?: ContextMeterTokens | undefined;\n /** Per-bucket costs (USD) for the popover breakdown. */\n cost?: ContextMeterCost | undefined;\n budget?: ContextMeterBudget | undefined;\n className?: string | undefined;\n};\n\nconst COPY_STATUS_RESET_MS = 2000;\nconst GAUGE_RADIUS = 15;\nconst GAUGE_CIRCUMFERENCE = 2 * Math.PI * GAUGE_RADIUS;\n\nfunction barTone(pct: number): string {\n return pct > 80\n ? \"bg-red-600 [[data-theme=dark]_&]:bg-red-400\"\n : pct > 50\n ? \"bg-amber-700 [[data-theme=dark]_&]:bg-amber-400\"\n : \"bg-emerald-600 [[data-theme=dark]_&]:bg-emerald-400\";\n}\n\nfunction ringTone(pct: number): string {\n return pct > 80\n ? \"text-red-600 [[data-theme=dark]_&]:text-red-400\"\n : pct > 50\n ? \"text-amber-700 [[data-theme=dark]_&]:text-amber-400\"\n : \"text-emerald-600 [[data-theme=dark]_&]:text-emerald-400\";\n}\n\nfunction textTone(pct: number): string {\n return pct > 80\n ? \"text-red-700 [[data-theme=dark]_&]:text-red-400\"\n : pct > 50\n ? \"text-amber-700 [[data-theme=dark]_&]:text-amber-400\"\n : \"text-emerald-700 [[data-theme=dark]_&]:text-emerald-400\";\n}\n\nfunction Row({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"flex justify-between gap-4\">\n <span className=\"text-muted-foreground\">{label}</span>\n <span className=\"font-medium tabular-nums\">{value}</span>\n </div>\n );\n}\n\nfunction anyPositive(...values: (number | undefined)[]): boolean {\n return values.some((v) => v != null && v > 0);\n}\n\ntype BucketRow = {\n label: string;\n tokens?: number | undefined;\n cost?: number | undefined;\n};\n\ntype CopyableIdentity = {\n key: string;\n label: string;\n value: string;\n};\n\n/** One row per token/cost bucket, kept only when it carries a token or cost\n * value. Cache stays split into read and write on both axes. */\nfunction bucketRows(\n tokens?: ContextMeterTokens,\n cost?: ContextMeterCost,\n): BucketRow[] {\n const rows: BucketRow[] = [\n { label: \"Input\", tokens: tokens?.input, cost: cost?.input },\n { label: \"Output\", tokens: tokens?.output, cost: cost?.output },\n { label: \"Reasoning\", tokens: tokens?.reasoning, cost: cost?.reasoning },\n { label: \"Cache read\", tokens: tokens?.cacheRead, cost: cost?.cacheRead },\n {\n label: \"Cache write\",\n tokens: tokens?.cacheWrite,\n cost: cost?.cacheWrite,\n },\n ];\n return rows.filter((row) => (row.tokens ?? 0) > 0 || (row.cost ?? 0) > 0);\n}\n\nfunction UsageTable({\n rows,\n totalTokens,\n totalCost,\n showTokens,\n showCost,\n}: {\n rows: BucketRow[];\n totalTokens: number;\n totalCost: number;\n showTokens: boolean;\n showCost: boolean;\n}) {\n const numCell = \"px-1 py-0.5 text-right tabular-nums\";\n return (\n <table className=\"w-full border-collapse\">\n <thead>\n <tr className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">\n <th className=\"px-1 py-0.5 text-left font-medium\" />\n {showTokens && (\n <th className=\"px-1 py-0.5 text-right font-medium\">Tokens</th>\n )}\n {showCost && (\n <th className=\"px-1 py-0.5 text-right font-medium\">Cost</th>\n )}\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => (\n <tr key={row.label}>\n <td className=\"px-1 py-0.5 text-left text-muted-foreground\">\n {row.label}\n </td>\n {showTokens && (\n <td className={numCell}>\n {row.tokens ? (\n compactTokens(row.tokens)\n ) : (\n <span className=\"text-muted-foreground/50\">—</span>\n )}\n </td>\n )}\n {showCost && (\n <td className={numCell}>\n {row.cost ? (\n formatCost(row.cost)\n ) : (\n <span className=\"text-muted-foreground/50\">—</span>\n )}\n </td>\n )}\n </tr>\n ))}\n <tr className=\"border-t border-border font-medium\">\n <td className=\"px-1 py-0.5 text-left\">Total</td>\n {showTokens && (\n <td className={numCell}>{compactTokens(totalTokens)}</td>\n )}\n {showCost && <td className={numCell}>{formatCost(totalCost)}</td>}\n </tr>\n </tbody>\n </table>\n );\n}\n\n/** Unified context-window meter. Renders as a compact progress `bar` or a\n * circular `gauge`; hovering or focusing it opens a popover with the model, the\n * runtime and copyable identities, context-window breakdown, per-bucket token\n * usage and the cost + budget detail. Domain-agnostic — callers feed plain\n * values. */\nexport function ContextMeter({\n mode = \"bar\",\n usedPercent,\n usedTokens,\n windowTokens,\n messageCount,\n sessionId,\n captainSessionId,\n providerSessionId,\n threadId,\n turnId,\n backend,\n executionMode,\n model,\n modelIcon: Glyph,\n modelIconClassName,\n effort,\n tokens,\n cost,\n budget,\n className,\n}: ContextMeterProps) {\n const [copyResult, setCopyResult] = useState<\n {\n identity: string;\n value: string;\n status: \"copied\" | \"failed\";\n } | undefined\n >();\n const copyResetTimer = useRef<number | undefined>(undefined);\n useEffect(\n () => () => {\n if (copyResetTimer.current !== undefined) {\n window.clearTimeout(copyResetTimer.current);\n }\n },\n [],\n );\n const pct = Math.min(100, Math.max(0, Math.round(usedPercent)));\n const totalTokens = tokens?.total ?? 0;\n const totalCost = cost?.total ?? 0;\n const EffortGlyph = effort ? effortLevelIcon(effort) : undefined;\n const effortColor = effort ? effortLevelColor(effort) : undefined;\n const identities = [\n sessionId ? { key: \"session\", label: \"Session\", value: sessionId } : undefined,\n captainSessionId\n ? {\n key: \"captain-session\",\n label: \"Captain session\",\n value: captainSessionId,\n }\n : undefined,\n providerSessionId\n ? {\n key: \"provider-session\",\n label: \"Provider session\",\n value: providerSessionId,\n }\n : undefined,\n threadId ? { key: \"thread\", label: \"Thread\", value: threadId } : undefined,\n turnId ? { key: \"turn\", label: \"Turn\", value: turnId } : undefined,\n ].filter((identity): identity is CopyableIdentity => identity !== undefined);\n // The status is transient feedback for the click that produced it — without\n // this it would greet every later hover of the same identity.\n const copyIdentity = async (identity: CopyableIdentity) => {\n if (copyResetTimer.current !== undefined) {\n window.clearTimeout(copyResetTimer.current);\n }\n try {\n await navigator.clipboard.writeText(identity.value);\n setCopyResult({\n identity: identity.key,\n value: identity.value,\n status: \"copied\",\n });\n } catch {\n setCopyResult({\n identity: identity.key,\n value: identity.value,\n status: \"failed\",\n });\n }\n copyResetTimer.current = window.setTimeout(\n () => setCopyResult(undefined),\n COPY_STATUS_RESET_MS,\n );\n };\n\n const trigger =\n mode === \"gauge\" ? (\n <span\n className={cn(\n \"inline-flex items-center rounded-md px-2.5 py-1.5 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground\",\n className,\n )}\n aria-label={`Context ${pct}% used`}\n >\n <span className=\"relative inline-flex size-9 shrink-0 items-center justify-center\">\n <svg\n data-context-gauge-ring\n aria-hidden=\"true\"\n width=\"36\"\n height=\"36\"\n viewBox=\"0 0 36 36\"\n className={cn(\"absolute inset-0\", ringTone(pct))}\n >\n <circle\n cx=\"18\"\n cy=\"18\"\n r={GAUGE_RADIUS}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n opacity=\"0.15\"\n />\n <circle\n cx=\"18\"\n cy=\"18\"\n r={GAUGE_RADIUS}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeDasharray={GAUGE_CIRCUMFERENCE}\n strokeDashoffset={(1 - pct / 100) * GAUGE_CIRCUMFERENCE}\n strokeLinecap=\"round\"\n transform=\"rotate(-90 18 18)\"\n />\n </svg>\n {Glyph ? (\n <Icon\n icon={Glyph}\n className={cn(\"relative size-5\", modelIconClassName)}\n />\n ) : (\n <span\n className={cn(\n \"relative text-[9px] font-semibold tabular-nums\",\n textTone(pct),\n )}\n >\n {pct}\n </span>\n )}\n </span>\n </span>\n ) : (\n <span\n className={cn(\n \"inline-flex items-center gap-1.5 rounded border border-border px-1.5 py-0.5 text-[11px] text-muted-foreground\",\n className,\n )}\n aria-label={`Context ${pct}% used`}\n >\n <span className=\"shrink-0\">ctx</span>\n <span className=\"h-1.5 w-16 overflow-hidden rounded-full bg-muted\">\n <span\n className={cn(\n \"block h-full rounded-full transition-all\",\n barTone(pct),\n )}\n style={{ width: `${pct}%` }}\n />\n </span>\n <span\n className={cn(\"shrink-0 font-medium tabular-nums\", textTone(pct))}\n >\n {pct}%\n </span>\n </span>\n );\n\n return (\n <HoverCard\n trigger={trigger}\n placement=\"bottom\"\n delay={200}\n cardClassName=\"w-72 p-3\"\n >\n <div className=\"space-y-2.5 text-xs\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-semibold\">Context usage</span>\n <span className={cn(\"font-semibold tabular-nums\", textTone(pct))}>\n {pct}%\n </span>\n </div>\n\n {(model || effort) && (\n <div className=\"flex items-center justify-between gap-3 border-b border-border pb-2\">\n {model ? (\n <div className=\"flex min-w-0 items-center gap-2\">\n {Glyph && (\n <Icon\n icon={Glyph}\n className={cn(\"size-4 shrink-0\", modelIconClassName)}\n />\n )}\n <span className=\"truncate font-medium\" title={model}>\n {model}\n </span>\n </div>\n ) : null}\n {effort ? (\n <span\n className={cn(\n \"inline-flex shrink-0 items-center gap-1 font-medium\",\n effortColor,\n )}\n >\n {EffortGlyph ? <EffortGlyph className=\"size-3.5\" /> : null}\n {effortLevelLabel(effort)} effort\n </span>\n ) : null}\n </div>\n )}\n\n {(identities.length > 0 || backend || executionMode) && (\n <div className=\"space-y-1.5 border-b border-border pb-2\">\n {identities.map((identity) => {\n const copyStatus =\n copyResult?.identity === identity.key &&\n copyResult.value === identity.value\n ? copyResult.status\n : undefined;\n return (\n <div\n key={identity.key}\n className=\"flex items-center justify-between gap-4\"\n >\n <span className=\"text-muted-foreground\">\n {identity.label}\n </span>\n <button\n type=\"button\"\n aria-label={\n copyStatus === \"copied\"\n ? `${identity.label} ID copied`\n : copyStatus === \"failed\"\n ? `${identity.label} ID copy failed`\n : `Copy ${identity.label.toLowerCase()} ID`\n }\n title={identity.value}\n className={cn(\n \"flex min-w-0 max-w-48 items-center gap-1.5 font-medium hover:text-foreground\",\n copyStatus === \"failed\" && \"text-destructive\",\n )}\n onClick={() => void copyIdentity(identity)}\n >\n <span className=\"truncate font-mono\">\n {identity.value}\n </span>\n <Icon\n icon={copyStatus === \"copied\" ? UiCheck : UiCopy}\n className=\"size-3.5 shrink-0\"\n />\n </button>\n </div>\n );\n })}\n {backend ? <Row label=\"Backend\" value={backend} /> : null}\n {executionMode ? <Row label=\"Mode\" value={executionMode} /> : null}\n </div>\n )}\n\n <div className=\"space-y-1.5\">\n <div className=\"h-1.5 overflow-hidden rounded-full bg-muted\">\n <div\n className={cn(\"h-full rounded-full\", barTone(pct))}\n style={{ width: `${pct}%` }}\n />\n </div>\n {windowTokens ? (\n <>\n <Row\n label=\"Window\"\n value={`${compactTokens(usedTokens ?? 0)} / ${compactTokens(windowTokens)}`}\n />\n <Row label=\"Free\" value={`${100 - pct}%`} />\n </>\n ) : (\n <Row label=\"Used\" value={`${pct}%`} />\n )}\n {messageCount != null ? (\n <Row label=\"Messages\" value={String(messageCount)} />\n ) : null}\n </div>\n\n {(totalTokens > 0 || totalCost > 0 || budget) && (\n <div className=\"space-y-1.5 border-t border-border pt-2\">\n {(totalTokens > 0 || totalCost > 0) && (\n <UsageTable\n rows={bucketRows(tokens, cost)}\n totalTokens={totalTokens}\n totalCost={totalCost}\n showTokens={totalTokens > 0}\n showCost={totalCost > 0}\n />\n )}\n {budget?.total != null && budget.total > 0 ? (\n <Row\n label=\"Budget\"\n value={`${formatCost(budget.used ?? 0)} / ${formatCost(budget.total)}`}\n />\n ) : budget?.remaining != null ? (\n <Row label=\"Budget left\" value={formatCost(budget.remaining)} />\n ) : anyPositive(budget?.used) ? (\n <Row label=\"Budget\" value={formatCost(budget?.used ?? 0)} />\n ) : null}\n </div>\n )}\n </div>\n </HoverCard>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -8,8 +8,19 @@ const ToolCall = require("./ToolCall.cjs");
|
|
|
8
8
|
const MessageActions = require("./MessageActions.cjs");
|
|
9
9
|
const Reasoning = require("./Reasoning.cjs");
|
|
10
10
|
const types = require("./types.cjs");
|
|
11
|
+
const forkSeed = require("./fork-seed.cjs");
|
|
12
|
+
const UiGitBranch = require("../../icons/components/UiGitBranch.cjs");
|
|
11
13
|
const UiFile = require("../../icons/components/UiFile.cjs");
|
|
12
|
-
function Message({
|
|
14
|
+
function Message({
|
|
15
|
+
message,
|
|
16
|
+
className,
|
|
17
|
+
onRegenerate,
|
|
18
|
+
onApprove,
|
|
19
|
+
renderToolResult
|
|
20
|
+
}) {
|
|
21
|
+
if (forkSeed.isForkSeedMessage(message)) {
|
|
22
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ForkSeedMessage, { message, className });
|
|
23
|
+
}
|
|
13
24
|
const isUser = message.role === "user";
|
|
14
25
|
const text = message.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
15
26
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -51,6 +62,30 @@ function Message({ message, className, onRegenerate, onApprove, renderToolResult
|
|
|
51
62
|
}
|
|
52
63
|
);
|
|
53
64
|
}
|
|
65
|
+
function ForkSeedMessage({
|
|
66
|
+
message,
|
|
67
|
+
className
|
|
68
|
+
}) {
|
|
69
|
+
const provenance = forkSeed.forkSeedProvenance(message);
|
|
70
|
+
const transcript = message.parts.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
71
|
+
const label = provenance.title ? `Forked from ${provenance.title}` : "Forked from another conversation";
|
|
72
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
73
|
+
"details",
|
|
74
|
+
{
|
|
75
|
+
className: utils.cn(
|
|
76
|
+
"group/fork max-w-full self-center rounded-full border border-border bg-muted/40 px-3 py-1.5 text-xs text-muted-foreground open:w-full open:rounded-lg",
|
|
77
|
+
className
|
|
78
|
+
),
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ jsxRuntime.jsxs("summary", { className: "flex cursor-pointer list-none items-center justify-center gap-1.5 font-medium text-foreground/80", children: [
|
|
81
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiGitBranch.UiGitBranch, className: "size-3.5" }),
|
|
82
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: label })
|
|
83
|
+
] }),
|
|
84
|
+
transcript && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "mt-2 max-h-64 overflow-auto whitespace-pre-wrap border-t border-border pt-2 font-mono text-[11px] text-muted-foreground", children: transcript })
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
54
89
|
function MessagePart({
|
|
55
90
|
part,
|
|
56
91
|
isUser,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Message.cjs","sources":["../../../src/data/chat/Message.tsx"],"sourcesContent":["import { cn } from \"../../lib/utils\";\nimport { Icon } from \"../Icon\";\nimport { UiFile } from \"../../icons\";\nimport { Markdown } from \"../Markdown\";\nimport { ToolCall } from \"./ToolCall\";\nimport { MessageActions } from \"./MessageActions\";\nimport { Reasoning } from \"./Reasoning\";\nimport type { UIMessage, FileUIPart } from \"./types\";\nimport {\n isDynamicToolPart,\n isTypedToolPart,\n isReasoningPart,\n isFilePart,\n type AnyToolPart,\n type ToolResultRenderer,\n} from \"./types\";\n\n/** Callbacks the conversation threads down to each message. */\nexport type MessageActionHandlers = {\n /** Re-generate the assistant message with the given id. */\n onRegenerate?: ((messageId: string) => void) | undefined;\n /** Respond to a tool approval request. */\n onApprove
|
|
1
|
+
{"version":3,"file":"Message.cjs","sources":["../../../src/data/chat/Message.tsx"],"sourcesContent":["import { cn } from \"../../lib/utils\";\nimport { Icon } from \"../Icon\";\nimport { UiFile, UiGitBranch } from \"../../icons\";\nimport { Markdown } from \"../Markdown\";\nimport { ToolCall } from \"./ToolCall\";\nimport { MessageActions } from \"./MessageActions\";\nimport { Reasoning } from \"./Reasoning\";\nimport type { UIMessage, FileUIPart } from \"./types\";\nimport {\n isDynamicToolPart,\n isTypedToolPart,\n isReasoningPart,\n isFilePart,\n type AnyToolPart,\n type ToolResultRenderer,\n} from \"./types\";\nimport { forkSeedProvenance, isForkSeedMessage } from \"./fork-seed\";\n\n/** Callbacks the conversation threads down to each message. */\nexport type MessageActionHandlers = {\n /** Re-generate the assistant message with the given id. */\n onRegenerate?: ((messageId: string) => void) | undefined;\n /** Respond to a tool approval request. */\n onApprove?:\n | ((approvalId: string, approved: boolean, reason?: string) => void)\n | undefined;\n /** Optional host renderer for recognized completed tool outputs. */\n renderToolResult?: ToolResultRenderer;\n};\n\nexport type MessageProps = MessageActionHandlers & {\n message: UIMessage;\n className?: string;\n};\n\n/** Renders one chat message. User messages are right-aligned bubbles; assistant\n * messages render text as markdown, reasoning and tool parts inline, file parts\n * as thumbnails/chips, and a hover action row (copy / regenerate). */\nexport function Message({\n message,\n className,\n onRegenerate,\n onApprove,\n renderToolResult,\n}: MessageProps) {\n if (isForkSeedMessage(message)) {\n return <ForkSeedMessage message={message} className={className} />;\n }\n const isUser = message.role === \"user\";\n const text = message.parts\n .filter((p) => p.type === \"text\")\n .map((p) => (p as { text: string }).text)\n .join(\"\");\n\n return (\n <div\n className={cn(\n \"group flex w-full max-w-[95%] flex-col gap-2\",\n isUser ? \"ml-auto items-end\" : \"items-start\",\n className,\n )}\n >\n <div\n className={cn(\n \"flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm\",\n isUser &&\n \"rounded-lg bg-secondary px-4 py-3 text-secondary-foreground\",\n )}\n >\n {message.parts.map((part, i) => (\n <MessagePart\n key={`${message.id}-${i}`}\n part={part}\n isUser={isUser}\n onApprove={onApprove}\n renderToolResult={renderToolResult}\n />\n ))}\n </div>\n\n {!isUser && text && (\n <MessageActions\n text={text}\n onRegenerate={\n onRegenerate ? () => onRegenerate(message.id) : undefined\n }\n />\n )}\n </div>\n );\n}\n\nfunction ForkSeedMessage({\n message,\n className,\n}: {\n message: UIMessage;\n className?: string | undefined;\n}) {\n const provenance = forkSeedProvenance(message);\n const transcript = message.parts\n .filter((part) => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\\n\");\n const label = provenance.title\n ? `Forked from ${provenance.title}`\n : \"Forked from another conversation\";\n return (\n <details\n className={cn(\n \"group/fork max-w-full self-center rounded-full border border-border bg-muted/40 px-3 py-1.5 text-xs text-muted-foreground open:w-full open:rounded-lg\",\n className,\n )}\n >\n <summary className=\"flex cursor-pointer list-none items-center justify-center gap-1.5 font-medium text-foreground/80\">\n <Icon icon={UiGitBranch} className=\"size-3.5\" />\n <span>{label}</span>\n </summary>\n {transcript && (\n <pre className=\"mt-2 max-h-64 overflow-auto whitespace-pre-wrap border-t border-border pt-2 font-mono text-[11px] text-muted-foreground\">\n {transcript}\n </pre>\n )}\n </details>\n );\n}\n\nfunction MessagePart({\n part,\n isUser,\n onApprove,\n renderToolResult,\n}: {\n part: UIMessage[\"parts\"][number];\n isUser: boolean;\n onApprove: MessageActionHandlers[\"onApprove\"];\n renderToolResult: MessageActionHandlers[\"renderToolResult\"];\n}) {\n if (part.type === \"text\") {\n if (isUser) {\n return (\n <span className=\"whitespace-pre-wrap break-words\">{part.text}</span>\n );\n }\n return <Markdown text={part.text} />;\n }\n if (isReasoningPart(part)) {\n return <Reasoning text={part.text} />;\n }\n if (isFilePart(part)) {\n return <FilePart part={part} />;\n }\n if (isDynamicToolPart(part) || isTypedToolPart(part)) {\n return (\n <ToolCall\n part={part as AnyToolPart}\n onApprove={onApprove}\n {...(renderToolResult ? { renderToolResult } : {})}\n />\n );\n }\n return null;\n}\n\n/** Renders an attachment: images inline as a thumbnail, everything else as a\n * labelled file chip. */\nfunction FilePart({ part }: { part: FileUIPart }) {\n const isImage = part.mediaType?.startsWith(\"image/\");\n if (isImage && part.url) {\n return (\n <img\n src={part.url}\n alt={part.filename ?? \"attachment\"}\n className=\"max-h-48 max-w-full rounded-md border border-border\"\n />\n );\n }\n return (\n <a\n href={part.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={UiFile} className=\"size-3.5 shrink-0\" />\n <span className=\"truncate\">\n {part.filename ?? part.mediaType ?? \"file\"}\n </span>\n </a>\n );\n}\n"],"names":["isForkSeedMessage","jsx","jsxs","cn","MessageActions","forkSeedProvenance","Icon","UiGitBranch","Markdown","isReasoningPart","Reasoning","isFilePart","isDynamicToolPart","isTypedToolPart","ToolCall","UiFile"],"mappings":"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"}
|
|
@@ -15,5 +15,5 @@ export type MessageProps = MessageActionHandlers & {
|
|
|
15
15
|
/** Renders one chat message. User messages are right-aligned bubbles; assistant
|
|
16
16
|
* messages render text as markdown, reasoning and tool parts inline, file parts
|
|
17
17
|
* as thumbnails/chips, and a hover action row (copy / regenerate). */
|
|
18
|
-
export declare function Message({ message, className, onRegenerate, onApprove, renderToolResult }: MessageProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export declare function Message({ message, className, onRegenerate, onApprove, renderToolResult, }: MessageProps): import("react/jsx-runtime").JSX.Element;
|
|
19
19
|
//# sourceMappingURL=Message.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Message.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Message.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,SAAS,CAAC;AACrD,OAAO,EAML,KAAK,kBAAkB,EACxB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"Message.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Message.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,SAAS,CAAC;AACrD,OAAO,EAML,KAAK,kBAAkB,EACxB,MAAM,SAAS,CAAC;AAGjB,+DAA+D;AAC/D,MAAM,MAAM,qBAAqB,GAAG;IAClC,2DAA2D;IAC3D,YAAY,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACzD,0CAA0C;IAC1C,SAAS,CAAC,EACN,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC,GAClE,SAAS,CAAC;IACd,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,kBAAkB,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,qBAAqB,GAAG;IACjD,OAAO,EAAE,SAAS,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;uEAEuE;AACvE,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,SAAS,EACT,YAAY,EACZ,SAAS,EACT,gBAAgB,GACjB,EAAE,YAAY,2CA8Cd"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../../lib/utils.js";
|
|
3
3
|
import { Icon } from "../Icon.js";
|
|
4
4
|
import { Markdown } from "../Markdown.js";
|
|
@@ -6,8 +6,19 @@ import { ToolCall } from "./ToolCall.js";
|
|
|
6
6
|
import { MessageActions } from "./MessageActions.js";
|
|
7
7
|
import { Reasoning } from "./Reasoning.js";
|
|
8
8
|
import { isReasoningPart, isFilePart, isDynamicToolPart, isTypedToolPart } from "./types.js";
|
|
9
|
+
import { isForkSeedMessage, forkSeedProvenance } from "./fork-seed.js";
|
|
10
|
+
import { UiGitBranch } from "../../icons/components/UiGitBranch.js";
|
|
9
11
|
import { UiFile } from "../../icons/components/UiFile.js";
|
|
10
|
-
function Message({
|
|
12
|
+
function Message({
|
|
13
|
+
message,
|
|
14
|
+
className,
|
|
15
|
+
onRegenerate,
|
|
16
|
+
onApprove,
|
|
17
|
+
renderToolResult
|
|
18
|
+
}) {
|
|
19
|
+
if (isForkSeedMessage(message)) {
|
|
20
|
+
return /* @__PURE__ */ jsx(ForkSeedMessage, { message, className });
|
|
21
|
+
}
|
|
11
22
|
const isUser = message.role === "user";
|
|
12
23
|
const text = message.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
13
24
|
return /* @__PURE__ */ jsxs(
|
|
@@ -49,6 +60,30 @@ function Message({ message, className, onRegenerate, onApprove, renderToolResult
|
|
|
49
60
|
}
|
|
50
61
|
);
|
|
51
62
|
}
|
|
63
|
+
function ForkSeedMessage({
|
|
64
|
+
message,
|
|
65
|
+
className
|
|
66
|
+
}) {
|
|
67
|
+
const provenance = forkSeedProvenance(message);
|
|
68
|
+
const transcript = message.parts.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
69
|
+
const label = provenance.title ? `Forked from ${provenance.title}` : "Forked from another conversation";
|
|
70
|
+
return /* @__PURE__ */ jsxs(
|
|
71
|
+
"details",
|
|
72
|
+
{
|
|
73
|
+
className: cn(
|
|
74
|
+
"group/fork max-w-full self-center rounded-full border border-border bg-muted/40 px-3 py-1.5 text-xs text-muted-foreground open:w-full open:rounded-lg",
|
|
75
|
+
className
|
|
76
|
+
),
|
|
77
|
+
children: [
|
|
78
|
+
/* @__PURE__ */ jsxs("summary", { className: "flex cursor-pointer list-none items-center justify-center gap-1.5 font-medium text-foreground/80", children: [
|
|
79
|
+
/* @__PURE__ */ jsx(Icon, { icon: UiGitBranch, className: "size-3.5" }),
|
|
80
|
+
/* @__PURE__ */ jsx("span", { children: label })
|
|
81
|
+
] }),
|
|
82
|
+
transcript && /* @__PURE__ */ jsx("pre", { className: "mt-2 max-h-64 overflow-auto whitespace-pre-wrap border-t border-border pt-2 font-mono text-[11px] text-muted-foreground", children: transcript })
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
}
|
|
52
87
|
function MessagePart({
|
|
53
88
|
part,
|
|
54
89
|
isUser,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Message.js","sources":["../../../src/data/chat/Message.tsx"],"sourcesContent":["import { cn } from \"../../lib/utils\";\nimport { Icon } from \"../Icon\";\nimport { UiFile } from \"../../icons\";\nimport { Markdown } from \"../Markdown\";\nimport { ToolCall } from \"./ToolCall\";\nimport { MessageActions } from \"./MessageActions\";\nimport { Reasoning } from \"./Reasoning\";\nimport type { UIMessage, FileUIPart } from \"./types\";\nimport {\n isDynamicToolPart,\n isTypedToolPart,\n isReasoningPart,\n isFilePart,\n type AnyToolPart,\n type ToolResultRenderer,\n} from \"./types\";\n\n/** Callbacks the conversation threads down to each message. */\nexport type MessageActionHandlers = {\n /** Re-generate the assistant message with the given id. */\n onRegenerate?: ((messageId: string) => void) | undefined;\n /** Respond to a tool approval request. */\n onApprove
|
|
1
|
+
{"version":3,"file":"Message.js","sources":["../../../src/data/chat/Message.tsx"],"sourcesContent":["import { cn } from \"../../lib/utils\";\nimport { Icon } from \"../Icon\";\nimport { UiFile, UiGitBranch } from \"../../icons\";\nimport { Markdown } from \"../Markdown\";\nimport { ToolCall } from \"./ToolCall\";\nimport { MessageActions } from \"./MessageActions\";\nimport { Reasoning } from \"./Reasoning\";\nimport type { UIMessage, FileUIPart } from \"./types\";\nimport {\n isDynamicToolPart,\n isTypedToolPart,\n isReasoningPart,\n isFilePart,\n type AnyToolPart,\n type ToolResultRenderer,\n} from \"./types\";\nimport { forkSeedProvenance, isForkSeedMessage } from \"./fork-seed\";\n\n/** Callbacks the conversation threads down to each message. */\nexport type MessageActionHandlers = {\n /** Re-generate the assistant message with the given id. */\n onRegenerate?: ((messageId: string) => void) | undefined;\n /** Respond to a tool approval request. */\n onApprove?:\n | ((approvalId: string, approved: boolean, reason?: string) => void)\n | undefined;\n /** Optional host renderer for recognized completed tool outputs. */\n renderToolResult?: ToolResultRenderer;\n};\n\nexport type MessageProps = MessageActionHandlers & {\n message: UIMessage;\n className?: string;\n};\n\n/** Renders one chat message. User messages are right-aligned bubbles; assistant\n * messages render text as markdown, reasoning and tool parts inline, file parts\n * as thumbnails/chips, and a hover action row (copy / regenerate). */\nexport function Message({\n message,\n className,\n onRegenerate,\n onApprove,\n renderToolResult,\n}: MessageProps) {\n if (isForkSeedMessage(message)) {\n return <ForkSeedMessage message={message} className={className} />;\n }\n const isUser = message.role === \"user\";\n const text = message.parts\n .filter((p) => p.type === \"text\")\n .map((p) => (p as { text: string }).text)\n .join(\"\");\n\n return (\n <div\n className={cn(\n \"group flex w-full max-w-[95%] flex-col gap-2\",\n isUser ? \"ml-auto items-end\" : \"items-start\",\n className,\n )}\n >\n <div\n className={cn(\n \"flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm\",\n isUser &&\n \"rounded-lg bg-secondary px-4 py-3 text-secondary-foreground\",\n )}\n >\n {message.parts.map((part, i) => (\n <MessagePart\n key={`${message.id}-${i}`}\n part={part}\n isUser={isUser}\n onApprove={onApprove}\n renderToolResult={renderToolResult}\n />\n ))}\n </div>\n\n {!isUser && text && (\n <MessageActions\n text={text}\n onRegenerate={\n onRegenerate ? () => onRegenerate(message.id) : undefined\n }\n />\n )}\n </div>\n );\n}\n\nfunction ForkSeedMessage({\n message,\n className,\n}: {\n message: UIMessage;\n className?: string | undefined;\n}) {\n const provenance = forkSeedProvenance(message);\n const transcript = message.parts\n .filter((part) => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\\n\");\n const label = provenance.title\n ? `Forked from ${provenance.title}`\n : \"Forked from another conversation\";\n return (\n <details\n className={cn(\n \"group/fork max-w-full self-center rounded-full border border-border bg-muted/40 px-3 py-1.5 text-xs text-muted-foreground open:w-full open:rounded-lg\",\n className,\n )}\n >\n <summary className=\"flex cursor-pointer list-none items-center justify-center gap-1.5 font-medium text-foreground/80\">\n <Icon icon={UiGitBranch} className=\"size-3.5\" />\n <span>{label}</span>\n </summary>\n {transcript && (\n <pre className=\"mt-2 max-h-64 overflow-auto whitespace-pre-wrap border-t border-border pt-2 font-mono text-[11px] text-muted-foreground\">\n {transcript}\n </pre>\n )}\n </details>\n );\n}\n\nfunction MessagePart({\n part,\n isUser,\n onApprove,\n renderToolResult,\n}: {\n part: UIMessage[\"parts\"][number];\n isUser: boolean;\n onApprove: MessageActionHandlers[\"onApprove\"];\n renderToolResult: MessageActionHandlers[\"renderToolResult\"];\n}) {\n if (part.type === \"text\") {\n if (isUser) {\n return (\n <span className=\"whitespace-pre-wrap break-words\">{part.text}</span>\n );\n }\n return <Markdown text={part.text} />;\n }\n if (isReasoningPart(part)) {\n return <Reasoning text={part.text} />;\n }\n if (isFilePart(part)) {\n return <FilePart part={part} />;\n }\n if (isDynamicToolPart(part) || isTypedToolPart(part)) {\n return (\n <ToolCall\n part={part as AnyToolPart}\n onApprove={onApprove}\n {...(renderToolResult ? { renderToolResult } : {})}\n />\n );\n }\n return null;\n}\n\n/** Renders an attachment: images inline as a thumbnail, everything else as a\n * labelled file chip. */\nfunction FilePart({ part }: { part: FileUIPart }) {\n const isImage = part.mediaType?.startsWith(\"image/\");\n if (isImage && part.url) {\n return (\n <img\n src={part.url}\n alt={part.filename ?? \"attachment\"}\n className=\"max-h-48 max-w-full rounded-md border border-border\"\n />\n );\n }\n return (\n <a\n href={part.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={UiFile} className=\"size-3.5 shrink-0\" />\n <span className=\"truncate\">\n {part.filename ?? part.mediaType ?? \"file\"}\n </span>\n </a>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;AAsCO,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAiB;AACf,MAAI,kBAAkB,OAAO,GAAG;AAC9B,WAAO,oBAAC,iBAAA,EAAgB,SAAkB,UAAA,CAAsB;AAAA,EAClE;AACA,QAAM,SAAS,QAAQ,SAAS;AAChC,QAAM,OAAO,QAAQ,MAClB,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,EAC/B,IAAI,CAAC,MAAO,EAAuB,IAAI,EACvC,KAAK,EAAE;AAEV,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,SAAS,sBAAsB;AAAA,QAC/B;AAAA,MAAA;AAAA,MAGF,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,UACE;AAAA,YAAA;AAAA,YAGH,UAAA,QAAQ,MAAM,IAAI,CAAC,MAAM,MACxB;AAAA,cAAC;AAAA,cAAA;AAAA,gBAEC;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,cAAA;AAAA,cAJK,GAAG,QAAQ,EAAE,IAAI,CAAC;AAAA,YAAA,CAM1B;AAAA,UAAA;AAAA,QAAA;AAAA,QAGF,CAAC,UAAU,QACV;AAAA,UAAC;AAAA,UAAA;AAAA,YACC;AAAA,YACA,cACE,eAAe,MAAM,aAAa,QAAQ,EAAE,IAAI;AAAA,UAAA;AAAA,QAAA;AAAA,MAEpD;AAAA,IAAA;AAAA,EAAA;AAIR;AAEA,SAAS,gBAAgB;AAAA,EACvB;AAAA,EACA;AACF,GAGG;AACD,QAAM,aAAa,mBAAmB,OAAO;AAC7C,QAAM,aAAa,QAAQ,MACxB,OAAO,CAAC,SAAS,KAAK,SAAS,MAAM,EACrC,IAAI,CAAC,SAAS,KAAK,IAAI,EACvB,KAAK,IAAI;AACZ,QAAM,QAAQ,WAAW,QACrB,eAAe,WAAW,KAAK,KAC/B;AACJ,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAGF,UAAA;AAAA,QAAA,qBAAC,WAAA,EAAQ,WAAU,oGACjB,UAAA;AAAA,UAAA,oBAAC,MAAA,EAAK,MAAM,aAAa,WAAU,YAAW;AAAA,UAC9C,oBAAC,UAAM,UAAA,MAAA,CAAM;AAAA,QAAA,GACf;AAAA,QACC,cACC,oBAAC,OAAA,EAAI,WAAU,2HACZ,UAAA,WAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIR;AAEA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,MAAI,KAAK,SAAS,QAAQ;AACxB,QAAI,QAAQ;AACV,aACE,oBAAC,QAAA,EAAK,WAAU,mCAAmC,eAAK,MAAK;AAAA,IAEjE;AACA,WAAO,oBAAC,UAAA,EAAS,MAAM,KAAK,KAAA,CAAM;AAAA,EACpC;AACA,MAAI,gBAAgB,IAAI,GAAG;AACzB,WAAO,oBAAC,WAAA,EAAU,MAAM,KAAK,KAAA,CAAM;AAAA,EACrC;AACA,MAAI,WAAW,IAAI,GAAG;AACpB,WAAO,oBAAC,YAAS,MAAY;AAAA,EAC/B;AACA,MAAI,kBAAkB,IAAI,KAAK,gBAAgB,IAAI,GAAG;AACpD,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACC,GAAI,mBAAmB,EAAE,qBAAqB,CAAA;AAAA,MAAC;AAAA,IAAA;AAAA,EAGtD;AACA,SAAO;AACT;AAIA,SAAS,SAAS,EAAE,QAA8B;;AAChD,QAAM,WAAU,UAAK,cAAL,mBAAgB,WAAW;AAC3C,MAAI,WAAW,KAAK,KAAK;AACvB,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK,KAAK;AAAA,QACV,KAAK,KAAK,YAAY;AAAA,QACtB,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EAGhB;AACA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAM,KAAK;AAAA,MACX,QAAO;AAAA,MACP,KAAI;AAAA,MACJ,WAAU;AAAA,MAEV,UAAA;AAAA,QAAA,oBAAC,MAAA,EAAK,MAAM,QAAQ,WAAU,qBAAoB;AAAA,QAClD,oBAAC,UAAK,WAAU,YACb,eAAK,YAAY,KAAK,aAAa,OAAA,CACtC;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
|
|
@@ -12,6 +12,22 @@ async function getChatSession(sessionsApi, sessionId, fetcher = fetch) {
|
|
|
12
12
|
}
|
|
13
13
|
return parseSession(await response.json());
|
|
14
14
|
}
|
|
15
|
+
async function forkChatSession(sessionsApi, sessionId, fetcher = fetch) {
|
|
16
|
+
if (!sessionId.trim()) {
|
|
17
|
+
throw new Error("A session id is required to fork this chat.");
|
|
18
|
+
}
|
|
19
|
+
const response = await fetcher(
|
|
20
|
+
`${sessionEndpoint(sessionsApi, sessionId)}/fork`,
|
|
21
|
+
{
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: { Accept: "application/json" }
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw await responseError("Fork chat session", response);
|
|
28
|
+
}
|
|
29
|
+
return parseSession(await response.json());
|
|
30
|
+
}
|
|
15
31
|
async function postToolApproval(decision, fetcher = fetch) {
|
|
16
32
|
if (!decision.sessionId.trim()) {
|
|
17
33
|
throw new Error("A session id is required to resolve this tool approval.");
|
|
@@ -59,6 +75,7 @@ async function responseError(label, response) {
|
|
|
59
75
|
`${label} failed with status ${response.status}${detail ? `: ${detail}` : "."}`
|
|
60
76
|
);
|
|
61
77
|
}
|
|
78
|
+
exports.forkChatSession = forkChatSession;
|
|
62
79
|
exports.getChatSession = getChatSession;
|
|
63
80
|
exports.postToolApproval = postToolApproval;
|
|
64
81
|
//# sourceMappingURL=approval.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval.cjs","sources":["../../../src/data/chat/approval.ts"],"sourcesContent":["import type { UIMessage } from \"ai\";\n\nexport type CaptainChatSession = {\n id: string;\n revision?: number;\n messages: UIMessage[];\n};\n\nexport type ToolApprovalDecision = {\n sessionsApi: string;\n sessionId: string;\n approvalId: string;\n approved: boolean;\n reason?: string;\n};\n\ntype SessionFetch = (\n input: RequestInfo | URL,\n init?: RequestInit,\n) => Promise<Response>;\n\nexport async function getChatSession(\n sessionsApi: string,\n sessionId: string,\n fetcher: SessionFetch = fetch,\n): Promise<CaptainChatSession> {\n if (!sessionId.trim()) {\n throw new Error(\"A session id is required to load this chat.\");\n }\n const response = await fetcher(sessionEndpoint(sessionsApi, sessionId), {\n headers: { Accept: \"application/json\" },\n });\n if (!response.ok) {\n throw await responseError(\"Chat session request\", response);\n }\n return parseSession(await response.json());\n}\n\nexport async function postToolApproval(\n decision: ToolApprovalDecision,\n fetcher: SessionFetch = fetch,\n): Promise<CaptainChatSession> {\n if (!decision.sessionId.trim()) {\n throw new Error(\"A session id is required to resolve this tool approval.\");\n }\n const endpoint = `${sessionEndpoint(\n decision.sessionsApi,\n decision.sessionId,\n )}/approvals/${encodeURIComponent(decision.approvalId)}`;\n const response = await fetcher(endpoint, {\n method: \"POST\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(\n decision.reason\n ? { approved: decision.approved, reason: decision.reason }\n : { approved: decision.approved },\n ),\n });\n if (!response.ok) {\n throw await responseError(\"Tool approval\", response);\n }\n return parseSession(await response.json());\n}\n\nfunction sessionEndpoint(sessionsApi: string, sessionId: string): string {\n return `${stripTrailingSlashes(sessionsApi)}/${encodeURIComponent(sessionId)}`;\n}\n\n// Linear scan instead of `.replace(/\\/+$/, \"\")`: the regex backtracks\n// polynomially on caller-supplied base URLs with many trailing slashes.\nfunction stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value[end - 1] === \"/\") end--;\n return value.slice(0, end);\n}\n\nfunction parseSession(value: unknown): CaptainChatSession {\n if (\n typeof value !== \"object\" ||\n value === null ||\n !(\"id\" in value) ||\n typeof value.id !== \"string\"\n ) {\n throw new Error(\"Captain chat session response is invalid.\");\n }\n const messages = \"messages\" in value ? value.messages : [];\n if (!Array.isArray(messages)) {\n throw new Error(\"Captain chat session response is invalid.\");\n }\n return { ...value, messages } as CaptainChatSession;\n}\n\nasync function responseError(\n label: string,\n response: Response,\n): Promise<Error> {\n const detail = (await response.text()).trim();\n return new Error(\n `${label} failed with status ${response.status}${\n detail ? `: ${detail}` : \".\"\n }`,\n );\n}\n"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"approval.cjs","sources":["../../../src/data/chat/approval.ts"],"sourcesContent":["import type { UIMessage } from \"ai\";\nimport type { ChatModelRuntime } from \"./types\";\n\nexport type CaptainChatSession = {\n id: string;\n revision?: number;\n messages: UIMessage[];\n providerSessionId?: string;\n runtime?: ChatModelRuntime;\n forkedFrom?: string;\n};\n\nexport type ToolApprovalDecision = {\n sessionsApi: string;\n sessionId: string;\n approvalId: string;\n approved: boolean;\n reason?: string;\n};\n\ntype SessionFetch = (\n input: RequestInfo | URL,\n init?: RequestInit,\n) => Promise<Response>;\n\nexport async function getChatSession(\n sessionsApi: string,\n sessionId: string,\n fetcher: SessionFetch = fetch,\n): Promise<CaptainChatSession> {\n if (!sessionId.trim()) {\n throw new Error(\"A session id is required to load this chat.\");\n }\n const response = await fetcher(sessionEndpoint(sessionsApi, sessionId), {\n headers: { Accept: \"application/json\" },\n });\n if (!response.ok) {\n throw await responseError(\"Chat session request\", response);\n }\n return parseSession(await response.json());\n}\n\nexport async function forkChatSession(\n sessionsApi: string,\n sessionId: string,\n fetcher: SessionFetch = fetch,\n): Promise<CaptainChatSession> {\n if (!sessionId.trim()) {\n throw new Error(\"A session id is required to fork this chat.\");\n }\n const response = await fetcher(\n `${sessionEndpoint(sessionsApi, sessionId)}/fork`,\n {\n method: \"POST\",\n headers: { Accept: \"application/json\" },\n },\n );\n if (!response.ok) {\n throw await responseError(\"Fork chat session\", response);\n }\n return parseSession(await response.json());\n}\n\nexport async function postToolApproval(\n decision: ToolApprovalDecision,\n fetcher: SessionFetch = fetch,\n): Promise<CaptainChatSession> {\n if (!decision.sessionId.trim()) {\n throw new Error(\"A session id is required to resolve this tool approval.\");\n }\n const endpoint = `${sessionEndpoint(\n decision.sessionsApi,\n decision.sessionId,\n )}/approvals/${encodeURIComponent(decision.approvalId)}`;\n const response = await fetcher(endpoint, {\n method: \"POST\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(\n decision.reason\n ? { approved: decision.approved, reason: decision.reason }\n : { approved: decision.approved },\n ),\n });\n if (!response.ok) {\n throw await responseError(\"Tool approval\", response);\n }\n return parseSession(await response.json());\n}\n\nfunction sessionEndpoint(sessionsApi: string, sessionId: string): string {\n return `${stripTrailingSlashes(sessionsApi)}/${encodeURIComponent(sessionId)}`;\n}\n\n// Linear scan instead of `.replace(/\\/+$/, \"\")`: the regex backtracks\n// polynomially on caller-supplied base URLs with many trailing slashes.\nfunction stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value[end - 1] === \"/\") end--;\n return value.slice(0, end);\n}\n\nfunction parseSession(value: unknown): CaptainChatSession {\n if (\n typeof value !== \"object\" ||\n value === null ||\n !(\"id\" in value) ||\n typeof value.id !== \"string\"\n ) {\n throw new Error(\"Captain chat session response is invalid.\");\n }\n const messages = \"messages\" in value ? value.messages : [];\n if (!Array.isArray(messages)) {\n throw new Error(\"Captain chat session response is invalid.\");\n }\n return { ...value, messages } as CaptainChatSession;\n}\n\nasync function responseError(\n label: string,\n response: Response,\n): Promise<Error> {\n const detail = (await response.text()).trim();\n return new Error(\n `${label} failed with status ${response.status}${\n detail ? `: ${detail}` : \".\"\n }`,\n );\n}\n"],"names":[],"mappings":";;AAyBA,eAAsB,eACpB,aACA,WACA,UAAwB,OACK;AAC7B,MAAI,CAAC,UAAU,QAAQ;AACrB,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AACA,QAAM,WAAW,MAAM,QAAQ,gBAAgB,aAAa,SAAS,GAAG;AAAA,IACtE,SAAS,EAAE,QAAQ,mBAAA;AAAA,EAAmB,CACvC;AACD,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,cAAc,wBAAwB,QAAQ;AAAA,EAC5D;AACA,SAAO,aAAa,MAAM,SAAS,MAAM;AAC3C;AAEA,eAAsB,gBACpB,aACA,WACA,UAAwB,OACK;AAC7B,MAAI,CAAC,UAAU,QAAQ;AACrB,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AACA,QAAM,WAAW,MAAM;AAAA,IACrB,GAAG,gBAAgB,aAAa,SAAS,CAAC;AAAA,IAC1C;AAAA,MACE,QAAQ;AAAA,MACR,SAAS,EAAE,QAAQ,mBAAA;AAAA,IAAmB;AAAA,EACxC;AAEF,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,cAAc,qBAAqB,QAAQ;AAAA,EACzD;AACA,SAAO,aAAa,MAAM,SAAS,MAAM;AAC3C;AAEA,eAAsB,iBACpB,UACA,UAAwB,OACK;AAC7B,MAAI,CAAC,SAAS,UAAU,QAAQ;AAC9B,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC3E;AACA,QAAM,WAAW,GAAG;AAAA,IAClB,SAAS;AAAA,IACT,SAAS;AAAA,EAAA,CACV,cAAc,mBAAmB,SAAS,UAAU,CAAC;AACtD,QAAM,WAAW,MAAM,QAAQ,UAAU;AAAA,IACvC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,QAAQ;AAAA,MACR,gBAAgB;AAAA,IAAA;AAAA,IAElB,MAAM,KAAK;AAAA,MACT,SAAS,SACL,EAAE,UAAU,SAAS,UAAU,QAAQ,SAAS,OAAA,IAChD,EAAE,UAAU,SAAS,SAAA;AAAA,IAAS;AAAA,EACpC,CACD;AACD,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,cAAc,iBAAiB,QAAQ;AAAA,EACrD;AACA,SAAO,aAAa,MAAM,SAAS,MAAM;AAC3C;AAEA,SAAS,gBAAgB,aAAqB,WAA2B;AACvE,SAAO,GAAG,qBAAqB,WAAW,CAAC,IAAI,mBAAmB,SAAS,CAAC;AAC9E;AAIA,SAAS,qBAAqB,OAAuB;AACnD,MAAI,MAAM,MAAM;AAChB,SAAO,MAAM,KAAK,MAAM,MAAM,CAAC,MAAM,IAAK;AAC1C,SAAO,MAAM,MAAM,GAAG,GAAG;AAC3B;AAEA,SAAS,aAAa,OAAoC;AACxD,MACE,OAAO,UAAU,YACjB,UAAU,QACV,EAAE,QAAQ,UACV,OAAO,MAAM,OAAO,UACpB;AACA,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,QAAM,WAAW,cAAc,QAAQ,MAAM,WAAW,CAAA;AACxD,MAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG;AAC5B,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,SAAO,EAAE,GAAG,OAAO,SAAA;AACrB;AAEA,eAAe,cACb,OACA,UACgB;AAChB,QAAM,UAAU,MAAM,SAAS,KAAA,GAAQ,KAAA;AACvC,SAAO,IAAI;AAAA,IACT,GAAG,KAAK,uBAAuB,SAAS,MAAM,GAC5C,SAAS,KAAK,MAAM,KAAK,GAC3B;AAAA,EAAA;AAEJ;;;;"}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { UIMessage } from 'ai';
|
|
2
|
+
import { ChatModelRuntime } from './types';
|
|
2
3
|
export type CaptainChatSession = {
|
|
3
4
|
id: string;
|
|
4
5
|
revision?: number;
|
|
5
6
|
messages: UIMessage[];
|
|
7
|
+
providerSessionId?: string;
|
|
8
|
+
runtime?: ChatModelRuntime;
|
|
9
|
+
forkedFrom?: string;
|
|
6
10
|
};
|
|
7
11
|
export type ToolApprovalDecision = {
|
|
8
12
|
sessionsApi: string;
|
|
@@ -13,6 +17,7 @@ export type ToolApprovalDecision = {
|
|
|
13
17
|
};
|
|
14
18
|
type SessionFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
15
19
|
export declare function getChatSession(sessionsApi: string, sessionId: string, fetcher?: SessionFetch): Promise<CaptainChatSession>;
|
|
20
|
+
export declare function forkChatSession(sessionsApi: string, sessionId: string, fetcher?: SessionFetch): Promise<CaptainChatSession>;
|
|
16
21
|
export declare function postToolApproval(decision: ToolApprovalDecision, fetcher?: SessionFetch): Promise<CaptainChatSession>;
|
|
17
22
|
export {};
|
|
18
23
|
//# sourceMappingURL=approval.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../../../src/data/chat/approval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../../../src/data/chat/approval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,KAAK,YAAY,GAAG,CAClB,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,CAAC,EAAE,WAAW,KACf,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvB,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,YAAoB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAW7B;AAED,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,YAAoB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAe7B;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,oBAAoB,EAC9B,OAAO,GAAE,YAAoB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAwB7B"}
|
|
@@ -10,6 +10,22 @@ async function getChatSession(sessionsApi, sessionId, fetcher = fetch) {
|
|
|
10
10
|
}
|
|
11
11
|
return parseSession(await response.json());
|
|
12
12
|
}
|
|
13
|
+
async function forkChatSession(sessionsApi, sessionId, fetcher = fetch) {
|
|
14
|
+
if (!sessionId.trim()) {
|
|
15
|
+
throw new Error("A session id is required to fork this chat.");
|
|
16
|
+
}
|
|
17
|
+
const response = await fetcher(
|
|
18
|
+
`${sessionEndpoint(sessionsApi, sessionId)}/fork`,
|
|
19
|
+
{
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: { Accept: "application/json" }
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw await responseError("Fork chat session", response);
|
|
26
|
+
}
|
|
27
|
+
return parseSession(await response.json());
|
|
28
|
+
}
|
|
13
29
|
async function postToolApproval(decision, fetcher = fetch) {
|
|
14
30
|
if (!decision.sessionId.trim()) {
|
|
15
31
|
throw new Error("A session id is required to resolve this tool approval.");
|
|
@@ -58,6 +74,7 @@ async function responseError(label, response) {
|
|
|
58
74
|
);
|
|
59
75
|
}
|
|
60
76
|
export {
|
|
77
|
+
forkChatSession,
|
|
61
78
|
getChatSession,
|
|
62
79
|
postToolApproval
|
|
63
80
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval.js","sources":["../../../src/data/chat/approval.ts"],"sourcesContent":["import type { UIMessage } from \"ai\";\n\nexport type CaptainChatSession = {\n id: string;\n revision?: number;\n messages: UIMessage[];\n};\n\nexport type ToolApprovalDecision = {\n sessionsApi: string;\n sessionId: string;\n approvalId: string;\n approved: boolean;\n reason?: string;\n};\n\ntype SessionFetch = (\n input: RequestInfo | URL,\n init?: RequestInit,\n) => Promise<Response>;\n\nexport async function getChatSession(\n sessionsApi: string,\n sessionId: string,\n fetcher: SessionFetch = fetch,\n): Promise<CaptainChatSession> {\n if (!sessionId.trim()) {\n throw new Error(\"A session id is required to load this chat.\");\n }\n const response = await fetcher(sessionEndpoint(sessionsApi, sessionId), {\n headers: { Accept: \"application/json\" },\n });\n if (!response.ok) {\n throw await responseError(\"Chat session request\", response);\n }\n return parseSession(await response.json());\n}\n\nexport async function postToolApproval(\n decision: ToolApprovalDecision,\n fetcher: SessionFetch = fetch,\n): Promise<CaptainChatSession> {\n if (!decision.sessionId.trim()) {\n throw new Error(\"A session id is required to resolve this tool approval.\");\n }\n const endpoint = `${sessionEndpoint(\n decision.sessionsApi,\n decision.sessionId,\n )}/approvals/${encodeURIComponent(decision.approvalId)}`;\n const response = await fetcher(endpoint, {\n method: \"POST\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(\n decision.reason\n ? { approved: decision.approved, reason: decision.reason }\n : { approved: decision.approved },\n ),\n });\n if (!response.ok) {\n throw await responseError(\"Tool approval\", response);\n }\n return parseSession(await response.json());\n}\n\nfunction sessionEndpoint(sessionsApi: string, sessionId: string): string {\n return `${stripTrailingSlashes(sessionsApi)}/${encodeURIComponent(sessionId)}`;\n}\n\n// Linear scan instead of `.replace(/\\/+$/, \"\")`: the regex backtracks\n// polynomially on caller-supplied base URLs with many trailing slashes.\nfunction stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value[end - 1] === \"/\") end--;\n return value.slice(0, end);\n}\n\nfunction parseSession(value: unknown): CaptainChatSession {\n if (\n typeof value !== \"object\" ||\n value === null ||\n !(\"id\" in value) ||\n typeof value.id !== \"string\"\n ) {\n throw new Error(\"Captain chat session response is invalid.\");\n }\n const messages = \"messages\" in value ? value.messages : [];\n if (!Array.isArray(messages)) {\n throw new Error(\"Captain chat session response is invalid.\");\n }\n return { ...value, messages } as CaptainChatSession;\n}\n\nasync function responseError(\n label: string,\n response: Response,\n): Promise<Error> {\n const detail = (await response.text()).trim();\n return new Error(\n `${label} failed with status ${response.status}${\n detail ? `: ${detail}` : \".\"\n }`,\n );\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"approval.js","sources":["../../../src/data/chat/approval.ts"],"sourcesContent":["import type { UIMessage } from \"ai\";\nimport type { ChatModelRuntime } from \"./types\";\n\nexport type CaptainChatSession = {\n id: string;\n revision?: number;\n messages: UIMessage[];\n providerSessionId?: string;\n runtime?: ChatModelRuntime;\n forkedFrom?: string;\n};\n\nexport type ToolApprovalDecision = {\n sessionsApi: string;\n sessionId: string;\n approvalId: string;\n approved: boolean;\n reason?: string;\n};\n\ntype SessionFetch = (\n input: RequestInfo | URL,\n init?: RequestInit,\n) => Promise<Response>;\n\nexport async function getChatSession(\n sessionsApi: string,\n sessionId: string,\n fetcher: SessionFetch = fetch,\n): Promise<CaptainChatSession> {\n if (!sessionId.trim()) {\n throw new Error(\"A session id is required to load this chat.\");\n }\n const response = await fetcher(sessionEndpoint(sessionsApi, sessionId), {\n headers: { Accept: \"application/json\" },\n });\n if (!response.ok) {\n throw await responseError(\"Chat session request\", response);\n }\n return parseSession(await response.json());\n}\n\nexport async function forkChatSession(\n sessionsApi: string,\n sessionId: string,\n fetcher: SessionFetch = fetch,\n): Promise<CaptainChatSession> {\n if (!sessionId.trim()) {\n throw new Error(\"A session id is required to fork this chat.\");\n }\n const response = await fetcher(\n `${sessionEndpoint(sessionsApi, sessionId)}/fork`,\n {\n method: \"POST\",\n headers: { Accept: \"application/json\" },\n },\n );\n if (!response.ok) {\n throw await responseError(\"Fork chat session\", response);\n }\n return parseSession(await response.json());\n}\n\nexport async function postToolApproval(\n decision: ToolApprovalDecision,\n fetcher: SessionFetch = fetch,\n): Promise<CaptainChatSession> {\n if (!decision.sessionId.trim()) {\n throw new Error(\"A session id is required to resolve this tool approval.\");\n }\n const endpoint = `${sessionEndpoint(\n decision.sessionsApi,\n decision.sessionId,\n )}/approvals/${encodeURIComponent(decision.approvalId)}`;\n const response = await fetcher(endpoint, {\n method: \"POST\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(\n decision.reason\n ? { approved: decision.approved, reason: decision.reason }\n : { approved: decision.approved },\n ),\n });\n if (!response.ok) {\n throw await responseError(\"Tool approval\", response);\n }\n return parseSession(await response.json());\n}\n\nfunction sessionEndpoint(sessionsApi: string, sessionId: string): string {\n return `${stripTrailingSlashes(sessionsApi)}/${encodeURIComponent(sessionId)}`;\n}\n\n// Linear scan instead of `.replace(/\\/+$/, \"\")`: the regex backtracks\n// polynomially on caller-supplied base URLs with many trailing slashes.\nfunction stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value[end - 1] === \"/\") end--;\n return value.slice(0, end);\n}\n\nfunction parseSession(value: unknown): CaptainChatSession {\n if (\n typeof value !== \"object\" ||\n value === null ||\n !(\"id\" in value) ||\n typeof value.id !== \"string\"\n ) {\n throw new Error(\"Captain chat session response is invalid.\");\n }\n const messages = \"messages\" in value ? value.messages : [];\n if (!Array.isArray(messages)) {\n throw new Error(\"Captain chat session response is invalid.\");\n }\n return { ...value, messages } as CaptainChatSession;\n}\n\nasync function responseError(\n label: string,\n response: Response,\n): Promise<Error> {\n const detail = (await response.text()).trim();\n return new Error(\n `${label} failed with status ${response.status}${\n detail ? `: ${detail}` : \".\"\n }`,\n );\n}\n"],"names":[],"mappings":"AAyBA,eAAsB,eACpB,aACA,WACA,UAAwB,OACK;AAC7B,MAAI,CAAC,UAAU,QAAQ;AACrB,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AACA,QAAM,WAAW,MAAM,QAAQ,gBAAgB,aAAa,SAAS,GAAG;AAAA,IACtE,SAAS,EAAE,QAAQ,mBAAA;AAAA,EAAmB,CACvC;AACD,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,cAAc,wBAAwB,QAAQ;AAAA,EAC5D;AACA,SAAO,aAAa,MAAM,SAAS,MAAM;AAC3C;AAEA,eAAsB,gBACpB,aACA,WACA,UAAwB,OACK;AAC7B,MAAI,CAAC,UAAU,QAAQ;AACrB,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AACA,QAAM,WAAW,MAAM;AAAA,IACrB,GAAG,gBAAgB,aAAa,SAAS,CAAC;AAAA,IAC1C;AAAA,MACE,QAAQ;AAAA,MACR,SAAS,EAAE,QAAQ,mBAAA;AAAA,IAAmB;AAAA,EACxC;AAEF,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,cAAc,qBAAqB,QAAQ;AAAA,EACzD;AACA,SAAO,aAAa,MAAM,SAAS,MAAM;AAC3C;AAEA,eAAsB,iBACpB,UACA,UAAwB,OACK;AAC7B,MAAI,CAAC,SAAS,UAAU,QAAQ;AAC9B,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC3E;AACA,QAAM,WAAW,GAAG;AAAA,IAClB,SAAS;AAAA,IACT,SAAS;AAAA,EAAA,CACV,cAAc,mBAAmB,SAAS,UAAU,CAAC;AACtD,QAAM,WAAW,MAAM,QAAQ,UAAU;AAAA,IACvC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,QAAQ;AAAA,MACR,gBAAgB;AAAA,IAAA;AAAA,IAElB,MAAM,KAAK;AAAA,MACT,SAAS,SACL,EAAE,UAAU,SAAS,UAAU,QAAQ,SAAS,OAAA,IAChD,EAAE,UAAU,SAAS,SAAA;AAAA,IAAS;AAAA,EACpC,CACD;AACD,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,cAAc,iBAAiB,QAAQ;AAAA,EACrD;AACA,SAAO,aAAa,MAAM,SAAS,MAAM;AAC3C;AAEA,SAAS,gBAAgB,aAAqB,WAA2B;AACvE,SAAO,GAAG,qBAAqB,WAAW,CAAC,IAAI,mBAAmB,SAAS,CAAC;AAC9E;AAIA,SAAS,qBAAqB,OAAuB;AACnD,MAAI,MAAM,MAAM;AAChB,SAAO,MAAM,KAAK,MAAM,MAAM,CAAC,MAAM,IAAK;AAC1C,SAAO,MAAM,MAAM,GAAG,GAAG;AAC3B;AAEA,SAAS,aAAa,OAAoC;AACxD,MACE,OAAO,UAAU,YACjB,UAAU,QACV,EAAE,QAAQ,UACV,OAAO,MAAM,OAAO,UACpB;AACA,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,QAAM,WAAW,cAAc,QAAQ,MAAM,WAAW,CAAA;AACxD,MAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG;AAC5B,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,SAAO,EAAE,GAAG,OAAO,SAAA;AACrB;AAEA,eAAe,cACb,OACA,UACgB;AAChB,QAAM,UAAU,MAAM,SAAS,KAAA,GAAQ,KAAA;AACvC,SAAO,IAAI;AAAA,IACT,GAAG,KAAK,uBAAuB,SAAS,MAAM,GAC5C,SAAS,KAAK,MAAM,KAAK,GAC3B;AAAA,EAAA;AAEJ;"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const FORK_SEED_PART_TYPE = "data-fork-seed";
|
|
4
|
+
function isForkSeedMessage(message) {
|
|
5
|
+
return message.parts.some((part) => part.type === FORK_SEED_PART_TYPE);
|
|
6
|
+
}
|
|
7
|
+
function forkSeedProvenance(message) {
|
|
8
|
+
const marker = message.parts.find(
|
|
9
|
+
(part) => part.type === FORK_SEED_PART_TYPE
|
|
10
|
+
);
|
|
11
|
+
if (!marker || typeof marker.data !== "object" || marker.data === null)
|
|
12
|
+
return {};
|
|
13
|
+
const data = marker.data;
|
|
14
|
+
return {
|
|
15
|
+
...typeof data.forkedFrom === "string" ? { forkedFrom: data.forkedFrom } : {},
|
|
16
|
+
...typeof data.title === "string" ? { title: data.title } : {}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
exports.FORK_SEED_PART_TYPE = FORK_SEED_PART_TYPE;
|
|
20
|
+
exports.forkSeedProvenance = forkSeedProvenance;
|
|
21
|
+
exports.isForkSeedMessage = isForkSeedMessage;
|
|
22
|
+
//# sourceMappingURL=fork-seed.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fork-seed.cjs","sources":["../../../src/data/chat/fork-seed.ts"],"sourcesContent":["import type { UIMessage } from \"ai\";\n\nexport const FORK_SEED_PART_TYPE = \"data-fork-seed\";\n\nexport function isForkSeedMessage(message: Pick<UIMessage, \"parts\">): boolean {\n return message.parts.some((part) => part.type === FORK_SEED_PART_TYPE);\n}\n\nexport function forkSeedProvenance(message: Pick<UIMessage, \"parts\">): {\n forkedFrom?: string;\n title?: string;\n} {\n const marker = message.parts.find(\n (part) => part.type === FORK_SEED_PART_TYPE,\n ) as { data?: unknown } | undefined;\n if (!marker || typeof marker.data !== \"object\" || marker.data === null)\n return {};\n const data = marker.data as Record<string, unknown>;\n return {\n ...(typeof data.forkedFrom === \"string\"\n ? { forkedFrom: data.forkedFrom }\n : {}),\n ...(typeof data.title === \"string\" ? { title: data.title } : {}),\n };\n}\n"],"names":[],"mappings":";;AAEO,MAAM,sBAAsB;AAE5B,SAAS,kBAAkB,SAA4C;AAC5E,SAAO,QAAQ,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,mBAAmB;AACvE;AAEO,SAAS,mBAAmB,SAGjC;AACA,QAAM,SAAS,QAAQ,MAAM;AAAA,IAC3B,CAAC,SAAS,KAAK,SAAS;AAAA,EAAA;AAE1B,MAAI,CAAC,UAAU,OAAO,OAAO,SAAS,YAAY,OAAO,SAAS;AAChE,WAAO,CAAA;AACT,QAAM,OAAO,OAAO;AACpB,SAAO;AAAA,IACL,GAAI,OAAO,KAAK,eAAe,WAC3B,EAAE,YAAY,KAAK,WAAA,IACnB,CAAA;AAAA,IACJ,GAAI,OAAO,KAAK,UAAU,WAAW,EAAE,OAAO,KAAK,UAAU,CAAA;AAAA,EAAC;AAElE;;;;"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { UIMessage } from 'ai';
|
|
2
|
+
export declare const FORK_SEED_PART_TYPE = "data-fork-seed";
|
|
3
|
+
export declare function isForkSeedMessage(message: Pick<UIMessage, "parts">): boolean;
|
|
4
|
+
export declare function forkSeedProvenance(message: Pick<UIMessage, "parts">): {
|
|
5
|
+
forkedFrom?: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=fork-seed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fork-seed.d.ts","sourceRoot":"","sources":["../../../src/data/chat/fork-seed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAEpC,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAEpD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,OAAO,CAE5E;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAaA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const FORK_SEED_PART_TYPE = "data-fork-seed";
|
|
2
|
+
function isForkSeedMessage(message) {
|
|
3
|
+
return message.parts.some((part) => part.type === FORK_SEED_PART_TYPE);
|
|
4
|
+
}
|
|
5
|
+
function forkSeedProvenance(message) {
|
|
6
|
+
const marker = message.parts.find(
|
|
7
|
+
(part) => part.type === FORK_SEED_PART_TYPE
|
|
8
|
+
);
|
|
9
|
+
if (!marker || typeof marker.data !== "object" || marker.data === null)
|
|
10
|
+
return {};
|
|
11
|
+
const data = marker.data;
|
|
12
|
+
return {
|
|
13
|
+
...typeof data.forkedFrom === "string" ? { forkedFrom: data.forkedFrom } : {},
|
|
14
|
+
...typeof data.title === "string" ? { title: data.title } : {}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
FORK_SEED_PART_TYPE,
|
|
19
|
+
forkSeedProvenance,
|
|
20
|
+
isForkSeedMessage
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=fork-seed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fork-seed.js","sources":["../../../src/data/chat/fork-seed.ts"],"sourcesContent":["import type { UIMessage } from \"ai\";\n\nexport const FORK_SEED_PART_TYPE = \"data-fork-seed\";\n\nexport function isForkSeedMessage(message: Pick<UIMessage, \"parts\">): boolean {\n return message.parts.some((part) => part.type === FORK_SEED_PART_TYPE);\n}\n\nexport function forkSeedProvenance(message: Pick<UIMessage, \"parts\">): {\n forkedFrom?: string;\n title?: string;\n} {\n const marker = message.parts.find(\n (part) => part.type === FORK_SEED_PART_TYPE,\n ) as { data?: unknown } | undefined;\n if (!marker || typeof marker.data !== \"object\" || marker.data === null)\n return {};\n const data = marker.data as Record<string, unknown>;\n return {\n ...(typeof data.forkedFrom === \"string\"\n ? { forkedFrom: data.forkedFrom }\n : {}),\n ...(typeof data.title === \"string\" ? { title: data.title } : {}),\n };\n}\n"],"names":[],"mappings":"AAEO,MAAM,sBAAsB;AAE5B,SAAS,kBAAkB,SAA4C;AAC5E,SAAO,QAAQ,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,mBAAmB;AACvE;AAEO,SAAS,mBAAmB,SAGjC;AACA,QAAM,SAAS,QAAQ,MAAM;AAAA,IAC3B,CAAC,SAAS,KAAK,SAAS;AAAA,EAAA;AAE1B,MAAI,CAAC,UAAU,OAAO,OAAO,SAAS,YAAY,OAAO,SAAS;AAChE,WAAO,CAAA;AACT,QAAM,OAAO,OAAO;AACpB,SAAO;AAAA,IACL,GAAI,OAAO,KAAK,eAAe,WAC3B,EAAE,YAAY,KAAK,WAAA,IACnB,CAAA;AAAA,IACJ,GAAI,OAAO,KAAK,UAAU,WAAW,EAAE,OAAO,KAAK,UAAU,CAAA;AAAA,EAAC;AAElE;"}
|