@iloveagents/foundry-web-ui 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/README.md +13 -4
  2. package/dist/components/ag-ui-runtime-provider.d.ts +64 -0
  3. package/dist/components/ag-ui-runtime-provider.js +71 -0
  4. package/dist/components/app-brand.d.ts +6 -0
  5. package/dist/components/app-brand.js +13 -0
  6. package/dist/components/assistant-chat.d.ts +26 -0
  7. package/dist/components/assistant-chat.js +93 -0
  8. package/dist/components/chat-attachments.d.ts +25 -0
  9. package/dist/components/chat-attachments.js +20 -0
  10. package/dist/components/chat-bubble.d.ts +1 -0
  11. package/dist/components/chat-bubble.js +103 -0
  12. package/dist/components/chat-header.d.ts +2 -0
  13. package/dist/components/chat-header.js +16 -0
  14. package/dist/components/client-tool-executor.d.ts +12 -0
  15. package/dist/components/client-tool-executor.js +108 -0
  16. package/dist/components/collection-empty-state.d.ts +9 -0
  17. package/dist/components/collection-empty-state.js +6 -0
  18. package/dist/components/collection-filter-bar.d.ts +19 -0
  19. package/dist/components/collection-filter-bar.js +28 -0
  20. package/dist/components/collection-search-input.d.ts +6 -0
  21. package/dist/components/collection-search-input.js +8 -0
  22. package/dist/components/collection-skeleton.d.ts +6 -0
  23. package/dist/components/collection-skeleton.js +10 -0
  24. package/dist/components/collection-sort-menu.d.ts +10 -0
  25. package/dist/components/collection-sort-menu.js +9 -0
  26. package/dist/components/collection-surface.d.ts +8 -0
  27. package/dist/components/collection-surface.js +5 -0
  28. package/dist/components/collection-toolbar.d.ts +9 -0
  29. package/dist/components/collection-toolbar.js +5 -0
  30. package/dist/components/collection-view-toggle.d.ts +7 -0
  31. package/dist/components/collection-view-toggle.js +17 -0
  32. package/dist/components/composer-submit-bridge.d.ts +1 -0
  33. package/dist/components/composer-submit-bridge.js +15 -0
  34. package/dist/components/confirm-dialog.d.ts +11 -0
  35. package/dist/components/confirm-dialog.js +6 -0
  36. package/dist/components/confirmation-card.d.ts +14 -0
  37. package/dist/components/confirmation-card.js +12 -0
  38. package/dist/components/context-badges.d.ts +16 -0
  39. package/dist/components/context-badges.js +56 -0
  40. package/dist/components/context-bar.d.ts +41 -0
  41. package/dist/components/context-bar.js +123 -0
  42. package/dist/components/form-dialog.d.ts +12 -0
  43. package/dist/components/form-dialog.js +13 -0
  44. package/dist/components/global-selection-popover.d.ts +7 -0
  45. package/dist/components/global-selection-popover.js +167 -0
  46. package/dist/components/infinite-scroll-sentinel.d.ts +6 -0
  47. package/dist/components/infinite-scroll-sentinel.js +25 -0
  48. package/dist/components/json-viewer.d.ts +10 -0
  49. package/dist/components/json-viewer.js +80 -0
  50. package/dist/components/loading-indicator.d.ts +4 -0
  51. package/dist/components/loading-indicator.js +22 -0
  52. package/dist/components/markdown-text.d.ts +13 -0
  53. package/dist/components/markdown-text.js +221 -0
  54. package/dist/components/name-dialog.d.ts +16 -0
  55. package/dist/components/name-dialog.js +23 -0
  56. package/dist/components/selection-popover.d.ts +12 -0
  57. package/dist/components/selection-popover.js +111 -0
  58. package/dist/components/show-document-tool-ui.d.ts +1 -0
  59. package/dist/components/show-document-tool-ui.js +53 -0
  60. package/dist/components/sidebar.d.ts +4 -0
  61. package/dist/components/sidebar.js +904 -0
  62. package/dist/components/surface-card.d.ts +9 -0
  63. package/dist/components/surface-card.js +33 -0
  64. package/dist/components/theme-runtime-provider.d.ts +15 -0
  65. package/dist/components/theme-runtime-provider.js +48 -0
  66. package/dist/components/theme-toggle.d.ts +2 -0
  67. package/dist/components/theme-toggle.js +19 -0
  68. package/dist/components/tool-call-card.d.ts +27 -0
  69. package/dist/components/tool-call-card.js +13 -0
  70. package/dist/components/tool-fallback.d.ts +26 -0
  71. package/dist/components/tool-fallback.js +129 -0
  72. package/dist/components/tool-panel-layout.d.ts +10 -0
  73. package/dist/components/tool-panel-layout.js +12 -0
  74. package/dist/components/tool-panel.d.ts +1 -0
  75. package/dist/components/tool-panel.js +89 -0
  76. package/dist/components/tooltip-icon-button.d.ts +7 -0
  77. package/dist/components/tooltip-icon-button.js +9 -0
  78. package/dist/components/user-menu.d.ts +2 -0
  79. package/dist/components/user-menu.js +14 -0
  80. package/dist/index.d.ts +68 -0
  81. package/dist/index.js +83 -0
  82. package/dist/lib/ag-ui-adapter.d.ts +29 -0
  83. package/dist/lib/ag-ui-adapter.js +373 -0
  84. package/dist/lib/app-store.d.ts +115 -0
  85. package/dist/lib/app-store.js +199 -0
  86. package/dist/lib/attachment-adapter.d.ts +20 -0
  87. package/dist/lib/attachment-adapter.js +99 -0
  88. package/dist/lib/auth-provider.d.ts +16 -0
  89. package/dist/lib/auth-provider.js +88 -0
  90. package/dist/lib/chat-bubble-store.d.ts +30 -0
  91. package/dist/lib/chat-bubble-store.js +23 -0
  92. package/dist/lib/chat-lifecycle-store.d.ts +10 -0
  93. package/{src/lib/chat-lifecycle-store.ts → dist/lib/chat-lifecycle-store.js} +3 -13
  94. package/dist/lib/client-tools.d.ts +11 -0
  95. package/dist/lib/client-tools.js +274 -0
  96. package/dist/lib/composer-submit-store.d.ts +12 -0
  97. package/dist/lib/composer-submit-store.js +17 -0
  98. package/dist/lib/dev-store.d.ts +38 -0
  99. package/dist/lib/dev-store.js +32 -0
  100. package/dist/lib/nav-config.d.ts +159 -0
  101. package/dist/lib/nav-config.js +162 -0
  102. package/dist/lib/nav-dnd.d.ts +8 -0
  103. package/dist/lib/nav-dnd.js +9 -0
  104. package/dist/lib/nav-store.d.ts +45 -0
  105. package/dist/lib/nav-store.js +49 -0
  106. package/dist/lib/selection-context.d.ts +21 -0
  107. package/dist/lib/selection-context.js +34 -0
  108. package/dist/lib/sidebar-store.d.ts +23 -0
  109. package/dist/lib/sidebar-store.js +98 -0
  110. package/dist/lib/theme-runtime.d.ts +130 -0
  111. package/dist/lib/theme-runtime.js +479 -0
  112. package/dist/lib/theme-store.d.ts +15 -0
  113. package/dist/lib/theme-store.js +53 -0
  114. package/dist/lib/tool-panel-store.d.ts +48 -0
  115. package/dist/lib/tool-panel-store.js +69 -0
  116. package/dist/lib/use-new-conversation.d.ts +8 -0
  117. package/dist/lib/use-new-conversation.js +62 -0
  118. package/dist/lib/use-page-tools.d.ts +27 -0
  119. package/{src/lib/use-page-tools.ts → dist/lib/use-page-tools.js} +11 -13
  120. package/dist/styles.css +142 -0
  121. package/dist/ui/collapsible.d.ts +5 -0
  122. package/{src/ui/collapsible.tsx → dist/ui/collapsible.js} +0 -2
  123. package/dist/ui/dialog.d.ts +15 -0
  124. package/dist/ui/dialog.js +25 -0
  125. package/dist/ui/dropdown-menu.d.ts +7 -0
  126. package/dist/ui/dropdown-menu.js +19 -0
  127. package/dist/ui/popover.d.ts +7 -0
  128. package/dist/ui/popover.js +10 -0
  129. package/dist/ui/select.d.ts +4 -0
  130. package/dist/ui/select.js +9 -0
  131. package/dist/ui/tooltip.d.ts +7 -0
  132. package/dist/ui/tooltip.js +10 -0
  133. package/package.json +31 -12
  134. package/AGENTS.md +0 -59
  135. package/CHANGELOG.md +0 -211
  136. package/CLAUDE.md +0 -1
  137. package/src/__tests__/no-spaces-imports.test.ts +0 -59
  138. package/src/__tests__/open-core-guards.test.ts +0 -307
  139. package/src/__tests__/theme-runtime.test.ts +0 -81
  140. package/src/__tests__/tool-fallback.test.tsx +0 -109
  141. package/src/components/__tests__/context-bar.test.ts +0 -112
  142. package/src/components/ag-ui-runtime-provider.tsx +0 -170
  143. package/src/components/app-brand.tsx +0 -38
  144. package/src/components/assistant-chat.tsx +0 -461
  145. package/src/components/chat-attachments.tsx +0 -68
  146. package/src/components/chat-bubble.tsx +0 -373
  147. package/src/components/chat-header.tsx +0 -44
  148. package/src/components/client-tool-executor.tsx +0 -230
  149. package/src/components/collection-empty-state.tsx +0 -37
  150. package/src/components/collection-filter-bar.tsx +0 -168
  151. package/src/components/collection-search-input.tsx +0 -41
  152. package/src/components/collection-skeleton.tsx +0 -55
  153. package/src/components/collection-sort-menu.tsx +0 -62
  154. package/src/components/collection-surface.tsx +0 -46
  155. package/src/components/collection-toolbar.tsx +0 -33
  156. package/src/components/collection-view-toggle.tsx +0 -61
  157. package/src/components/composer-submit-bridge.tsx +0 -19
  158. package/src/components/confirm-dialog.tsx +0 -49
  159. package/src/components/confirmation-card.tsx +0 -32
  160. package/src/components/context-badges.tsx +0 -136
  161. package/src/components/context-bar.tsx +0 -238
  162. package/src/components/form-dialog.tsx +0 -56
  163. package/src/components/global-selection-popover.tsx +0 -266
  164. package/src/components/infinite-scroll-sentinel.tsx +0 -48
  165. package/src/components/json-viewer.tsx +0 -232
  166. package/src/components/loading-indicator.tsx +0 -40
  167. package/src/components/markdown-text-citations.test.tsx +0 -108
  168. package/src/components/markdown-text.tsx +0 -508
  169. package/src/components/name-dialog.tsx +0 -87
  170. package/src/components/selection-popover.tsx +0 -156
  171. package/src/components/show-document-tool-ui.tsx +0 -90
  172. package/src/components/sidebar.test.tsx +0 -611
  173. package/src/components/sidebar.tsx +0 -1689
  174. package/src/components/surface-card.tsx +0 -45
  175. package/src/components/theme-runtime-provider.tsx +0 -93
  176. package/src/components/theme-toggle.tsx +0 -27
  177. package/src/components/tool-call-card.tsx +0 -126
  178. package/src/components/tool-fallback.tsx +0 -169
  179. package/src/components/tool-panel-layout.tsx +0 -36
  180. package/src/components/tool-panel.tsx +0 -233
  181. package/src/components/tooltip-icon-button.tsx +0 -28
  182. package/src/components/user-menu.tsx +0 -60
  183. package/src/index.ts +0 -185
  184. package/src/lib/__tests__/ag-ui-adapter.test.ts +0 -279
  185. package/src/lib/__tests__/app-store.test.ts +0 -405
  186. package/src/lib/__tests__/attachment-adapter.test.ts +0 -48
  187. package/src/lib/__tests__/chat-bubble-store.test.ts +0 -67
  188. package/src/lib/__tests__/client-tools.test.ts +0 -124
  189. package/src/lib/__tests__/dev-store.test.ts +0 -78
  190. package/src/lib/__tests__/nav-config.test.ts +0 -135
  191. package/src/lib/__tests__/nav-dnd.test.ts +0 -24
  192. package/src/lib/__tests__/nav-store.test.ts +0 -59
  193. package/src/lib/__tests__/selection-context.test.ts +0 -114
  194. package/src/lib/__tests__/sidebar-store.test.ts +0 -144
  195. package/src/lib/__tests__/theme-store.test.ts +0 -83
  196. package/src/lib/__tests__/tool-panel-store.test.ts +0 -61
  197. package/src/lib/__tests__/use-page-context.test.ts +0 -58
  198. package/src/lib/ag-ui-adapter.ts +0 -426
  199. package/src/lib/app-store.ts +0 -361
  200. package/src/lib/attachment-adapter.ts +0 -92
  201. package/src/lib/auth-provider.tsx +0 -144
  202. package/src/lib/chat-bubble-store.ts +0 -46
  203. package/src/lib/client-tools.ts +0 -293
  204. package/src/lib/composer-submit-store.ts +0 -32
  205. package/src/lib/dev-store.ts +0 -69
  206. package/src/lib/nav-config.ts +0 -329
  207. package/src/lib/nav-dnd.ts +0 -14
  208. package/src/lib/nav-store.ts +0 -89
  209. package/src/lib/selection-context.ts +0 -65
  210. package/src/lib/sidebar-store.ts +0 -113
  211. package/src/lib/theme-runtime.ts +0 -660
  212. package/src/lib/theme-store.ts +0 -67
  213. package/src/lib/tool-panel-store.ts +0 -121
  214. package/src/lib/use-new-conversation.test.tsx +0 -168
  215. package/src/lib/use-new-conversation.ts +0 -64
  216. package/src/ui/dialog.tsx +0 -109
  217. package/src/ui/dropdown-menu.tsx +0 -75
  218. package/src/ui/popover.tsx +0 -34
  219. package/src/ui/select.tsx +0 -15
  220. package/src/ui/tooltip.tsx +0 -30
  221. package/src/vite-env.d.ts +0 -1
  222. package/tsconfig.json +0 -16
  223. package/vitest.config.ts +0 -8
@@ -1,109 +0,0 @@
1
- /**
2
- * Unit tests for the error-shape sniffing helpers in
3
- * ``tool-fallback.tsx``. The component itself renders a collapsible
4
- * card which is awkward to assert from node-land without
5
- * ``@testing-library/react`` (not a workspace dep); exercising the
6
- * pure helpers covers the interesting logic. The helpers are
7
- * exported with an underscore prefix specifically for this use.
8
- */
9
-
10
- import { describe, expect, it } from "vitest";
11
- import {
12
- _detectErrorResult,
13
- _extractErrorMessage,
14
- } from "../components/tool-fallback.tsx";
15
-
16
- describe("_detectErrorResult", () => {
17
- it("returns false for null / undefined", () => {
18
- expect(_detectErrorResult(null)).toBe(false);
19
- expect(_detectErrorResult(undefined)).toBe(false);
20
- });
21
-
22
- it("returns false for normal strings", () => {
23
- expect(_detectErrorResult("Found 3 documents.")).toBe(false);
24
- expect(_detectErrorResult("")).toBe(false);
25
- });
26
-
27
- it("detects the agent-framework `Error: ...` serialization", () => {
28
- expect(_detectErrorResult("Error: search backend 500")).toBe(true);
29
- expect(_detectErrorResult(" Error: with leading whitespace")).toBe(true);
30
- });
31
-
32
- it("does NOT false-positive on strings containing the word Error mid-text", () => {
33
- // This is the failure the anchored regex exists to prevent.
34
- expect(
35
- _detectErrorResult("A passage about Error Handling in Python"),
36
- ).toBe(false);
37
- expect(_detectErrorResult("Error Boundaries in React")).toBe(false);
38
- });
39
-
40
- it("detects MCP canonical `{isError: true, content: [...]}`", () => {
41
- expect(
42
- _detectErrorResult({
43
- isError: true,
44
- content: [{ type: "text", text: "forbidden" }],
45
- }),
46
- ).toBe(true);
47
- });
48
-
49
- it("returns false when isError is false/undefined", () => {
50
- expect(_detectErrorResult({ isError: false, content: [] })).toBe(false);
51
- expect(_detectErrorResult({ data: [] })).toBe(false);
52
- });
53
-
54
- it("detects structured `{error: 'msg'}`", () => {
55
- expect(_detectErrorResult({ error: "boom" })).toBe(true);
56
- });
57
-
58
- it("does not treat an empty `error` string as an error", () => {
59
- // Avoids false-positives where a tool echoes `error: ""` in a success
60
- // envelope. Narrow by design.
61
- expect(_detectErrorResult({ error: "" })).toBe(false);
62
- });
63
-
64
- it("detects `{error: {message: 'msg'}}`", () => {
65
- expect(_detectErrorResult({ error: { message: "timed out" } })).toBe(true);
66
- });
67
- });
68
-
69
- describe("_extractErrorMessage", () => {
70
- it("strips the `Error: ` prefix from string errors", () => {
71
- expect(_extractErrorMessage("Error: search backend 500")).toBe(
72
- "search backend 500",
73
- );
74
- });
75
-
76
- it("returns `Execution failed` when the string is just `Error:`", () => {
77
- expect(_extractErrorMessage("Error:")).toBe("Execution failed");
78
- });
79
-
80
- it("returns null for normal strings (so caller falls back)", () => {
81
- expect(_extractErrorMessage("All good")).toBeNull();
82
- });
83
-
84
- it("extracts `error: 'msg'` directly", () => {
85
- expect(_extractErrorMessage({ error: "timed out" })).toBe("timed out");
86
- });
87
-
88
- it("extracts nested `error.message`", () => {
89
- expect(
90
- _extractErrorMessage({ error: { message: "invalid token" } }),
91
- ).toBe("invalid token");
92
- });
93
-
94
- it("extracts the first text item from MCP content arrays", () => {
95
- expect(
96
- _extractErrorMessage({
97
- isError: true,
98
- content: [
99
- { type: "text", text: "forbidden" },
100
- { type: "text", text: "second" },
101
- ],
102
- }),
103
- ).toBe("forbidden");
104
- });
105
-
106
- it("returns null when no recognisable payload is present", () => {
107
- expect(_extractErrorMessage({ random: "shape" })).toBeNull();
108
- });
109
- });
@@ -1,112 +0,0 @@
1
- /**
2
- * Unit tests for ``getNavigablePath`` — the function powering
3
- * click-to-navigate on pinned context chips. Two shapes the framework
4
- * supports: page (uses payload.path) and ref (uses sourcePage as the
5
- * deep-link URL set by the source module).
6
- */
7
-
8
- import { describe, expect, it } from "vitest";
9
- import { getNavigablePath } from "../context-bar.tsx";
10
- import type { ContextItem } from "../../lib/app-store.ts";
11
-
12
- function pageItem(path: string): ContextItem {
13
- return {
14
- id: "i-1",
15
- type: "page",
16
- label: "Workspace",
17
- payload: { kind: "page", path, label: "Workspace" },
18
- sourcePage: path,
19
- persistence: "persistent",
20
- createdAt: 0,
21
- };
22
- }
23
-
24
- function refItem(refId: string, sourcePage: string): ContextItem {
25
- return {
26
- id: "i-2",
27
- type: "ref",
28
- label: "Field · cell",
29
- payload: { kind: "ref", refType: "block", refId },
30
- sourcePage,
31
- persistence: "persistent",
32
- createdAt: 0,
33
- };
34
- }
35
-
36
- describe("getNavigablePath", () => {
37
- it("returns the page payload path for page items", () => {
38
- expect(getNavigablePath(pageItem("/spaces/abc"))).toBe("/spaces/abc");
39
- });
40
-
41
- it("returns the ref's sourcePage URL (incl. hash) for ref items", () => {
42
- // The source module places the deep-link in sourcePage when it
43
- // creates the ref; the framework surfaces it without knowing
44
- // anything about the ref shape.
45
- expect(
46
- getNavigablePath(refItem("abc:b1", "/spaces/abc#block-b1")),
47
- ).toBe("/spaces/abc#block-b1");
48
- });
49
-
50
- it("returns null when a ref has no sourcePage", () => {
51
- const item = refItem("abc:b1", "");
52
- expect(getNavigablePath(item)).toBeNull();
53
- });
54
-
55
- it("returns null for selection items (excerpts are not anchors)", () => {
56
- const item: ContextItem = {
57
- id: "i-3",
58
- type: "selection",
59
- label: "x",
60
- payload: { kind: "text", text: "x" },
61
- sourcePage: "/spaces/abc",
62
- persistence: "ephemeral",
63
- createdAt: 0,
64
- };
65
- expect(getNavigablePath(item)).toBeNull();
66
- });
67
-
68
- it("returns null for note items (use text payload — note shares it)", () => {
69
- // ``ContextPayload`` is a closed union of ``text | page | ref``.
70
- // Note items reuse the ``text`` payload (the ``type`` field is
71
- // what distinguishes them from selections).
72
- const item: ContextItem = {
73
- id: "i-4",
74
- type: "note",
75
- label: "n",
76
- payload: { kind: "text", text: "n" },
77
- sourcePage: "/spaces/abc",
78
- persistence: "ephemeral",
79
- createdAt: 0,
80
- };
81
- expect(getNavigablePath(item)).toBeNull();
82
- });
83
-
84
- it("returns null for ref items whose sourcePage isn't a route-like path", () => {
85
- // Some callers populate ``sourcePage`` with a free-form
86
- // breadcrumb label rather than a URL. Guard against navigate()
87
- // pushing a relative/invalid route.
88
- const item: ContextItem = {
89
- id: "i-5",
90
- type: "ref",
91
- label: "Field · x",
92
- payload: { kind: "ref", refType: "block", refId: "abc:b1" },
93
- sourcePage: "Workspace breadcrumb",
94
- persistence: "persistent",
95
- createdAt: 0,
96
- };
97
- expect(getNavigablePath(item)).toBeNull();
98
- });
99
-
100
- it("accepts hash-only sourcePages (same-page deep-link)", () => {
101
- const item: ContextItem = {
102
- id: "i-6",
103
- type: "ref",
104
- label: "Field · x",
105
- payload: { kind: "ref", refType: "block", refId: "abc:b1" },
106
- sourcePage: "#block-b1",
107
- persistence: "persistent",
108
- createdAt: 0,
109
- };
110
- expect(getNavigablePath(item)).toBe("#block-b1");
111
- });
112
- });
@@ -1,170 +0,0 @@
1
- import { createContext, useContext, useState, useCallback, useMemo } from "react";
2
- import { AssistantRuntimeProvider, useLocalRuntime } from "@assistant-ui/react";
3
- import type { ThreadHistoryAdapter } from "@assistant-ui/react";
4
- import { TooltipProvider } from "../ui/tooltip.tsx";
5
- import { AGUIAdapterSDK } from "../lib/ag-ui-adapter.ts";
6
- import { FileAttachmentAdapter } from "../lib/attachment-adapter.ts";
7
- import { ShowDocumentToolUI } from "./show-document-tool-ui.tsx";
8
- import { ClientToolExecutor } from "./client-tool-executor.tsx";
9
-
10
- /**
11
- * Per-mount inputs the host shell passes when it wants a chat
12
- * conversation history wired to the runtime. Mirrors
13
- * :type:`ChatConversationFactoryArgs` in ``foundry-web-shell`` —
14
- * duplicated here (rather than imported) so the web-ui package stays
15
- * independent of the shell package's dependency graph.
16
- */
17
- export interface AGUIChatConversationFactoryArgs {
18
- /** Conversation id captured from the URL (resumed chat) or undefined (fresh). */
19
- urlMatch?: string;
20
- /** Stable AG-UI adapter thread id — present in both cases. */
21
- aguiThreadId: string;
22
- }
23
-
24
- /**
25
- * Factory the consumer passes to build a :type:`ThreadHistoryAdapter`
26
- * once the AG-UI adapter has minted its thread id. Called inside the
27
- * keyed runtime mount; assistant-ui invokes ``load()``/``append()``
28
- * via the returned adapter.
29
- */
30
- export type AGUIHistoryAdapterFactory = (
31
- args: AGUIChatConversationFactoryArgs,
32
- ) => ThreadHistoryAdapter;
33
-
34
- interface AGUIRuntimeProviderProps {
35
- children: React.ReactNode;
36
- /** Custom fetch for agent requests. Handles URL rewriting + auth in production. */
37
- fetchFn?: typeof fetch;
38
- /**
39
- * Optional stable thread id for the AG-UI adapter. When set, the
40
- * agent's POST body carries this id so server-side persistence
41
- * (Foundry thread, conversation row) targets the same conversation
42
- * across reloads / resumes. Changing the id remounts the inner
43
- * runtime — past messages from a different thread don't bleed
44
- * into the new one.
45
- */
46
- threadId?: string;
47
- /**
48
- * Conversation id captured from the current URL when the user
49
- * resumed an existing chat (e.g. ``/chat/<id>``), or ``undefined``
50
- * for a brand-new chat. Distinct from ``threadId``: the host shell
51
- * MAY set ``threadId`` from a non-URL source (a sticky active
52
- * conversation, a freshly-minted UUID) so ``threadId`` alone can't
53
- * tell a history adapter "we're resuming". ``urlMatch`` carries
54
- * that signal verbatim into ``historyAdapterFactory`` so the
55
- * adapter can decide whether to load history (resume) or
56
- * initialise empty (fresh) without inferring from ``threadId``.
57
- */
58
- urlMatch?: string;
59
- /**
60
- * Factory that builds the assistant-ui ``ThreadHistoryAdapter`` for
61
- * this mount. Called once after the AG-UI adapter exists so it can
62
- * receive both the URL match (when resuming) and the adapter's own
63
- * thread id (always set — covers fresh chats). Pass alongside
64
- * ``threadId`` for resumed chats; pass without ``threadId`` to get
65
- * a fresh-chat runtime that still persists.
66
- */
67
- historyAdapterFactory?: AGUIHistoryAdapterFactory;
68
- }
69
-
70
- interface AGUIContextValue {
71
- adapter: AGUIAdapterSDK;
72
- resetThread: () => void;
73
- }
74
-
75
- const AGUIAdapterContext = createContext<AGUIContextValue | null>(null);
76
-
77
- export function useAGUIAdapter() {
78
- const context = useContext(AGUIAdapterContext);
79
- if (!context) {
80
- throw new Error("useAGUIAdapter must be used within AGUIRuntimeProvider");
81
- }
82
- return context;
83
- }
84
-
85
- export function AGUIRuntimeProvider(props: AGUIRuntimeProviderProps) {
86
- // Splitting render across an outer wrapper + a keyed inner is the
87
- // canonical React idiom for "rebuild this stateful subtree when X
88
- // changes". assistant-ui's runtime is created inside useLocalRuntime
89
- // and tracks its messages internally; swapping threadId or history
90
- // mid-life would leave stale state. Keying the inner subtree on
91
- // threadId (+ the manual reset counter) guarantees a clean runtime
92
- // per conversation, matching the ChatGPT-style "click → fresh chat
93
- // backed by past messages" UX.
94
- const [resetKey, setResetKey] = useState(0);
95
- const resetThread = useCallback(() => setResetKey((p) => p + 1), []);
96
-
97
- const innerKey = `${props.threadId ?? "fresh"}:${resetKey}`;
98
-
99
- return (
100
- <AGUIRuntimeInner key={innerKey} {...props} resetThread={resetThread} />
101
- );
102
- }
103
-
104
- function AGUIRuntimeInner({
105
- children,
106
- fetchFn,
107
- threadId,
108
- urlMatch,
109
- historyAdapterFactory,
110
- resetThread,
111
- }: AGUIRuntimeProviderProps & { resetThread: () => void }) {
112
- const adapter = useMemo(
113
- () =>
114
- new AGUIAdapterSDK(
115
- "/api/agent",
116
- // Only pass options if at least one is set, mirroring the prior
117
- // single-arg semantics for the "fresh chat, no overrides" case.
118
- fetchFn || threadId ? { fetchFn, threadId } : undefined,
119
- ),
120
- [fetchFn, threadId],
121
- );
122
-
123
- const attachmentAdapter = useMemo(() => new FileAttachmentAdapter(), []);
124
-
125
- // Build the history adapter ONCE per inner mount, after the AG-UI
126
- // adapter exists so we know its thread id. The keyed-remount in the
127
- // outer wrapper ensures threadId changes trigger a fresh adapter +
128
- // fresh history adapter together, so resumed chats hydrate from the
129
- // right conversation and fresh chats persist under their assistant-
130
- // ui-minted UUID.
131
- const historyAdapter = useMemo<ThreadHistoryAdapter | undefined>(() => {
132
- if (!historyAdapterFactory) return undefined;
133
- return historyAdapterFactory({
134
- // ``urlMatch`` is the URL-derived resume signal (undefined for
135
- // fresh chats). ``aguiThreadId`` is always defined — the AG-UI
136
- // adapter mints it on construction. The factory uses ``urlMatch``
137
- // to decide "load history" vs "initialise empty", and
138
- // ``aguiThreadId`` to identify the row to write into.
139
- urlMatch,
140
- aguiThreadId: adapter.threadId,
141
- });
142
- // adapter is stable for the life of this inner mount (keyed remount
143
- // covers the threadId change case), so it's OK to depend on the
144
- // factory + urlMatch here.
145
- // eslint-disable-next-line react-hooks/exhaustive-deps
146
- }, [historyAdapterFactory, urlMatch]);
147
-
148
- const runtime = useLocalRuntime(adapter, {
149
- adapters: {
150
- attachments: attachmentAdapter,
151
- // history is optional — when undefined, the runtime acts as a
152
- // pure in-memory thread (the original behaviour). When provided,
153
- // assistant-ui calls .load() on mount to seed past messages and
154
- // .append() after every turn.
155
- ...(historyAdapter ? { history: historyAdapter } : {}),
156
- },
157
- });
158
-
159
- return (
160
- <AGUIAdapterContext.Provider value={{ adapter, resetThread }}>
161
- <AssistantRuntimeProvider runtime={runtime}>
162
- <TooltipProvider>
163
- <ShowDocumentToolUI />
164
- <ClientToolExecutor />
165
- {children}
166
- </TooltipProvider>
167
- </AssistantRuntimeProvider>
168
- </AGUIAdapterContext.Provider>
169
- );
170
- }
@@ -1,38 +0,0 @@
1
- import { Layers3 } from "lucide-react";
2
- import { cn } from "@iloveagents/foundry-web-primitives";
3
- import { useThemeRuntime } from "./theme-runtime-provider.tsx";
4
-
5
- export function AppBrand({
6
- showName = true,
7
- className,
8
- iconClassName,
9
- labelClassName,
10
- }: {
11
- showName?: boolean;
12
- className?: string;
13
- iconClassName?: string;
14
- labelClassName?: string;
15
- }) {
16
- const runtime = useThemeRuntime();
17
- const brandName = runtime.branding.appName || "eomi";
18
- const logoUrl =
19
- runtime.mode === "dark"
20
- ? runtime.branding.darkLogoUrl || runtime.branding.logoUrl
21
- : runtime.branding.logoUrl || runtime.branding.darkLogoUrl;
22
- const shouldShowName = showName && !logoUrl;
23
-
24
- return (
25
- <span className={cn("flex items-center gap-2", className)}>
26
- {logoUrl ? (
27
- <img
28
- src={logoUrl}
29
- alt={brandName}
30
- className={cn("h-5 w-auto max-w-[7rem] shrink-0 object-contain", iconClassName)}
31
- />
32
- ) : (
33
- <Layers3 className={cn("size-5 shrink-0 text-primary", iconClassName)} />
34
- )}
35
- {shouldShowName && <span className={cn("truncate", labelClassName)}>{brandName}</span>}
36
- </span>
37
- );
38
- }