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