@arbidocs/react 0.3.103 → 0.3.105

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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import React__default, { createContext, forwardRef, useMemo, useContext, useState, useCallback, useEffect, useRef } from 'react';
2
+ import React__default, { createContext, forwardRef, useMemo, useContext, useState, useRef, useCallback, useEffect } from 'react';
3
3
  import { Arbi, resolveCitations, summarizeCitations } from '@arbidocs/sdk/browser';
4
4
  export { Arbi, ArbiError } from '@arbidocs/sdk/browser';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -11,7 +11,12 @@ import * as AvatarPrimitive from '@radix-ui/react-avatar';
11
11
  import { cva } from 'class-variance-authority';
12
12
  import { Slot } from '@radix-ui/react-slot';
13
13
  import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
14
- import { MoreVertical, Eye, FolderOpen, Bot, Calendar, Search, Folder, StickyNote, Hash, List, SquareCheckBig, Package } from 'lucide-react';
14
+ import { diffWords } from 'diff';
15
+ import ReactMarkdown from 'react-markdown';
16
+ import remarkGfm from 'remark-gfm';
17
+ import rehypeRaw from 'rehype-raw';
18
+ import { MoreVertical, Loader2, AlertTriangle, ShieldCheck, Check, Copy, Sparkles, FileText, GitCompareArrows, Download, Search, CornerDownLeft, Send, Tags, Eye, FolderOpen, Bot, Calendar, Folder, StickyNote, Hash, List, SquareCheckBig, Package } from 'lucide-react';
19
+ import { AnimatePresence, motion } from 'framer-motion';
15
20
  import { AgGridReact } from 'ag-grid-react';
16
21
  import { ModuleRegistry, AllCommunityModule } from 'ag-grid-community';
17
22
  import { create } from 'zustand';
@@ -1392,6 +1397,7 @@ var badgeVariants = cva(
1392
1397
  variants: {
1393
1398
  variant: {
1394
1399
  default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
1400
+ accent: "border-transparent bg-ai-accent-soft text-ai-accent",
1395
1401
  secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
1396
1402
  destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
1397
1403
  outline: "text-foreground"
@@ -1411,6 +1417,7 @@ var buttonVariants = cva(
1411
1417
  variants: {
1412
1418
  variant: {
1413
1419
  default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
1420
+ accent: "bg-ai-accent text-ai-accent-foreground shadow hover:bg-ai-accent/90",
1414
1421
  destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
1415
1422
  outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
1416
1423
  secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
@@ -1453,6 +1460,748 @@ var HoverCardContent = React.forwardRef(({ className, align = "center", sideOffs
1453
1460
  }
1454
1461
  ) }));
1455
1462
  HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
1463
+ function Card({ className, ...props }) {
1464
+ return /* @__PURE__ */ jsx(
1465
+ "div",
1466
+ {
1467
+ "data-slot": "card",
1468
+ className: cn(
1469
+ "rounded-xl border border-border bg-card text-card-foreground shadow-sm",
1470
+ className
1471
+ ),
1472
+ ...props
1473
+ }
1474
+ );
1475
+ }
1476
+ function CardHeader({ className, ...props }) {
1477
+ return /* @__PURE__ */ jsx(
1478
+ "div",
1479
+ {
1480
+ "data-slot": "card-header",
1481
+ className: cn("flex flex-col gap-1.5 p-6", className),
1482
+ ...props
1483
+ }
1484
+ );
1485
+ }
1486
+ function CardTitle({ className, ...props }) {
1487
+ return /* @__PURE__ */ jsx(
1488
+ "div",
1489
+ {
1490
+ "data-slot": "card-title",
1491
+ className: cn("text-lg font-semibold leading-none tracking-tight", className),
1492
+ ...props
1493
+ }
1494
+ );
1495
+ }
1496
+ function CardDescription({ className, ...props }) {
1497
+ return /* @__PURE__ */ jsx(
1498
+ "div",
1499
+ {
1500
+ "data-slot": "card-description",
1501
+ className: cn("text-sm text-muted-foreground", className),
1502
+ ...props
1503
+ }
1504
+ );
1505
+ }
1506
+ function CardContent({ className, ...props }) {
1507
+ return /* @__PURE__ */ jsx("div", { "data-slot": "card-content", className: cn("p-6 pt-0", className), ...props });
1508
+ }
1509
+ function CardFooter({ className, ...props }) {
1510
+ return /* @__PURE__ */ jsx(
1511
+ "div",
1512
+ {
1513
+ "data-slot": "card-footer",
1514
+ className: cn("flex items-center p-6 pt-0", className),
1515
+ ...props
1516
+ }
1517
+ );
1518
+ }
1519
+ function Input({ className, type, ...props }) {
1520
+ return /* @__PURE__ */ jsx(
1521
+ "input",
1522
+ {
1523
+ type,
1524
+ "data-slot": "input",
1525
+ className: cn(
1526
+ "flex h-9 w-full rounded-md border border-input bg-card px-3 py-1 text-sm shadow-sm transition-colors",
1527
+ "placeholder:text-muted-foreground/70",
1528
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:border-ring/60",
1529
+ "disabled:cursor-not-allowed disabled:opacity-50 file:border-0 file:bg-transparent file:text-sm file:font-medium",
1530
+ className
1531
+ ),
1532
+ ...props
1533
+ }
1534
+ );
1535
+ }
1536
+ function useWorkspaceDocs(workspaceId) {
1537
+ const { data: docs = [], isLoading, isError } = useDocuments(workspaceId);
1538
+ const docIds = useMemo(
1539
+ () => docs.map((d) => d.external_id).filter((id) => !!id),
1540
+ [docs]
1541
+ );
1542
+ return { wsId: workspaceId, docs, docIds, isLoading, isError, ready: docIds.length > 0 };
1543
+ }
1544
+ function useAiTask() {
1545
+ const mutation = useAssistantQuery();
1546
+ const [status, setStatus] = useState("idle");
1547
+ const [text, setText] = useState("");
1548
+ const [stepCount, setStepCount] = useState(0);
1549
+ const [error, setError] = useState(null);
1550
+ const [responseId, setResponseId] = useState(null);
1551
+ const [artifacts, setArtifacts] = useState([]);
1552
+ const bufferRef = useRef("");
1553
+ const artifactsRef = useRef(/* @__PURE__ */ new Map());
1554
+ const reset = useCallback(() => {
1555
+ setStatus("idle");
1556
+ setText("");
1557
+ setStepCount(0);
1558
+ setError(null);
1559
+ setResponseId(null);
1560
+ setArtifacts([]);
1561
+ artifactsRef.current = /* @__PURE__ */ new Map();
1562
+ bufferRef.current = "";
1563
+ }, []);
1564
+ const run = useCallback(
1565
+ async (question, docIds, opts) => {
1566
+ setStatus("running");
1567
+ setText("");
1568
+ setStepCount(0);
1569
+ setError(null);
1570
+ setArtifacts([]);
1571
+ artifactsRef.current = /* @__PURE__ */ new Map();
1572
+ bufferRef.current = "";
1573
+ let streamError = null;
1574
+ try {
1575
+ const result = await mutation.mutateAsync({
1576
+ question,
1577
+ docIds,
1578
+ previousResponseId: opts?.previousResponseId ?? void 0,
1579
+ model: opts?.model,
1580
+ onToken: (c) => {
1581
+ bufferRef.current += c;
1582
+ setText(bufferRef.current);
1583
+ },
1584
+ onAgentStep: () => setStepCount((n) => n + 1),
1585
+ onArtifact: (a) => {
1586
+ artifactsRef.current.set(a.doc_ext_id, a);
1587
+ setArtifacts([...artifactsRef.current.values()]);
1588
+ },
1589
+ onError: (m) => {
1590
+ streamError = m;
1591
+ }
1592
+ });
1593
+ const finalText = result.text || bufferRef.current;
1594
+ setText(finalText);
1595
+ setResponseId(result.assistantMessageExtId);
1596
+ setStepCount(result.agentSteps.length || 0);
1597
+ if (result.artifacts?.length) {
1598
+ for (const a of result.artifacts) artifactsRef.current.set(a.doc_ext_id, a);
1599
+ setArtifacts([...artifactsRef.current.values()]);
1600
+ }
1601
+ const err = streamError || result.errors[0] || null;
1602
+ if (err && !finalText) {
1603
+ setError(err);
1604
+ setStatus("error");
1605
+ } else {
1606
+ setStatus("done");
1607
+ }
1608
+ return result;
1609
+ } catch (e) {
1610
+ setError(e instanceof Error ? e.message : "The assistant request failed.");
1611
+ setStatus("error");
1612
+ return null;
1613
+ }
1614
+ },
1615
+ [mutation]
1616
+ );
1617
+ return {
1618
+ status,
1619
+ text,
1620
+ stepCount,
1621
+ error,
1622
+ responseId,
1623
+ artifacts,
1624
+ run,
1625
+ reset,
1626
+ isRunning: status === "running",
1627
+ isDone: status === "done"
1628
+ };
1629
+ }
1630
+ function useSemanticSearch(docIds) {
1631
+ const arbi = useArbi();
1632
+ const [results, setResults] = useState([]);
1633
+ const [status, setStatus] = useState("idle");
1634
+ const [error, setError] = useState(null);
1635
+ const [query, setQuery] = useState("");
1636
+ const search = useCallback(
1637
+ async (q, mode = "hybrid") => {
1638
+ const trimmed = q.trim();
1639
+ setQuery(trimmed);
1640
+ if (!trimmed || docIds.length === 0) {
1641
+ setResults([]);
1642
+ setStatus("idle");
1643
+ return;
1644
+ }
1645
+ setStatus("running");
1646
+ setError(null);
1647
+ try {
1648
+ const res = await arbi.assistant.retrieve(trimmed, docIds, { searchMode: mode });
1649
+ const byTool = res.retrieval_chunk?.tool_responses ?? {};
1650
+ const chunks = Object.values(byTool).flat().map(
1651
+ (c) => ({
1652
+ docId: c.metadata.doc_ext_id,
1653
+ docTitle: c.metadata.doc_title,
1654
+ page: c.metadata.page_number,
1655
+ score: c.metadata.rerank_score ?? c.metadata.score,
1656
+ content: c.content
1657
+ })
1658
+ ).sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
1659
+ setResults(chunks);
1660
+ setStatus("done");
1661
+ } catch (e) {
1662
+ setError(e instanceof Error ? e.message : "Search failed.");
1663
+ setStatus("error");
1664
+ }
1665
+ },
1666
+ [arbi, docIds]
1667
+ );
1668
+ return { query, results, status, error, search, isRunning: status === "running" };
1669
+ }
1670
+ function toRedlineMarkdown(original, revised) {
1671
+ const parts = diffWords(original ?? "", revised ?? "");
1672
+ return parts.map((p) => {
1673
+ if (p.added) return `<ins>${p.value}</ins>`;
1674
+ if (p.removed) return `<del>${p.value}</del>`;
1675
+ return p.value;
1676
+ }).join("");
1677
+ }
1678
+ function redlineStats(original, revised) {
1679
+ let added = 0;
1680
+ let removed = 0;
1681
+ for (const p of diffWords(original ?? "", revised ?? "")) {
1682
+ const words = p.value.trim() ? p.value.trim().split(/\s+/).length : 0;
1683
+ if (p.added) added += words;
1684
+ else if (p.removed) removed += words;
1685
+ }
1686
+ return { added, removed };
1687
+ }
1688
+ function textToArtifact(title, content, opts) {
1689
+ return {
1690
+ type: "arbi.artifact",
1691
+ doc_ext_id: opts?.id ?? `local-${title.replace(/\W+/g, "-").toLowerCase()}`,
1692
+ title,
1693
+ mode: opts?.mode ?? "markdown",
1694
+ content,
1695
+ is_complete: opts?.isComplete ?? true,
1696
+ version: opts?.version ?? 1,
1697
+ message_ext_id: null,
1698
+ t: null
1699
+ };
1700
+ }
1701
+ function AiMarkdown({ children, className }) {
1702
+ return /* @__PURE__ */ jsx(
1703
+ "div",
1704
+ {
1705
+ className: cn("text-sm leading-relaxed text-foreground", className),
1706
+ "data-testid": "ai-markdown",
1707
+ children: /* @__PURE__ */ jsx(
1708
+ ReactMarkdown,
1709
+ {
1710
+ remarkPlugins: [remarkGfm],
1711
+ rehypePlugins: [rehypeRaw],
1712
+ components: {
1713
+ h1: (p) => /* @__PURE__ */ jsx("h1", { className: "mb-2 mt-4 text-lg font-semibold text-foreground first:mt-0", ...p }),
1714
+ h2: (p) => /* @__PURE__ */ jsx("h2", { className: "mb-2 mt-4 text-base font-semibold text-foreground first:mt-0", ...p }),
1715
+ h3: (p) => /* @__PURE__ */ jsx("h3", { className: "mb-1.5 mt-3 text-sm font-semibold text-foreground first:mt-0", ...p }),
1716
+ p: (p) => /* @__PURE__ */ jsx("p", { className: "mb-3 last:mb-0", ...p }),
1717
+ ul: (p) => /* @__PURE__ */ jsx("ul", { className: "mb-3 list-disc space-y-1 pl-5 last:mb-0", ...p }),
1718
+ ol: (p) => /* @__PURE__ */ jsx("ol", { className: "mb-3 list-decimal space-y-1 pl-5 last:mb-0", ...p }),
1719
+ li: (p) => /* @__PURE__ */ jsx("li", { className: "pl-1", ...p }),
1720
+ strong: (p) => /* @__PURE__ */ jsx("strong", { className: "font-semibold text-foreground", ...p }),
1721
+ a: (p) => /* @__PURE__ */ jsx("a", { className: "text-ai-accent underline underline-offset-2", ...p }),
1722
+ // Redline marks: deletions in red strike, <ins>/<mark> additions accented.
1723
+ del: (p) => /* @__PURE__ */ jsx(
1724
+ "del",
1725
+ {
1726
+ className: "rounded bg-destructive/10 text-destructive decoration-destructive/60",
1727
+ ...p
1728
+ }
1729
+ ),
1730
+ ins: (p) => /* @__PURE__ */ jsx("ins", { className: "rounded bg-ai-accent-soft px-0.5 text-ai-accent no-underline", ...p }),
1731
+ mark: (p) => /* @__PURE__ */ jsx("mark", { className: "rounded bg-ai-accent-soft px-0.5 text-ai-accent", ...p }),
1732
+ code: (p) => /* @__PURE__ */ jsx("code", { className: "rounded bg-secondary px-1 py-0.5 font-mono text-[0.8em]", ...p }),
1733
+ blockquote: (p) => /* @__PURE__ */ jsx(
1734
+ "blockquote",
1735
+ {
1736
+ className: "my-3 border-l-2 border-ai-accent/40 pl-3 italic text-muted-foreground",
1737
+ ...p
1738
+ }
1739
+ ),
1740
+ table: (p) => /* @__PURE__ */ jsx("div", { className: "my-3 overflow-x-auto rounded-lg border border-border", children: /* @__PURE__ */ jsx("table", { className: "w-full border-collapse text-xs", ...p }) }),
1741
+ thead: (p) => /* @__PURE__ */ jsx("thead", { className: "bg-secondary/60", ...p }),
1742
+ th: (p) => /* @__PURE__ */ jsx(
1743
+ "th",
1744
+ {
1745
+ className: "border-b border-border px-3 py-2 text-left font-semibold text-foreground",
1746
+ ...p
1747
+ }
1748
+ ),
1749
+ td: (p) => /* @__PURE__ */ jsx(
1750
+ "td",
1751
+ {
1752
+ className: "border-b border-border px-3 py-2 align-top text-muted-foreground",
1753
+ ...p
1754
+ }
1755
+ )
1756
+ },
1757
+ children
1758
+ }
1759
+ )
1760
+ }
1761
+ );
1762
+ }
1763
+ function AiAnswer({
1764
+ status,
1765
+ text,
1766
+ stepCount,
1767
+ error,
1768
+ className,
1769
+ emptyHint,
1770
+ groundedNote = "Grounded in the live workspace \xB7 verify citations before relying on it",
1771
+ runningHint = "Reading the documents\u2026",
1772
+ testId = "ai-answer"
1773
+ }) {
1774
+ const [copied, setCopied] = useState(false);
1775
+ const running = status === "running";
1776
+ const hasText = text.trim().length > 0;
1777
+ const copy = async () => {
1778
+ await navigator.clipboard.writeText(text);
1779
+ setCopied(true);
1780
+ setTimeout(() => setCopied(false), 1600);
1781
+ };
1782
+ if (status === "idle" && !hasText) {
1783
+ return emptyHint ? /* @__PURE__ */ jsx("p", { className: cn("text-sm text-muted-foreground", className), "data-testid": `${testId}-empty`, children: emptyHint }) : null;
1784
+ }
1785
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), "data-testid": testId, "data-status": status, children: [
1786
+ running && /* @__PURE__ */ jsxs(
1787
+ "div",
1788
+ {
1789
+ className: "flex items-center gap-2 text-xs font-medium text-ai-accent",
1790
+ "data-testid": `${testId}-thinking`,
1791
+ children: [
1792
+ /* @__PURE__ */ jsx(Loader2, { className: "size-3.5 animate-spin" }),
1793
+ stepCount && stepCount > 0 ? `Working \u2014 ${stepCount} step${stepCount === 1 ? "" : "s"}\u2026` : "Thinking\u2026"
1794
+ ]
1795
+ }
1796
+ ),
1797
+ status === "error" && !hasText && /* @__PURE__ */ jsxs(
1798
+ "div",
1799
+ {
1800
+ className: "flex items-start gap-2 rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive",
1801
+ "data-testid": `${testId}-error`,
1802
+ children: [
1803
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 size-4 shrink-0" }),
1804
+ /* @__PURE__ */ jsx("span", { children: error ?? "The assistant request failed." })
1805
+ ]
1806
+ }
1807
+ ),
1808
+ hasText && /* @__PURE__ */ jsxs(Fragment, { children: [
1809
+ /* @__PURE__ */ jsx(AiMarkdown, { children: text }),
1810
+ !running && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-border pt-2.5", children: [
1811
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 text-xs text-muted-foreground", children: [
1812
+ /* @__PURE__ */ jsx(ShieldCheck, { className: "size-3.5 text-ai-accent" }),
1813
+ groundedNote
1814
+ ] }),
1815
+ /* @__PURE__ */ jsxs(
1816
+ "button",
1817
+ {
1818
+ type: "button",
1819
+ onClick: copy,
1820
+ className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground hover:text-foreground",
1821
+ "data-testid": `${testId}-copy`,
1822
+ children: [
1823
+ copied ? /* @__PURE__ */ jsx(Check, { className: "size-3.5 text-ai-accent" }) : /* @__PURE__ */ jsx(Copy, { className: "size-3.5" }),
1824
+ copied ? "Copied" : "Copy"
1825
+ ]
1826
+ }
1827
+ )
1828
+ ] })
1829
+ ] }),
1830
+ running && !hasText && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-sm text-muted-foreground", children: [
1831
+ /* @__PURE__ */ jsx(Sparkles, { className: "size-4 animate-pulse text-ai-accent" }),
1832
+ " ",
1833
+ runningHint
1834
+ ] })
1835
+ ] });
1836
+ }
1837
+ function AiActionCard({
1838
+ icon: Icon = Sparkles,
1839
+ title,
1840
+ description,
1841
+ prompt,
1842
+ docIds,
1843
+ runLabel = "Run",
1844
+ noDocsHint = "Connect to the workspace to run this.",
1845
+ disabled,
1846
+ testId
1847
+ }) {
1848
+ const task = useAiTask();
1849
+ const noDocs = docIds.length === 0;
1850
+ const onRun = () => {
1851
+ const p = typeof prompt === "function" ? prompt() : prompt;
1852
+ void task.run(p, docIds);
1853
+ };
1854
+ return /* @__PURE__ */ jsxs(Card, { className: "p-5", "data-testid": testId, children: [
1855
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
1856
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
1857
+ /* @__PURE__ */ jsx("div", { className: "flex size-9 shrink-0 items-center justify-center rounded-lg bg-ai-accent-soft text-ai-accent", children: /* @__PURE__ */ jsx(Icon, { className: "size-4" }) }),
1858
+ /* @__PURE__ */ jsxs("div", { children: [
1859
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-foreground", children: title }),
1860
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs leading-relaxed text-muted-foreground", children: description })
1861
+ ] })
1862
+ ] }),
1863
+ /* @__PURE__ */ jsxs(
1864
+ Button,
1865
+ {
1866
+ variant: task.isDone ? "outline" : "accent",
1867
+ size: "sm",
1868
+ onClick: onRun,
1869
+ disabled: disabled || noDocs || task.isRunning,
1870
+ "data-testid": testId && `${testId}-run`,
1871
+ children: [
1872
+ /* @__PURE__ */ jsx(Sparkles, { className: "size-3.5" }),
1873
+ task.isRunning ? "Running\u2026" : task.isDone ? "Re-run" : runLabel
1874
+ ]
1875
+ }
1876
+ )
1877
+ ] }),
1878
+ (task.status !== "idle" || noDocs) && /* @__PURE__ */ jsx("div", { className: "mt-4", children: noDocs ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: noDocsHint }) : /* @__PURE__ */ jsx(
1879
+ AiAnswer,
1880
+ {
1881
+ status: task.status,
1882
+ text: task.text,
1883
+ stepCount: task.stepCount,
1884
+ error: task.error,
1885
+ testId: testId && `${testId}-answer`
1886
+ }
1887
+ ) })
1888
+ ] });
1889
+ }
1890
+ function ArtifactCanvas({
1891
+ artifacts,
1892
+ className,
1893
+ emptyHint = "Draft artifacts produced by the agent will open here.",
1894
+ busy = false,
1895
+ busyHint = "The agent is working through the documents\u2026",
1896
+ stepCount,
1897
+ error = null
1898
+ }) {
1899
+ const [activeId, setActiveId] = useState(null);
1900
+ const [copied, setCopied] = useState(false);
1901
+ useEffect(() => {
1902
+ if (artifacts.length && (!activeId || !artifacts.some((a) => a.doc_ext_id === activeId))) {
1903
+ setActiveId(artifacts[artifacts.length - 1].doc_ext_id);
1904
+ }
1905
+ }, [artifacts, activeId]);
1906
+ if (artifacts.length === 0) {
1907
+ if (error && !busy) {
1908
+ return /* @__PURE__ */ jsxs(
1909
+ "div",
1910
+ {
1911
+ className: cn(
1912
+ "flex h-full min-h-[280px] flex-col items-center justify-center rounded-xl border border-destructive/30 bg-destructive/5 p-8 text-center",
1913
+ className
1914
+ ),
1915
+ "data-testid": "artifact-canvas-error",
1916
+ children: [
1917
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "size-8 text-destructive/70" }),
1918
+ /* @__PURE__ */ jsx("p", { className: "mt-3 max-w-md text-sm text-foreground", children: error })
1919
+ ]
1920
+ }
1921
+ );
1922
+ }
1923
+ if (busy) {
1924
+ return /* @__PURE__ */ jsxs(
1925
+ "div",
1926
+ {
1927
+ className: cn(
1928
+ "flex h-full min-h-[280px] flex-col items-center justify-center rounded-xl border border-border bg-ai-surface/40 p-8 text-center",
1929
+ className
1930
+ ),
1931
+ "data-testid": "artifact-canvas-busy",
1932
+ children: [
1933
+ /* @__PURE__ */ jsx(Loader2, { className: "size-8 animate-spin text-ai-accent" }),
1934
+ /* @__PURE__ */ jsx("p", { className: "mt-4 max-w-xs text-sm font-medium text-foreground", children: busyHint }),
1935
+ typeof stepCount === "number" && stepCount > 0 && /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-muted-foreground", "data-testid": "artifact-canvas-steps", children: [
1936
+ stepCount,
1937
+ " ",
1938
+ stepCount === 1 ? "step" : "steps",
1939
+ " so far"
1940
+ ] })
1941
+ ]
1942
+ }
1943
+ );
1944
+ }
1945
+ return /* @__PURE__ */ jsxs(
1946
+ "div",
1947
+ {
1948
+ className: cn(
1949
+ "flex h-full min-h-[280px] flex-col items-center justify-center rounded-xl border border-dashed border-border bg-secondary/30 p-8 text-center",
1950
+ className
1951
+ ),
1952
+ "data-testid": "artifact-canvas-empty",
1953
+ children: [
1954
+ /* @__PURE__ */ jsx(FileText, { className: "size-8 text-muted-foreground/50" }),
1955
+ /* @__PURE__ */ jsx("p", { className: "mt-3 max-w-xs text-sm text-muted-foreground", children: emptyHint })
1956
+ ]
1957
+ }
1958
+ );
1959
+ }
1960
+ const active = artifacts.find((a) => a.doc_ext_id === activeId) ?? artifacts[artifacts.length - 1];
1961
+ const isRedline = /~~|<ins|<del|<mark/i.test(active.content);
1962
+ const copy = async () => {
1963
+ await navigator.clipboard.writeText(active.content);
1964
+ setCopied(true);
1965
+ setTimeout(() => setCopied(false), 1600);
1966
+ };
1967
+ const download = () => {
1968
+ const ext = active.mode === "html" ? "html" : active.mode === "text" ? "txt" : "md";
1969
+ const blob = new Blob([active.content], { type: "text/plain" });
1970
+ const url = URL.createObjectURL(blob);
1971
+ const a = document.createElement("a");
1972
+ a.href = url;
1973
+ a.download = `${active.title.replace(/[^\w.-]+/g, "_") || "artifact"}.${ext}`;
1974
+ a.click();
1975
+ URL.revokeObjectURL(url);
1976
+ };
1977
+ return /* @__PURE__ */ jsxs(
1978
+ "div",
1979
+ {
1980
+ className: cn(
1981
+ "flex h-full flex-col overflow-hidden rounded-xl border border-border bg-card",
1982
+ className
1983
+ ),
1984
+ "data-testid": "artifact-canvas",
1985
+ children: [
1986
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-border bg-secondary/40 px-4 py-2.5", children: [
1987
+ /* @__PURE__ */ jsx(FileText, { className: "size-4 shrink-0 text-ai-accent" }),
1988
+ /* @__PURE__ */ jsx(
1989
+ "span",
1990
+ {
1991
+ className: "min-w-0 flex-1 truncate text-sm font-medium text-foreground",
1992
+ "data-testid": "artifact-title",
1993
+ children: active.title
1994
+ }
1995
+ ),
1996
+ isRedline && /* @__PURE__ */ jsxs(Badge, { variant: "accent", className: "gap-1", children: [
1997
+ /* @__PURE__ */ jsx(GitCompareArrows, { className: "size-3" }),
1998
+ " Redline"
1999
+ ] }),
2000
+ /* @__PURE__ */ jsxs(Badge, { variant: "secondary", children: [
2001
+ "v",
2002
+ active.version
2003
+ ] }),
2004
+ !active.is_complete ? /* @__PURE__ */ jsxs(
2005
+ "span",
2006
+ {
2007
+ className: "inline-flex items-center gap-1 text-xs text-ai-accent",
2008
+ "data-testid": "artifact-streaming",
2009
+ children: [
2010
+ /* @__PURE__ */ jsx(Loader2, { className: "size-3 animate-spin" }),
2011
+ " drafting"
2012
+ ]
2013
+ }
2014
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
2015
+ /* @__PURE__ */ jsx(
2016
+ "button",
2017
+ {
2018
+ onClick: copy,
2019
+ className: "rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
2020
+ title: "Copy",
2021
+ "data-testid": "artifact-copy",
2022
+ children: copied ? /* @__PURE__ */ jsx(Check, { className: "size-3.5 text-ai-accent" }) : /* @__PURE__ */ jsx(Copy, { className: "size-3.5" })
2023
+ }
2024
+ ),
2025
+ /* @__PURE__ */ jsx(
2026
+ "button",
2027
+ {
2028
+ onClick: download,
2029
+ className: "rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
2030
+ title: "Download",
2031
+ "data-testid": "artifact-download",
2032
+ children: /* @__PURE__ */ jsx(Download, { className: "size-3.5" })
2033
+ }
2034
+ )
2035
+ ] })
2036
+ ] }),
2037
+ artifacts.length > 1 && /* @__PURE__ */ jsx("div", { className: "flex gap-1 overflow-x-auto border-b border-border bg-card px-2 py-1.5", children: artifacts.map((a) => /* @__PURE__ */ jsx(
2038
+ "button",
2039
+ {
2040
+ onClick: () => setActiveId(a.doc_ext_id),
2041
+ className: cn(
2042
+ "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium",
2043
+ a.doc_ext_id === active.doc_ext_id ? "bg-ai-accent-soft text-ai-accent" : "text-muted-foreground hover:bg-accent"
2044
+ ),
2045
+ children: a.title.slice(0, 28)
2046
+ },
2047
+ a.doc_ext_id
2048
+ )) }),
2049
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto bg-ai-surface/40 p-6", children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsx(
2050
+ motion.div,
2051
+ {
2052
+ initial: { opacity: 0, y: 6 },
2053
+ animate: { opacity: 1, y: 0 },
2054
+ transition: { duration: 0.2 },
2055
+ className: "mx-auto max-w-2xl rounded-lg bg-card p-8 shadow-sm ring-1 ring-border",
2056
+ children: active.mode === "html" ? /* @__PURE__ */ jsx("div", { className: "text-sm", dangerouslySetInnerHTML: { __html: active.content } }) : active.mode === "text" ? /* @__PURE__ */ jsx("pre", { className: "whitespace-pre-wrap font-mono text-xs text-foreground", children: active.content }) : /* @__PURE__ */ jsx(AiMarkdown, { children: active.content })
2057
+ },
2058
+ active.doc_ext_id
2059
+ ) }) })
2060
+ ]
2061
+ }
2062
+ );
2063
+ }
2064
+ function DocSearch({
2065
+ docIds,
2066
+ onOpenDoc,
2067
+ placeholder = "Semantic search across your documents\u2026",
2068
+ className
2069
+ }) {
2070
+ const { results, status, error, search, isRunning, query } = useSemanticSearch(docIds);
2071
+ const [value, setValue] = useState("");
2072
+ const submit = () => {
2073
+ if (value.trim()) void search(value, "hybrid");
2074
+ };
2075
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), "data-testid": "doc-search", children: [
2076
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2077
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }),
2078
+ isRunning && /* @__PURE__ */ jsx(Loader2, { className: "absolute right-3 top-1/2 size-4 -translate-y-1/2 animate-spin text-ai-accent" }),
2079
+ /* @__PURE__ */ jsx(
2080
+ "input",
2081
+ {
2082
+ value,
2083
+ onChange: (e) => setValue(e.target.value),
2084
+ onKeyDown: (e) => e.key === "Enter" && (e.preventDefault(), submit()),
2085
+ placeholder,
2086
+ className: "h-11 w-full rounded-lg border border-input bg-background pl-9 pr-24 text-sm outline-none focus:border-ai-accent/50 focus:ring-2 focus:ring-ai-accent/15",
2087
+ "data-testid": "doc-search-input"
2088
+ }
2089
+ ),
2090
+ /* @__PURE__ */ jsxs(
2091
+ "button",
2092
+ {
2093
+ type: "button",
2094
+ onClick: submit,
2095
+ className: "absolute right-2 top-1/2 flex -translate-y-1/2 items-center gap-1 rounded-md bg-ai-accent px-2.5 py-1 text-xs font-medium text-ai-accent-foreground",
2096
+ "data-testid": "doc-search-submit",
2097
+ children: [
2098
+ "Search ",
2099
+ /* @__PURE__ */ jsx(CornerDownLeft, { className: "size-3" })
2100
+ ]
2101
+ }
2102
+ )
2103
+ ] }),
2104
+ status === "error" && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", "data-testid": "doc-search-error", children: error }),
2105
+ status === "done" && /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": "doc-search-results", children: [
2106
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: results.length === 0 ? `No passages matched \u201C${query}\u201D.` : `${results.length} passage${results.length === 1 ? "" : "s"} for \u201C${query}\u201D` }),
2107
+ results.slice(0, 8).map((r, i) => /* @__PURE__ */ jsxs(
2108
+ Card,
2109
+ {
2110
+ className: cn(
2111
+ "p-3",
2112
+ onOpenDoc && r.docId && "cursor-pointer transition-colors hover:border-ai-accent/40"
2113
+ ),
2114
+ onClick: () => r.docId && onOpenDoc?.(r.docId),
2115
+ "data-testid": `doc-search-hit-${i}`,
2116
+ children: [
2117
+ /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center gap-2", children: [
2118
+ /* @__PURE__ */ jsx(FileText, { className: "size-3.5 text-ai-accent" }),
2119
+ /* @__PURE__ */ jsx("span", { className: "truncate text-xs font-medium text-foreground", children: r.docTitle ?? "Document" }),
2120
+ r.page != null && /* @__PURE__ */ jsxs(Badge, { variant: "secondary", children: [
2121
+ "p.",
2122
+ r.page
2123
+ ] }),
2124
+ r.score != null && /* @__PURE__ */ jsxs("span", { className: "ml-auto text-[10px] text-muted-foreground", children: [
2125
+ "score ",
2126
+ r.score.toFixed(2)
2127
+ ] })
2128
+ ] }),
2129
+ /* @__PURE__ */ jsx("p", { className: "line-clamp-3 text-xs leading-relaxed text-muted-foreground", children: r.content })
2130
+ ]
2131
+ },
2132
+ `${r.docId}-${r.page}-${i}`
2133
+ ))
2134
+ ] })
2135
+ ] });
2136
+ }
2137
+ function DocQaPanel({ docId, docName }) {
2138
+ const task = useAiTask();
2139
+ const [q, setQ] = useState("");
2140
+ const ask = () => {
2141
+ const question = q.trim();
2142
+ if (!question || task.isRunning) return;
2143
+ void task.run(
2144
+ `You are answering strictly from the single document "${docName ?? docId}". Cite the page/section for each point and say if the document does not address the question.
2145
+
2146
+ Question: ${question}`,
2147
+ [docId]
2148
+ );
2149
+ };
2150
+ const suggestTags = () => void task.run(
2151
+ `Suggest 3-6 concise, reusable labels for the document "${docName ?? docId}" \u2014 covering document type, the key issues it touches, and any key parties. Return them as a short comma-separated list with a one-line rationale for each.`,
2152
+ [docId]
2153
+ );
2154
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", "data-testid": "doc-qa", children: [
2155
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2156
+ /* @__PURE__ */ jsx(
2157
+ Input,
2158
+ {
2159
+ value: q,
2160
+ onChange: (e) => setQ(e.target.value),
2161
+ onKeyDown: (e) => e.key === "Enter" && (e.preventDefault(), ask()),
2162
+ placeholder: "Ask about this document\u2026",
2163
+ "data-testid": "doc-qa-input"
2164
+ }
2165
+ ),
2166
+ /* @__PURE__ */ jsx(
2167
+ Button,
2168
+ {
2169
+ variant: "accent",
2170
+ size: "icon",
2171
+ onClick: ask,
2172
+ disabled: task.isRunning || !q.trim(),
2173
+ "data-testid": "doc-qa-send",
2174
+ children: /* @__PURE__ */ jsx(Send, { className: "size-4" })
2175
+ }
2176
+ ),
2177
+ /* @__PURE__ */ jsxs(
2178
+ Button,
2179
+ {
2180
+ variant: "outline",
2181
+ size: "sm",
2182
+ onClick: suggestTags,
2183
+ disabled: task.isRunning,
2184
+ "data-testid": "doc-qa-tags",
2185
+ children: [
2186
+ /* @__PURE__ */ jsx(Tags, { className: "size-4" }),
2187
+ " Tags"
2188
+ ]
2189
+ }
2190
+ )
2191
+ ] }),
2192
+ /* @__PURE__ */ jsx(
2193
+ AiAnswer,
2194
+ {
2195
+ status: task.status,
2196
+ text: task.text,
2197
+ stepCount: task.stepCount,
2198
+ error: task.error,
2199
+ emptyHint: "Ask a question, or suggest tags \u2014 answered from this document only.",
2200
+ testId: "doc-qa-answer"
2201
+ }
2202
+ )
2203
+ ] });
2204
+ }
1456
2205
 
1457
2206
  // src/icons/colors.ts
1458
2207
  var NEUTRAL_GRAY = "#64748b";
@@ -2462,6 +3211,6 @@ function DocumentTable({
2462
3211
  ) });
2463
3212
  }
2464
3213
 
2465
- export { ArbiChat, ArbiChatPanel, ArbiProvider, AudIcon, Avatar, AvatarFallback, AvatarImage, Badge, Button, DocumentEntity, DocumentTable, EmlIcon, EntityPill, FILE_TYPE_COLORS, FileIcon, HoverCard, HoverCardContent, HoverCardTrigger, ImgIcon, PdfIcon, ProjectEntity, TagEntity, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, VidIcon, arbiQueryKeys, badgeVariants, buttonVariants, cn, useAgents, useArbi, useArbiUiStore, useAssistantQuery, useConfigs, useConversations, useDeleteAgents, useDocuments, useHealth, useMemories, useSessions, useTags, useTasks, useWorkspaces };
3214
+ export { AiActionCard, AiAnswer, AiMarkdown, ArbiChat, ArbiChatPanel, ArbiProvider, ArtifactCanvas, AudIcon, Avatar, AvatarFallback, AvatarImage, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, DocQaPanel, DocSearch, DocumentEntity, DocumentTable, EmlIcon, EntityPill, FILE_TYPE_COLORS, FileIcon, HoverCard, HoverCardContent, HoverCardTrigger, ImgIcon, Input, PdfIcon, ProjectEntity, TagEntity, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, VidIcon, arbiQueryKeys, badgeVariants, buttonVariants, cn, redlineStats, textToArtifact, toRedlineMarkdown, useAgents, useAiTask, useArbi, useArbiUiStore, useAssistantQuery, useConfigs, useConversations, useDeleteAgents, useDocuments, useHealth, useMemories, useSemanticSearch, useSessions, useTags, useTasks, useWorkspaceDocs, useWorkspaces };
2466
3215
  //# sourceMappingURL=index.js.map
2467
3216
  //# sourceMappingURL=index.js.map