@ruimte/agents-react 0.0.0 → 0.7.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 (312) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +142 -1
  3. package/dist/agents/AccountDot.d.ts +4 -0
  4. package/dist/agents/AccountDot.js +8 -0
  5. package/dist/agents/AgentIcon.d.ts +6 -0
  6. package/dist/agents/AgentIcon.js +20 -0
  7. package/dist/agents/ProviderLogo.d.ts +8 -0
  8. package/dist/agents/ProviderLogo.js +8 -0
  9. package/dist/agents/account-limits.d.ts +7 -0
  10. package/dist/agents/account-limits.js +26 -0
  11. package/dist/agents/accounts.d.ts +22 -0
  12. package/dist/agents/accounts.js +103 -0
  13. package/dist/agents/model-name.d.ts +6 -0
  14. package/dist/agents/model-name.js +44 -0
  15. package/dist/agents/provider-paths.d.ts +2 -0
  16. package/dist/agents/provider-paths.js +8 -0
  17. package/dist/agents/status-look.d.ts +13 -0
  18. package/dist/agents/status-look.js +22 -0
  19. package/dist/chat/account-choice.d.ts +11 -0
  20. package/dist/chat/account-choice.js +30 -0
  21. package/dist/chat/actions.d.ts +6 -0
  22. package/dist/chat/actions.js +64 -0
  23. package/dist/chat/activity.d.ts +5 -0
  24. package/dist/chat/activity.js +4 -0
  25. package/dist/chat/attachments.d.ts +23 -0
  26. package/dist/chat/attachments.js +70 -0
  27. package/dist/chat/bookmarks.d.ts +14 -0
  28. package/dist/chat/bookmarks.js +73 -0
  29. package/dist/chat/chat-client.d.ts +53 -0
  30. package/dist/chat/chat-client.js +232 -0
  31. package/dist/chat/chat-references.d.ts +5 -0
  32. package/dist/chat/chat-references.js +5 -0
  33. package/dist/chat/drafts.d.ts +29 -0
  34. package/dist/chat/drafts.js +104 -0
  35. package/dist/chat/forks.d.ts +2 -0
  36. package/dist/chat/forks.js +26 -0
  37. package/dist/chat/guards.d.ts +12 -0
  38. package/dist/chat/guards.js +63 -0
  39. package/dist/chat/logic/answers.d.ts +3 -0
  40. package/dist/chat/logic/answers.js +14 -0
  41. package/dist/chat/logic/bookmarks.d.ts +5 -0
  42. package/dist/chat/logic/bookmarks.js +31 -0
  43. package/dist/chat/logic/context-usage.d.ts +12 -0
  44. package/dist/chat/logic/context-usage.js +20 -0
  45. package/dist/chat/logic/fork.d.ts +44 -0
  46. package/dist/chat/logic/fork.js +138 -0
  47. package/dist/chat/logic/handback.d.ts +2 -0
  48. package/dist/chat/logic/handback.js +8 -0
  49. package/dist/chat/logic/json.d.ts +1 -0
  50. package/dist/chat/logic/json.js +2 -0
  51. package/dist/chat/logic/limit.d.ts +7 -0
  52. package/dist/chat/logic/limit.js +34 -0
  53. package/dist/chat/logic/resume-compaction.d.ts +14 -0
  54. package/dist/chat/logic/resume-compaction.js +47 -0
  55. package/dist/chat/logic/scrubber.d.ts +50 -0
  56. package/dist/chat/logic/scrubber.js +216 -0
  57. package/dist/chat/logic/timeline-copy.d.ts +4 -0
  58. package/dist/chat/logic/timeline-copy.js +51 -0
  59. package/dist/chat/logic/timeline-target.d.ts +12 -0
  60. package/dist/chat/logic/timeline-target.js +34 -0
  61. package/dist/chat/logic/timeline.d.ts +110 -0
  62. package/dist/chat/logic/timeline.js +339 -0
  63. package/dist/chat/logic/tool-catalog.d.ts +10 -0
  64. package/dist/chat/logic/tool-catalog.js +40 -0
  65. package/dist/chat/logic/tools.d.ts +15 -0
  66. package/dist/chat/logic/tools.js +79 -0
  67. package/dist/chat/logic/workflow.d.ts +14 -0
  68. package/dist/chat/logic/workflow.js +47 -0
  69. package/dist/chat/mentions.d.ts +46 -0
  70. package/dist/chat/mentions.js +128 -0
  71. package/dist/chat/persisted-json.d.ts +5 -0
  72. package/dist/chat/persisted-json.js +24 -0
  73. package/dist/chat/preferences.d.ts +23 -0
  74. package/dist/chat/preferences.js +110 -0
  75. package/dist/chat/quote.d.ts +2 -0
  76. package/dist/chat/quote.js +25 -0
  77. package/dist/chat/recent-messages.d.ts +17 -0
  78. package/dist/chat/recent-messages.js +28 -0
  79. package/dist/chat/resume-compaction-dismissals.d.ts +3 -0
  80. package/dist/chat/resume-compaction-dismissals.js +14 -0
  81. package/dist/chat/runtime-modes.d.ts +4 -0
  82. package/dist/chat/runtime-modes.js +11 -0
  83. package/dist/chat/stash.d.ts +24 -0
  84. package/dist/chat/stash.js +88 -0
  85. package/dist/chat/subagent-conversation.d.ts +38 -0
  86. package/dist/chat/subagent-conversation.js +150 -0
  87. package/dist/chat/subagent-list.d.ts +16 -0
  88. package/dist/chat/subagent-list.js +76 -0
  89. package/dist/chat/subagent-support.d.ts +6 -0
  90. package/dist/chat/subagent-support.js +14 -0
  91. package/dist/chat/subagent-view.d.ts +33 -0
  92. package/dist/chat/subagent-view.js +65 -0
  93. package/dist/chat/thumbnails.d.ts +10 -0
  94. package/dist/chat/thumbnails.js +87 -0
  95. package/dist/chat/timeline-scroll.d.ts +16 -0
  96. package/dist/chat/timeline-scroll.js +100 -0
  97. package/dist/chat/ui/AccountPill.d.ts +3 -0
  98. package/dist/chat/ui/AccountPill.js +24 -0
  99. package/dist/chat/ui/BookmarkMarker.d.ts +8 -0
  100. package/dist/chat/ui/BookmarkMarker.js +59 -0
  101. package/dist/chat/ui/BookmarkSubmenu.d.ts +3 -0
  102. package/dist/chat/ui/BookmarkSubmenu.js +44 -0
  103. package/dist/chat/ui/ChatActivity.d.ts +3 -0
  104. package/dist/chat/ui/ChatActivity.js +87 -0
  105. package/dist/chat/ui/ChatReferenceChip.d.ts +4 -0
  106. package/dist/chat/ui/ChatReferenceChip.js +17 -0
  107. package/dist/chat/ui/CodeBlock.d.ts +4 -0
  108. package/dist/chat/ui/CodeBlock.js +130 -0
  109. package/dist/chat/ui/Composer.d.ts +14 -0
  110. package/dist/chat/ui/Composer.js +821 -0
  111. package/dist/chat/ui/ComposerInput.d.ts +28 -0
  112. package/dist/chat/ui/ComposerInput.js +118 -0
  113. package/dist/chat/ui/DiffPool.d.ts +5 -0
  114. package/dist/chat/ui/DiffPool.js +26 -0
  115. package/dist/chat/ui/EditDiff.d.ts +4 -0
  116. package/dist/chat/ui/EditDiff.js +21 -0
  117. package/dist/chat/ui/FadingWords.d.ts +3 -0
  118. package/dist/chat/ui/FadingWords.js +5 -0
  119. package/dist/chat/ui/ImageView.d.ts +6 -0
  120. package/dist/chat/ui/ImageView.js +85 -0
  121. package/dist/chat/ui/LimitState.d.ts +8 -0
  122. package/dist/chat/ui/LimitState.js +117 -0
  123. package/dist/chat/ui/Markdown.d.ts +18 -0
  124. package/dist/chat/ui/Markdown.js +158 -0
  125. package/dist/chat/ui/MessageActions.d.ts +9 -0
  126. package/dist/chat/ui/MessageActions.js +56 -0
  127. package/dist/chat/ui/Pickers.d.ts +15 -0
  128. package/dist/chat/ui/Pickers.js +110 -0
  129. package/dist/chat/ui/PromptComposer.d.ts +13 -0
  130. package/dist/chat/ui/PromptComposer.js +48 -0
  131. package/dist/chat/ui/QuoteButton.d.ts +7 -0
  132. package/dist/chat/ui/QuoteButton.js +76 -0
  133. package/dist/chat/ui/ResumeCompactionDock.d.ts +5 -0
  134. package/dist/chat/ui/ResumeCompactionDock.js +15 -0
  135. package/dist/chat/ui/RunSettings.d.ts +25 -0
  136. package/dist/chat/ui/RunSettings.js +148 -0
  137. package/dist/chat/ui/Scrubber.d.ts +17 -0
  138. package/dist/chat/ui/Scrubber.js +121 -0
  139. package/dist/chat/ui/SubagentControls.d.ts +11 -0
  140. package/dist/chat/ui/SubagentControls.js +37 -0
  141. package/dist/chat/ui/SubagentStopButton.d.ts +7 -0
  142. package/dist/chat/ui/SubagentStopButton.js +36 -0
  143. package/dist/chat/ui/SubagentTimeline.d.ts +4 -0
  144. package/dist/chat/ui/SubagentTimeline.js +92 -0
  145. package/dist/chat/ui/Timeline.d.ts +5 -0
  146. package/dist/chat/ui/Timeline.js +318 -0
  147. package/dist/chat/ui/TimelineMenu.d.ts +12 -0
  148. package/dist/chat/ui/TimelineMenu.js +61 -0
  149. package/dist/chat/ui/UnifiedDiff.d.ts +8 -0
  150. package/dist/chat/ui/UnifiedDiff.js +51 -0
  151. package/dist/chat/ui/UploadThumb.d.ts +4 -0
  152. package/dist/chat/ui/UploadThumb.js +11 -0
  153. package/dist/chat/ui/chips.d.ts +4 -0
  154. package/dist/chat/ui/chips.js +11 -0
  155. package/dist/chat/ui/code-lines.d.ts +18 -0
  156. package/dist/chat/ui/code-lines.js +46 -0
  157. package/dist/chat/ui/code-streaming.d.ts +1 -0
  158. package/dist/chat/ui/code-streaming.js +3 -0
  159. package/dist/chat/ui/code-theme.d.ts +3 -0
  160. package/dist/chat/ui/code-theme.js +10 -0
  161. package/dist/chat/ui/composer/chips.d.ts +6 -0
  162. package/dist/chat/ui/composer/chips.js +29 -0
  163. package/dist/chat/ui/composer/editor.d.ts +11 -0
  164. package/dist/chat/ui/composer/editor.js +94 -0
  165. package/dist/chat/ui/composer/keys.d.ts +30 -0
  166. package/dist/chat/ui/composer/keys.js +98 -0
  167. package/dist/chat/ui/diff-theme.d.ts +2 -0
  168. package/dist/chat/ui/diff-theme.js +20 -0
  169. package/dist/chat/ui/file-links.d.ts +5 -0
  170. package/dist/chat/ui/file-links.js +18 -0
  171. package/dist/chat/ui/find-reveal.d.ts +7 -0
  172. package/dist/chat/ui/find-reveal.js +13 -0
  173. package/dist/chat/ui/icons.d.ts +4 -0
  174. package/dist/chat/ui/icons.js +20 -0
  175. package/dist/chat/ui/markdown-blocks.d.ts +6 -0
  176. package/dist/chat/ui/markdown-blocks.js +101 -0
  177. package/dist/chat/ui/quote-selection.d.ts +3 -0
  178. package/dist/chat/ui/quote-selection.js +44 -0
  179. package/dist/chat/ui/rehype-chips.d.ts +23 -0
  180. package/dist/chat/ui/rehype-chips.js +28 -0
  181. package/dist/chat/ui/rehype-fade.d.ts +23 -0
  182. package/dist/chat/ui/rehype-fade.js +30 -0
  183. package/dist/chat/ui/remark-html-as-text.d.ts +8 -0
  184. package/dist/chat/ui/remark-html-as-text.js +24 -0
  185. package/dist/chat/ui/reveal.d.ts +13 -0
  186. package/dist/chat/ui/reveal.js +112 -0
  187. package/dist/chat/ui/rows/ForksRow.d.ts +4 -0
  188. package/dist/chat/ui/rows/ForksRow.js +40 -0
  189. package/dist/chat/ui/rows/MessageRows.d.ts +36 -0
  190. package/dist/chat/ui/rows/MessageRows.js +190 -0
  191. package/dist/chat/ui/rows/Rows.d.ts +12 -0
  192. package/dist/chat/ui/rows/Rows.js +49 -0
  193. package/dist/chat/ui/rows/SubagentRow.d.ts +15 -0
  194. package/dist/chat/ui/rows/SubagentRow.js +98 -0
  195. package/dist/chat/ui/rows/WorkRows.d.ts +46 -0
  196. package/dist/chat/ui/rows/WorkRows.js +143 -0
  197. package/dist/chat/ui/rows/WorkflowRow.d.ts +7 -0
  198. package/dist/chat/ui/rows/WorkflowRow.js +37 -0
  199. package/dist/chat/ui/rows/row-rhythm.d.ts +3 -0
  200. package/dist/chat/ui/rows/row-rhythm.js +14 -0
  201. package/dist/chat/ui/useToggleSet.d.ts +6 -0
  202. package/dist/chat/ui/useToggleSet.js +25 -0
  203. package/dist/host.d.ts +167 -0
  204. package/dist/host.js +41 -0
  205. package/dist/lazy.d.ts +5 -0
  206. package/dist/lazy.js +35 -0
  207. package/dist/locales/en/agent-chat.json +417 -0
  208. package/dist/locales/en/agent-prompts.json +42 -0
  209. package/dist/locales/en/agent-providers.json +131 -0
  210. package/dist/locales/en/agent-usage.json +131 -0
  211. package/dist/locales/nl/agent-chat.json +417 -0
  212. package/dist/locales/nl/agent-prompts.json +42 -0
  213. package/dist/locales/nl/agent-providers.json +131 -0
  214. package/dist/locales/nl/agent-usage.json +131 -0
  215. package/dist/locales.d.ts +5 -0
  216. package/dist/locales.js +18 -0
  217. package/dist/mounted-registry.d.ts +7 -0
  218. package/dist/mounted-registry.js +39 -0
  219. package/dist/port-transport.d.ts +6 -0
  220. package/dist/port-transport.js +120 -0
  221. package/dist/prompts/logic/focus.d.ts +1 -0
  222. package/dist/prompts/logic/focus.js +17 -0
  223. package/dist/prompts/logic/keys.d.ts +42 -0
  224. package/dist/prompts/logic/keys.js +84 -0
  225. package/dist/prompts/logic/prompts.d.ts +34 -0
  226. package/dist/prompts/logic/prompts.fixtures.d.ts +5 -0
  227. package/dist/prompts/logic/prompts.fixtures.js +73 -0
  228. package/dist/prompts/logic/prompts.js +22 -0
  229. package/dist/prompts/logic/subjects.d.ts +49 -0
  230. package/dist/prompts/logic/subjects.js +56 -0
  231. package/dist/prompts/logic/useFocusAfterAnswer.d.ts +6 -0
  232. package/dist/prompts/logic/useFocusAfterAnswer.js +28 -0
  233. package/dist/prompts/logic/usePromptSession.d.ts +24 -0
  234. package/dist/prompts/logic/usePromptSession.js +87 -0
  235. package/dist/prompts/ui/ApprovalBody.d.ts +13 -0
  236. package/dist/prompts/ui/ApprovalBody.js +35 -0
  237. package/dist/prompts/ui/PromptActions.d.ts +26 -0
  238. package/dist/prompts/ui/PromptActions.js +19 -0
  239. package/dist/prompts/ui/PromptCard.d.ts +16 -0
  240. package/dist/prompts/ui/PromptCard.js +50 -0
  241. package/dist/prompts/ui/PromptView.d.ts +2 -0
  242. package/dist/prompts/ui/PromptView.js +47 -0
  243. package/dist/prompts/ui/QuestionBody.d.ts +9 -0
  244. package/dist/prompts/ui/QuestionBody.js +87 -0
  245. package/dist/providers/AccountColors.d.ts +5 -0
  246. package/dist/providers/AccountColors.js +8 -0
  247. package/dist/providers/AccountDetail.d.ts +12 -0
  248. package/dist/providers/AccountDetail.js +76 -0
  249. package/dist/providers/AccountVariables.d.ts +8 -0
  250. package/dist/providers/AccountVariables.js +48 -0
  251. package/dist/providers/AddAccountForm.d.ts +10 -0
  252. package/dist/providers/AddAccountForm.js +58 -0
  253. package/dist/providers/CliDetail.d.ts +11 -0
  254. package/dist/providers/CliDetail.js +62 -0
  255. package/dist/providers/ProvidersPane.d.ts +8 -0
  256. package/dist/providers/ProvidersPane.js +90 -0
  257. package/dist/providers/account-actions.d.ts +7 -0
  258. package/dist/providers/account-actions.js +53 -0
  259. package/dist/providers/parts.d.ts +9 -0
  260. package/dist/providers/parts.js +18 -0
  261. package/dist/providers/provider-abilities.d.ts +2 -0
  262. package/dist/providers/provider-abilities.js +7 -0
  263. package/dist/providers/variables.d.ts +13 -0
  264. package/dist/providers/variables.js +53 -0
  265. package/dist/scope.d.ts +11 -0
  266. package/dist/scope.js +10 -0
  267. package/dist/settings/sections.d.ts +13 -0
  268. package/dist/settings/sections.js +30 -0
  269. package/dist/state/chats.d.ts +32 -0
  270. package/dist/state/chats.js +124 -0
  271. package/dist/state/provider-accounts.d.ts +17 -0
  272. package/dist/state/provider-accounts.js +63 -0
  273. package/dist/state/providers.d.ts +17 -0
  274. package/dist/state/providers.js +29 -0
  275. package/dist/state/usage.d.ts +41 -0
  276. package/dist/state/usage.js +100 -0
  277. package/dist/theme.css +186 -0
  278. package/dist/transport.d.ts +24 -0
  279. package/dist/transport.js +16 -0
  280. package/dist/ui/PlainTextarea.d.ts +2 -0
  281. package/dist/ui/PlainTextarea.js +5 -0
  282. package/dist/usage/LimitsList.d.ts +18 -0
  283. package/dist/usage/LimitsList.js +69 -0
  284. package/dist/usage/UsageBreakdown.d.ts +9 -0
  285. package/dist/usage/UsageBreakdown.js +69 -0
  286. package/dist/usage/UsageChart.d.ts +10 -0
  287. package/dist/usage/UsageChart.js +49 -0
  288. package/dist/usage/UsageDialog.d.ts +6 -0
  289. package/dist/usage/UsageDialog.js +15 -0
  290. package/dist/usage/UsageLimits.d.ts +1 -0
  291. package/dist/usage/UsageLimits.js +62 -0
  292. package/dist/usage/UsageLimitsCard.d.ts +4 -0
  293. package/dist/usage/UsageLimitsCard.js +27 -0
  294. package/dist/usage/UsagePage.d.ts +7 -0
  295. package/dist/usage/UsagePage.js +161 -0
  296. package/dist/usage/UsagePane.d.ts +3 -0
  297. package/dist/usage/UsagePane.js +24 -0
  298. package/dist/usage/UsageSummary.d.ts +12 -0
  299. package/dist/usage/UsageSummary.js +15 -0
  300. package/dist/usage/UsageTiles.d.ts +7 -0
  301. package/dist/usage/UsageTiles.js +19 -0
  302. package/dist/usage/format.d.ts +14 -0
  303. package/dist/usage/format.js +50 -0
  304. package/dist/usage/limit-groups.d.ts +22 -0
  305. package/dist/usage/limit-groups.js +78 -0
  306. package/dist/usage/limits.d.ts +4 -0
  307. package/dist/usage/limits.js +58 -0
  308. package/dist/usage/money.d.ts +1 -0
  309. package/dist/usage/money.js +13 -0
  310. package/dist/usage/summary.d.ts +34 -0
  311. package/dist/usage/summary.js +90 -0
  312. package/package.json +639 -2
@@ -0,0 +1,28 @@
1
+ import { type Ref } from 'react';
2
+ import { EditorState, type Extension } from '@codemirror/state';
3
+ import { EditorView } from '@codemirror/view';
4
+ export interface ComposerInputHandle {
5
+ focus(): void;
6
+ setCaret(pos: number): void;
7
+ }
8
+ export interface InputSelection {
9
+ from: number;
10
+ to: number;
11
+ }
12
+ interface ComposerInputProps {
13
+ ref?: Ref<ComposerInputHandle>;
14
+ dictationToolbar?: HTMLElement | null;
15
+ value: string;
16
+ placeholder: string | HTMLElement;
17
+ disabled: boolean;
18
+ tabbable: boolean;
19
+ className?: string;
20
+ extensions?: Extension;
21
+ onChange(text: string, selection: InputSelection, state: EditorState): void;
22
+ onSelectionChange?(text: string, selection: InputSelection, state: EditorState): void;
23
+ onBlur?(): void;
24
+ onKeyDown?(event: KeyboardEvent, view: EditorView): boolean;
25
+ onPaste?(event: ClipboardEvent, view: EditorView): boolean;
26
+ }
27
+ export declare function ComposerInput({ ref, value, placeholder, disabled, tabbable, className, dictationToolbar, extensions, ...handlers }: ComposerInputProps): import("react").JSX.Element;
28
+ export {};
@@ -0,0 +1,118 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useImperativeHandle, useLayoutEffect, useRef, useState } from 'react';
3
+ import { Compartment, EditorState, Prec, Transaction } from '@codemirror/state';
4
+ import { EditorView, placeholder as placeholderText } from '@codemirror/view';
5
+ import { chatHost } from '../../host';
6
+ import { composerEditorExtensions, externalChange } from './composer/editor';
7
+ const NO_EXTENSIONS = [];
8
+ /*
9
+ * A multi-line text field on CodeMirror, controlled by a plain string. The editor draws the text
10
+ * itself, so a decoration may change a font or add padding without walking the caret off its
11
+ * character, which a textarea with a painted layer behind it could not.
12
+ */
13
+ export function ComposerInput({ ref, value, placeholder, disabled, tabbable, className, dictationToolbar, extensions = NO_EXTENSIONS, ...handlers }) {
14
+ const hostRef = useRef(null);
15
+ const targetRef = useRef(null);
16
+ const viewRef = useRef(null);
17
+ const [compartments] = useState(() => ({
18
+ attributes: new Compartment(),
19
+ editable: new Compartment(),
20
+ placeholder: new Compartment(),
21
+ extensions: new Compartment()
22
+ }));
23
+ const latest = useRef({ value, handlers });
24
+ // What the editor last reported. The parent handing that back is catching up, not setting a value of its own.
25
+ const reported = useRef(value);
26
+ useLayoutEffect(() => {
27
+ latest.current = { value, handlers };
28
+ });
29
+ useLayoutEffect(() => {
30
+ const host = hostRef.current;
31
+ if (!host) {
32
+ return;
33
+ }
34
+ const initial = latest.current.value;
35
+ const view = new EditorView({
36
+ parent: host,
37
+ state: EditorState.create({
38
+ doc: initial,
39
+ selection: { anchor: initial.length },
40
+ extensions: [
41
+ composerEditorExtensions(),
42
+ chatHost().dictation.composer?.extensions ?? [],
43
+ compartments.attributes.of([]),
44
+ compartments.editable.of([]),
45
+ compartments.placeholder.of([]),
46
+ compartments.extensions.of([]),
47
+ Prec.highest(EditorView.domEventHandlers({
48
+ keydown: (event, target) => latest.current.handlers.onKeyDown?.(event, target) ?? false,
49
+ paste: (event, target) => latest.current.handlers.onPaste?.(event, target) ?? false
50
+ })),
51
+ EditorView.updateListener.of((update) => {
52
+ const current = latest.current.handlers;
53
+ if (update.focusChanged && !update.view.hasFocus) {
54
+ current.onBlur?.();
55
+ }
56
+ if ((!update.docChanged && !update.selectionSet) || update.transactions.some((tr) => tr.annotation(externalChange))) {
57
+ return;
58
+ }
59
+ const text = update.state.doc.toString();
60
+ const { from, to } = update.state.selection.main;
61
+ if (update.docChanged) {
62
+ reported.current = text;
63
+ current.onChange(text, { from, to }, update.state);
64
+ }
65
+ else {
66
+ current.onSelectionChange?.(text, { from, to }, update.state);
67
+ }
68
+ })
69
+ ]
70
+ })
71
+ });
72
+ viewRef.current = view;
73
+ return () => {
74
+ view.destroy();
75
+ viewRef.current = null;
76
+ };
77
+ }, [compartments]);
78
+ useLayoutEffect(() => {
79
+ viewRef.current?.dispatch({
80
+ effects: [
81
+ // Spellcheck stays on, as it was on the textarea this editor replaced.
82
+ compartments.attributes.reconfigure(EditorView.contentAttributes.of({ spellcheck: 'true', tabindex: tabbable ? '0' : '-1' })),
83
+ compartments.editable.reconfigure([EditorView.editable.of(!disabled), EditorState.readOnly.of(disabled)]),
84
+ compartments.placeholder.reconfigure(placeholderText(placeholder)),
85
+ compartments.extensions.reconfigure(extensions)
86
+ ]
87
+ });
88
+ }, [compartments, tabbable, disabled, placeholder, extensions]);
89
+ useLayoutEffect(() => {
90
+ const view = viewRef.current;
91
+ if (!view || value === reported.current) {
92
+ return;
93
+ }
94
+ reported.current = value;
95
+ const doc = view.state.doc.toString();
96
+ if (value === doc) {
97
+ return;
98
+ }
99
+ view.dispatch({
100
+ changes: { from: 0, to: doc.length, insert: value },
101
+ selection: { anchor: value.length },
102
+ annotations: [externalChange.of(true), Transaction.addToHistory.of(false)]
103
+ });
104
+ }, [value]);
105
+ useImperativeHandle(ref, () => ({
106
+ focus: () => viewRef.current?.focus(),
107
+ setCaret: (pos) => {
108
+ const view = viewRef.current;
109
+ if (!view) {
110
+ return;
111
+ }
112
+ view.dispatch({ selection: { anchor: Math.min(pos, view.state.doc.length) }, scrollIntoView: true });
113
+ view.focus();
114
+ }
115
+ }), []);
116
+ const Dictation = chatHost().dictation.composer?.Control;
117
+ return (_jsxs("div", { ref: targetRef, className: "min-w-0", children: [_jsx("div", { ref: hostRef, className: className }), Dictation && _jsx(Dictation, { buttonContainer: dictationToolbar, targetRef: targetRef, disabled: disabled, editor: () => viewRef.current })] }));
118
+ }
@@ -0,0 +1,5 @@
1
+ import { type ReactNode } from 'react';
2
+ export default function DiffPool({ workerFactory, children }: {
3
+ workerFactory: () => Worker;
4
+ children: ReactNode;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect } from 'react';
3
+ import { useWorkerPool, WorkerPoolContextProvider } from '@pierre/diffs/react';
4
+ import { useDiffTheme } from './diff-theme';
5
+ // Two workers keep highlighting off the main thread without holding a shiki instance per chat node.
6
+ const POOL_SIZE = 2;
7
+ /* The pool reads its highlighter options once, when the singleton is made, and its theme wins over a diff's own. */
8
+ function ThemeSync() {
9
+ const pool = useWorkerPool();
10
+ const theme = useDiffTheme();
11
+ useEffect(() => {
12
+ void pool?.setRenderOptions({ theme });
13
+ }, [pool, theme]);
14
+ return null;
15
+ }
16
+ /*
17
+ * Shares one highlighter pool between every diff under it; the provider keeps a singleton behind the
18
+ * scenes. How a worker of `@pierre/diffs/worker` is made is the bundler's business, so the app says.
19
+ */
20
+ export default function DiffPool({ workerFactory, children }) {
21
+ const theme = useDiffTheme();
22
+ return (_jsxs(WorkerPoolContextProvider, { poolOptions: {
23
+ poolSize: POOL_SIZE,
24
+ workerFactory
25
+ }, highlighterOptions: { theme }, children: [_jsx(ThemeSync, {}), children] }));
26
+ }
@@ -0,0 +1,4 @@
1
+ import type { FileChange } from '../logic/tools';
2
+ export default function EditDiff({ change }: {
3
+ change: FileChange;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import { MultiFileDiff } from '@pierre/diffs/react';
4
+ import { chatHost } from '../../host';
5
+ import { useDiffTheme } from './diff-theme';
6
+ /* One hunk of a file edit, highlighted in a worker. The header is ours, so the library's is off. */
7
+ export default function EditDiff({ change }) {
8
+ const resolved = chatHost().code.useMode();
9
+ const theme = useDiffTheme();
10
+ const oldFile = useMemo(() => ({ name: change.path, contents: change.before }), [change.path, change.before]);
11
+ const newFile = useMemo(() => ({ name: change.path, contents: change.after }), [change.path, change.after]);
12
+ const options = useMemo(() => ({
13
+ diffStyle: 'unified',
14
+ theme,
15
+ themeType: resolved,
16
+ disableFileHeader: true,
17
+ overflow: 'wrap',
18
+ hunkSeparators: 'simple'
19
+ }), [theme, resolved]);
20
+ return (_jsx("div", { className: "chat-diff select-text", children: _jsx(MultiFileDiff, { oldFile: oldFile, newFile: newFile, options: options }) }));
21
+ }
@@ -0,0 +1,3 @@
1
+ export declare function FadingWords({ text }: {
2
+ text: string;
3
+ }): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { FADE_CLASS, isWhitespace, wordSegments } from './rehype-fade';
3
+ export function FadingWords({ text }) {
4
+ return (_jsx(_Fragment, { children: wordSegments(text).map((segment, index) => isWhitespace(segment) ? (segment) : (_jsx("span", { className: FADE_CLASS, children: segment }, index))) }));
5
+ }
@@ -0,0 +1,6 @@
1
+ import type { ResourceUrl } from '../../host';
2
+ export declare function ImageThumb({ source, alt, className }: {
3
+ source: ResourceUrl;
4
+ alt: string;
5
+ className?: string;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,85 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useState } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Dialog } from '@base-ui-components/react/dialog';
5
+ import clsx from 'clsx';
6
+ import { ImageOff, Minus, Plus, RotateCcw } from 'lucide-react';
7
+ import { BTN_GROUP } from '@ruimte/ui/classes';
8
+ import { Tooltip } from '@ruimte/ui/Tooltip';
9
+ import { CloseButton } from '@ruimte/ui/CloseButton';
10
+ import { Icon } from '@ruimte/ui/Icon';
11
+ const MIN_SCALE = 1;
12
+ const MAX_SCALE = 8;
13
+ // One wheel notch, small enough that a trackpad flick does not jump past the detail it aimed at.
14
+ const WHEEL_STEP = 1.0015;
15
+ const BUTTON_STEP = 1.5;
16
+ const START = { scale: 1, x: 0, y: 0 };
17
+ /*
18
+ * Zooms around a point, which is what makes a wheel over a detail feel like a magnifier instead of
19
+ * a slider. The pixel under the pointer stays where it is while everything else moves away from it.
20
+ * `x` and `y` are offsets from the middle of the frame, so a scale of 1 is always centered.
21
+ */
22
+ const zoomed = (view, factor, pointX, pointY) => {
23
+ const scale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, view.scale * factor));
24
+ if (scale === MIN_SCALE) {
25
+ return START;
26
+ }
27
+ const ratio = scale / view.scale;
28
+ return { scale, x: pointX - (pointX - view.x) * ratio, y: pointY - (pointY - view.y) * ratio };
29
+ };
30
+ /* An image on its own, large: the wheel and the buttons zoom, dragging pans, Escape closes. */
31
+ function Lightbox({ src, alt, open, onOpenChange }) {
32
+ const { t } = useTranslation('agent-chat');
33
+ const [view, setView] = useState(START);
34
+ // The alt of an image an agent read is its path; the header shows the file and keeps the path on a tooltip.
35
+ const name = alt.slice(alt.lastIndexOf('/') + 1);
36
+ const title = _jsx("span", { className: "min-w-0 truncate text-xs text-text", children: name });
37
+ const frameRef = useRef(null);
38
+ const dragRef = useRef(null);
39
+ if (!open && view !== START) {
40
+ setView(START);
41
+ }
42
+ /* Where the pointer sits against the middle of the frame, which is what the transform counts from. */
43
+ const pointIn = (event) => {
44
+ const rect = frameRef.current?.getBoundingClientRect();
45
+ if (!rect) {
46
+ return { x: 0, y: 0 };
47
+ }
48
+ return { x: event.clientX - rect.left - rect.width / 2, y: event.clientY - rect.top - rect.height / 2 };
49
+ };
50
+ const zoomButton = (factor) => setView((current) => zoomed(current, factor, 0, 0));
51
+ return (_jsx(Dialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(Dialog.Portal, { children: [_jsx(Dialog.Backdrop, { className: "dialog-backdrop lightbox-backdrop" }), _jsxs(Dialog.Popup, { className: "dialog-popup flex min-w-72 flex-col", children: [_jsx(Dialog.Title, { className: "sr-only", children: alt }), _jsxs("div", { className: "flex h-11 shrink-0 items-center gap-2 border-b border-border pr-2 pl-3", children: [name === alt ? title : _jsx(Tooltip, { label: alt, children: title }), _jsx("span", { className: "grow" }), _jsxs("span", { className: "tabular-nums text-xs text-text-faint", children: [Math.round(view.scale * 100), "%"] }), _jsxs("span", { className: BTN_GROUP, children: [_jsx(Tooltip, { label: t('image.zoomOut'), name: true, children: _jsx("button", { className: "icon-btn icon-btn-sm", disabled: view.scale <= MIN_SCALE, onClick: () => zoomButton(1 / BUTTON_STEP), children: _jsx(Icon, { icon: Minus, size: 14 }) }) }), _jsx(Tooltip, { label: t('image.zoomIn'), name: true, children: _jsx("button", { className: "icon-btn icon-btn-sm", disabled: view.scale >= MAX_SCALE, onClick: () => zoomButton(BUTTON_STEP), children: _jsx(Icon, { icon: Plus, size: 14 }) }) }), _jsx(Tooltip, { label: t('image.reset'), name: true, children: _jsx("button", { className: "icon-btn icon-btn-sm", disabled: view.scale === MIN_SCALE, onClick: () => setView(START), children: _jsx(Icon, { icon: RotateCcw, size: 14 }) }) })] }), _jsx(CloseButton, { label: t('common.action.close'), kbd: "esc", className: "ml-1", dialog: true })] }), _jsx("div", { ref: frameRef, className: clsx('lightbox-frame grid place-items-center overflow-hidden bg-surface-sunken', view.scale > MIN_SCALE && 'cursor-grab'), onWheel: (e) => {
52
+ const point = pointIn(e);
53
+ setView((current) => zoomed(current, WHEEL_STEP ** -e.deltaY, point.x, point.y));
54
+ }, onDoubleClick: (e) => {
55
+ const point = pointIn(e);
56
+ setView((current) => (current.scale > MIN_SCALE ? START : zoomed(current, 2, point.x, point.y)));
57
+ }, onPointerDown: (e) => {
58
+ if (view.scale <= MIN_SCALE) {
59
+ return;
60
+ }
61
+ e.currentTarget.setPointerCapture(e.pointerId);
62
+ dragRef.current = { pointerId: e.pointerId, x: e.clientX - view.x, y: e.clientY - view.y };
63
+ }, onPointerMove: (e) => {
64
+ const drag = dragRef.current;
65
+ if (drag?.pointerId === e.pointerId) {
66
+ setView((current) => ({ ...current, x: e.clientX - drag.x, y: e.clientY - drag.y }));
67
+ }
68
+ }, onPointerUp: () => {
69
+ dragRef.current = null;
70
+ }, onPointerCancel: () => {
71
+ dragRef.current = null;
72
+ }, children: _jsx("img", { src: src, alt: alt, draggable: false, className: "select-none", style: { transform: `translate(${Math.round(view.x)}px, ${Math.round(view.y)}px) scale(${view.scale})` } }) })] })] }) }));
73
+ }
74
+ /*
75
+ * A thumbnail that opens the same image large. The button is the picture, so there is nothing to aim at.
76
+ * The image is bytes where the host keeps them; until they are here a quiet square stands in for it.
77
+ */
78
+ export function ImageThumb({ source, alt, className }) {
79
+ const [open, setOpen] = useState(false);
80
+ const { url: src, failure } = source;
81
+ if (src === null) {
82
+ return (_jsx("span", { className: "grid size-16 place-items-center rounded-lg border border-border bg-surface-sunken text-text-faint", children: failure !== null && (_jsx(Tooltip, { label: failure, children: _jsx(Icon, { icon: ImageOff, size: 14 }) })) }));
83
+ }
84
+ return (_jsxs(_Fragment, { children: [_jsx("button", { className: "block overflow-hidden rounded-lg border border-border", onClick: () => setOpen(true), children: _jsx("img", { src: src, alt: alt, className: className }) }), _jsx(Lightbox, { src: src, alt: alt, open: open, onOpenChange: setOpen })] }));
85
+ }
@@ -0,0 +1,8 @@
1
+ import type { ChatInfo } from '@ruimte/agent-contracts';
2
+ export declare function LimitDock({ chatId, info }: {
3
+ chatId: string;
4
+ info: ChatInfo;
5
+ }): import("react").JSX.Element | null;
6
+ export declare function LimitPill({ chatId }: {
7
+ chatId: string;
8
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,117 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Hourglass } from 'lucide-react';
5
+ import { AccountDot } from '../../agents/AccountDot';
6
+ import { continueTarget, hasUnreadAccount } from '../../agents/account-limits';
7
+ import { useAccountChoice } from '../account-choice';
8
+ import { limitView } from '../logic/limit';
9
+ import { useFormatLocale } from '@ruimte/ui/format/locale';
10
+ import { Toggle } from '@ruimte/ui/controls';
11
+ import { useUsageLimits } from '../../usage/limits';
12
+ import { useChats } from '../../state/chats';
13
+ import { chatHost } from '../../host';
14
+ import { useChatScope } from '../../scope';
15
+ import { useChatActions } from '../actions';
16
+ import { useUsageStore } from '../../state/usage';
17
+ import { Button } from '@ruimte/ui/Button';
18
+ import { Icon } from '@ruimte/ui/Icon';
19
+ import { Pill } from '@ruimte/ui/Pill';
20
+ import { Tooltip } from '@ruimte/ui/Tooltip';
21
+ import { useNow } from '@ruimte/ui/useNow';
22
+ // A time a limit names reads as a clock today and with its day after that, so the words follow the minute.
23
+ const MINUTE_MS = 60_000;
24
+ // Reading an account's plan starts its CLI, so an account nobody used yet is read at most this often per host.
25
+ const UNREAD_REFRESH_MS = 5 * MINUTE_MS;
26
+ const refreshedAt = new Map();
27
+ /*
28
+ * The host reads the plan of an account other than a CLI's default one only while it is in use, so
29
+ * an account that could take the chat over may have no numbers yet. The dock asks for them.
30
+ */
31
+ const readUnreadAccounts = (scope) => {
32
+ const last = refreshedAt.get(scope.id);
33
+ if (last !== undefined && Date.now() - last < UNREAD_REFRESH_MS) {
34
+ return;
35
+ }
36
+ refreshedAt.set(scope.id, Date.now());
37
+ void scope.transport
38
+ .request('usage.refreshLimits', {})
39
+ .then((snapshot) => useUsageStore.getState().setLimits(scope.id, snapshot))
40
+ .catch(() => undefined);
41
+ };
42
+ /*
43
+ * The chat's own "Resume at reset". It only counts while the host allows it, so with the host's
44
+ * switch off it stands off and says where that switch is.
45
+ */
46
+ function ResumeAtResetToggle({ chatId, info }) {
47
+ const { t } = useTranslation('agent-chat');
48
+ const { id } = useChatScope();
49
+ const actions = useChatActions();
50
+ const machineAllows = chatHost().useResumeAtReset(id);
51
+ const set = (on) => {
52
+ void actions.configure(chatId, { resumeAtReset: on }).catch(() => undefined);
53
+ };
54
+ return (_jsx(Tooltip, { label: machineAllows ? t('limit.toggle') : t('limit.machineOff'), children: _jsx("span", { className: "inline-flex shrink-0", children: _jsx(Toggle, { checked: machineAllows && info.resumeAtReset !== false, onChange: set, label: t('limit.toggle'), disabled: !machineAllows }) }) }));
55
+ }
56
+ /*
57
+ * Another account of the chat's CLI that has room: the one with the least of its session spent. Only
58
+ * ever offered, since going on there spends someone's other plan. An account that reads the chat's
59
+ * conversation takes it over; any other goes on in a fork, which then opens the way a fork does.
60
+ */
61
+ function ContinueOn({ chatId, info, choice }) {
62
+ const { t } = useTranslation('agent-chat');
63
+ const scope = useChatScope();
64
+ const actions = useChatActions();
65
+ const limits = useUsageLimits();
66
+ const [busy, setBusy] = useState(false);
67
+ const target = continueTarget(choice.accounts, limits, info.provider, info.account);
68
+ const unread = limits !== null && hasUnreadAccount(choice.accounts, limits, info.provider, info.account);
69
+ useEffect(() => {
70
+ if (unread) {
71
+ readUnreadAccounts(scope);
72
+ }
73
+ }, [scope, unread]);
74
+ if (target === null) {
75
+ return null;
76
+ }
77
+ const name = choice.nameOf(target);
78
+ const go = () => {
79
+ setBusy(true);
80
+ actions
81
+ .continueOn(chatId, target.id)
82
+ .catch((e) => chatHost().notify({
83
+ kind: 'error',
84
+ title: t('limit.continueFailed', { account: name }),
85
+ description: e instanceof Error ? e.message : String(e)
86
+ }))
87
+ .finally(() => setBusy(false));
88
+ };
89
+ return (_jsx(Tooltip, { label: t('limit.continueOnTip', { account: name }), children: _jsxs(Button, { variant: "secondary", size: "sm", className: "shrink-0 gap-1.5 whitespace-nowrap", disabled: busy, "aria-label": t('limit.continueOnAria', { account: name }), onClick: go, children: [t('limit.continueOn'), _jsx(AccountDot, { color: target.account.color, className: "size-1.75" }), name] }) }));
90
+ }
91
+ /* At the top of the composer while the last turn stopped on a limit: what stopped it, when it goes on, and the switch. */
92
+ export function LimitDock({ chatId, info }) {
93
+ const { t } = useTranslation('agent-chat');
94
+ useFormatLocale();
95
+ const now = useNow(MINUTE_MS, info.limit !== undefined);
96
+ const choice = useAccountChoice(info.provider, info.account);
97
+ const view = limitView(info, now);
98
+ if (view === null) {
99
+ return null;
100
+ }
101
+ // With a choice of accounts the line names the one that ran into the limit.
102
+ const account = choice === null ? null : choice.current === null ? choice.currentId : choice.nameOf(choice.current);
103
+ const detail = account === null ? view.detail : view.detail === null ? account : `${account} · ${view.detail}`;
104
+ return (_jsxs("div", { className: "flex items-center gap-2 border-b border-border px-3.5 py-2.5", children: [_jsx(Icon, { icon: Hourglass, size: 16, className: "shrink-0 text-text-faint" }), _jsxs("span", { className: "flex min-w-0 grow flex-col", children: [_jsx("span", { className: "truncate text-sm text-text", children: view.title }), detail !== null && _jsx("span", { className: "truncate text-xs tabular-nums text-text-faint", children: detail })] }), choice !== null && info.limit?.kind === 'usage' && _jsx(ContinueOn, { chatId: chatId, info: info, choice: choice }), _jsx("span", { className: "shrink-0 text-xs text-text-muted", children: t('limit.toggle') }), _jsx(ResumeAtResetToggle, { chatId: chatId, info: info })] }));
105
+ }
106
+ /* The same state in a chat node's header, read from the status every client gets, so a node nobody opened says it too. */
107
+ export function LimitPill({ chatId }) {
108
+ const { keyOf } = useChatScope();
109
+ const info = useChats((s) => s.statusByKey[keyOf(chatId)]?.info);
110
+ useFormatLocale();
111
+ const now = useNow(MINUTE_MS, info?.limit !== undefined);
112
+ const view = info === undefined ? null : limitView(info, now);
113
+ if (info === undefined || view === null) {
114
+ return null;
115
+ }
116
+ return (_jsxs(_Fragment, { children: [_jsx(Tooltip, { label: view.detail === null ? view.title : `${view.title}. ${view.detail}`, children: _jsx(Pill, { className: "tabular-nums", icon: _jsx(Icon, { icon: Hourglass, size: 12 }), children: view.pill }) }), _jsx(ResumeAtResetToggle, { chatId: chatId, info: info })] }));
117
+ }
@@ -0,0 +1,18 @@
1
+ import { type Components, type Options } from 'react-markdown';
2
+ export declare const ReplyMarkdown: import("react").MemoExoticComponent<({ text, streaming, arriving }: {
3
+ text: string;
4
+ streaming: boolean;
5
+ arriving?: boolean;
6
+ }) => import("react").JSX.Element>;
7
+ export declare const MessageMarkdown: import("react").MemoExoticComponent<({ text, mentions, skills }: {
8
+ text: string;
9
+ mentions?: string[];
10
+ skills?: string[];
11
+ }) => import("react").JSX.Element>;
12
+ export declare const Markdown: import("react").MemoExoticComponent<({ text, breaks, fileLinks, rehypePlugins, componentOverrides }: {
13
+ text: string;
14
+ breaks?: boolean;
15
+ fileLinks?: boolean;
16
+ rehypePlugins?: Options['rehypePlugins'];
17
+ componentOverrides?: Components;
18
+ }) => import("react").JSX.Element>;
@@ -0,0 +1,158 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { memo, useMemo } from 'react';
3
+ import clsx from 'clsx';
4
+ import { Zap } from 'lucide-react';
5
+ import ReactMarkdown, {} from 'react-markdown';
6
+ import remarkBreaks from 'remark-breaks';
7
+ import remarkGfm from 'remark-gfm';
8
+ import { CHIP_IN_MESSAGE, MENTION_TONE, SKILL_TONE } from './chips';
9
+ import { CodeBlock } from './CodeBlock';
10
+ import { CodeStreamingContext } from './code-streaming';
11
+ import { splitMarkdownBlocks } from './markdown-blocks';
12
+ import { rehypeChips } from './rehype-chips';
13
+ import { rehypeFadeWords } from './rehype-fade';
14
+ import { remarkHtmlAsText } from './remark-html-as-text';
15
+ import { openFileLink, useFileLinkCwd, useFileLinkTarget } from './file-links';
16
+ import { FileIcon } from '@ruimte/ui/FileIcon';
17
+ import { Icon } from '@ruimte/ui/Icon';
18
+ const languageOf = (className) => /language-([\w-]+)/.exec(className ?? '')?.[1] ?? 'text';
19
+ const INLINE_CODE = 'rounded-sm bg-surface-sunken px-1 py-px font-mono text-code';
20
+ /*
21
+ * A path an agent wrote down, opened in the preview panel. It keeps the shape of the code chip it
22
+ * would otherwise be, so a sentence full of them does not start dancing; only the color says it can
23
+ * be clicked. `data-file-path` is what the thread's own right-click menu reads, so a link answers to
24
+ * both ways of opening it.
25
+ */
26
+ function FileLink({ target, className, children }) {
27
+ const cwd = useFileLinkCwd();
28
+ return (_jsx("button", { type: "button", className: clsx('cursor-pointer select-text', className), "data-file-path": target.path, "data-file-line": target.line, onClick: () => openFileLink(cwd, target), children: children }));
29
+ }
30
+ /* Inline code, which is a file reference often enough that it is worth asking every time. */
31
+ function InlineCode({ text }) {
32
+ const target = useFileLinkTarget(text);
33
+ if (target === null) {
34
+ return _jsx("code", { className: INLINE_CODE, children: text });
35
+ }
36
+ return (_jsx(FileLink, { target: target, className: `${INLINE_CODE} text-accent hover:underline`, children: text }));
37
+ }
38
+ /* The path a link target spells, with the one scheme that still means a file on the host's machine
39
+ taken off. A href nobody encoded is left as it is, since decoding throws on a stray percent. */
40
+ const hrefPath = (href) => {
41
+ const bare = href.replace(/^file:\/\//, '');
42
+ try {
43
+ return decodeURI(bare);
44
+ }
45
+ catch {
46
+ return bare;
47
+ }
48
+ };
49
+ /* A markdown link, which is a file when its target names no scheme of its own. */
50
+ function MarkdownLink({ href, children }) {
51
+ const target = useFileLinkTarget(hrefPath(href ?? ''));
52
+ if (target !== null) {
53
+ return (_jsx(FileLink, { target: target, className: "text-accent underline underline-offset-2", children: children }));
54
+ }
55
+ return (_jsx("a", { href: href, target: "_blank", rel: "noreferrer", children: children }));
56
+ }
57
+ const components = {
58
+ pre({ children }) {
59
+ return _jsx(_Fragment, { children: children });
60
+ },
61
+ code({ className, children }) {
62
+ const text = String(children ?? '');
63
+ // Fenced blocks end with a newline and carry a language; anything else is inline.
64
+ if (className?.startsWith('language-') || text.includes('\n')) {
65
+ return _jsx(CodeBlock, { code: text.replace(/\n$/, ''), lang: languageOf(className) });
66
+ }
67
+ return _jsx(InlineCode, { text: text });
68
+ },
69
+ a({ href, children }) {
70
+ return _jsx(MarkdownLink, { href: href, children: children });
71
+ },
72
+ table({ children }) {
73
+ // A table wider than the column scrolls on its own instead of pushing the thread sideways.
74
+ return (_jsx("div", { className: "chat-table", children: _jsx("table", { children: children }) }));
75
+ }
76
+ };
77
+ /* For text from outside this machine, where a path in inline code is not a file anybody meant to open here. */
78
+ const plainComponents = {
79
+ ...components,
80
+ code({ className, children }) {
81
+ const text = String(children ?? '');
82
+ if (className?.startsWith('language-') || text.includes('\n')) {
83
+ return _jsx(CodeBlock, { code: text.replace(/\n$/, ''), lang: languageOf(className) });
84
+ }
85
+ return _jsx("code", { className: INLINE_CODE, children: text });
86
+ },
87
+ a({ href, children }) {
88
+ return (_jsx("a", { href: href, target: "_blank", rel: "noreferrer", children: children }));
89
+ }
90
+ };
91
+ // A reply sits under the heading of its message, an `h3`, so its own headings start one level below
92
+ // that. Only `aria-level` moves. The tag stays, since the styles and a copy of the thread read it.
93
+ const REPLY_HEADING_OFFSET = 3;
94
+ const replyHeading = (level) => {
95
+ const Tag = `h${level}`;
96
+ return function ReplyHeading({ children }) {
97
+ return _jsx(Tag, { "aria-level": Math.min(6, level + REPLY_HEADING_OFFSET), children: children });
98
+ };
99
+ };
100
+ const replyComponents = {
101
+ ...components,
102
+ h1: replyHeading(1),
103
+ h2: replyHeading(2),
104
+ h3: replyHeading(3),
105
+ h4: replyHeading(4),
106
+ h5: replyHeading(5),
107
+ h6: replyHeading(6)
108
+ };
109
+ /* A picked file or skill in a sent message. The glyph stands in for the sigil the text still carries. */
110
+ function Chip({ kind, value }) {
111
+ if (kind === 'skill') {
112
+ return (_jsxs("span", { className: clsx(CHIP_IN_MESSAGE, SKILL_TONE), children: [_jsx(Icon, { icon: Zap, size: 14, className: "shrink-0 opacity-85" }), _jsx("span", { className: "truncate", children: value })] }));
113
+ }
114
+ return (_jsxs("span", { className: clsx(CHIP_IN_MESSAGE, MENTION_TONE), "data-file-path": value, children: [_jsx(FileIcon, { path: value, size: 14 }), _jsx("span", { className: "truncate", children: value })] }));
115
+ }
116
+ const messageComponents = {
117
+ ...replyComponents,
118
+ span({ children, 'data-chip': chip, 'data-value': value }) {
119
+ if (chip === undefined || value === undefined) {
120
+ return _jsx("span", { children: children });
121
+ }
122
+ return _jsx(Chip, { kind: chip, value: value });
123
+ }
124
+ };
125
+ // Module constants, so a render never hands react-markdown a fresh array and makes it parse again.
126
+ const PLUGINS = [remarkGfm];
127
+ const PLUGINS_WITH_BREAKS = [remarkGfm, remarkBreaks];
128
+ const MESSAGE_PLUGINS = [remarkGfm, remarkHtmlAsText, remarkBreaks];
129
+ const FADE_PLUGINS = [rehypeFadeWords];
130
+ const NO_PLUGINS = [];
131
+ /* One block of a reply. It renders no element of its own, so prose still sees the paragraphs as
132
+ direct children and keeps its first and last margins. */
133
+ const ReplyBlock = memo(function ReplyBlock({ text, fade, open }) {
134
+ return (_jsx(CodeStreamingContext.Provider, { value: open, children: _jsx(ReactMarkdown, { remarkPlugins: PLUGINS, rehypePlugins: fade ? FADE_PLUGINS : NO_PLUGINS, components: replyComponents, children: text }) }));
135
+ });
136
+ /*
137
+ * A reply in a chat thread, parsed a block at a time so a delta only parses the block that grows.
138
+ * While it streams every new word fades in, and a fence that has not closed yet is highlighted a
139
+ * line at a time as it grows. `arriving` fades in each block as it is added instead, for a reply
140
+ * that is shown a block at a time.
141
+ */
142
+ export const ReplyMarkdown = memo(function ReplyMarkdown({ text, streaming, arriving = false }) {
143
+ const blocks = useMemo(() => splitMarkdownBlocks(text), [text]);
144
+ return (_jsx("div", { className: "chat-markdown prose prose-sm max-w-none text-sm", "data-arriving": arriving || undefined, children: blocks.map((block, index) => (_jsx(ReplyBlock, { text: block.text, fade: streaming, open: streaming && block.openFence }, index))) }));
145
+ });
146
+ /*
147
+ * A message a person sent. Enter meant a line break, a tag typed without backticks stays the text
148
+ * it was, and the files and skills picked in the composer are chips again.
149
+ */
150
+ export const MessageMarkdown = memo(function MessageMarkdown({ text, mentions, skills }) {
151
+ const rehypePlugins = useMemo(() => [[rehypeChips, { mentions, skills }]], [mentions, skills]);
152
+ return (_jsx("div", { className: "chat-markdown prose prose-sm max-w-none text-sm", children: _jsx(ReactMarkdown, { remarkPlugins: MESSAGE_PLUGINS, rehypePlugins: rehypePlugins, components: messageComponents, children: text }) }));
153
+ });
154
+ /* `breaks` is for person-authored notes; applying it to CLI markdown would change its layout. */
155
+ export const Markdown = memo(function Markdown({ text, breaks = false, fileLinks = true, rehypePlugins, componentOverrides }) {
156
+ const renderComponents = useMemo(() => ({ ...(fileLinks ? components : plainComponents), ...componentOverrides }), [fileLinks, componentOverrides]);
157
+ return (_jsx("div", { className: "chat-markdown prose prose-sm max-w-none text-sm", children: _jsx(ReactMarkdown, { remarkPlugins: breaks ? PLUGINS_WITH_BREAKS : PLUGINS, rehypePlugins: rehypePlugins, components: renderComponents, children: text }) }));
158
+ });
@@ -0,0 +1,9 @@
1
+ import type { TimelineRow } from '../logic/timeline';
2
+ type MessageRow = Extract<TimelineRow, {
3
+ kind: 'user' | 'assistant';
4
+ }>;
5
+ export declare function MessageActions({ chatId, row }: {
6
+ chatId: string;
7
+ row: MessageRow;
8
+ }): import("react").JSX.Element;
9
+ export {};