@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.
- package/LICENSE +110 -0
- package/README.md +142 -1
- package/dist/agents/AccountDot.d.ts +4 -0
- package/dist/agents/AccountDot.js +8 -0
- package/dist/agents/AgentIcon.d.ts +6 -0
- package/dist/agents/AgentIcon.js +20 -0
- package/dist/agents/ProviderLogo.d.ts +8 -0
- package/dist/agents/ProviderLogo.js +8 -0
- package/dist/agents/account-limits.d.ts +7 -0
- package/dist/agents/account-limits.js +26 -0
- package/dist/agents/accounts.d.ts +22 -0
- package/dist/agents/accounts.js +103 -0
- package/dist/agents/model-name.d.ts +6 -0
- package/dist/agents/model-name.js +44 -0
- package/dist/agents/provider-paths.d.ts +2 -0
- package/dist/agents/provider-paths.js +8 -0
- package/dist/agents/status-look.d.ts +13 -0
- package/dist/agents/status-look.js +22 -0
- package/dist/chat/account-choice.d.ts +11 -0
- package/dist/chat/account-choice.js +30 -0
- package/dist/chat/actions.d.ts +6 -0
- package/dist/chat/actions.js +64 -0
- package/dist/chat/activity.d.ts +5 -0
- package/dist/chat/activity.js +4 -0
- package/dist/chat/attachments.d.ts +23 -0
- package/dist/chat/attachments.js +70 -0
- package/dist/chat/bookmarks.d.ts +14 -0
- package/dist/chat/bookmarks.js +73 -0
- package/dist/chat/chat-client.d.ts +53 -0
- package/dist/chat/chat-client.js +232 -0
- package/dist/chat/chat-references.d.ts +5 -0
- package/dist/chat/chat-references.js +5 -0
- package/dist/chat/drafts.d.ts +29 -0
- package/dist/chat/drafts.js +104 -0
- package/dist/chat/forks.d.ts +2 -0
- package/dist/chat/forks.js +26 -0
- package/dist/chat/guards.d.ts +12 -0
- package/dist/chat/guards.js +63 -0
- package/dist/chat/logic/answers.d.ts +3 -0
- package/dist/chat/logic/answers.js +14 -0
- package/dist/chat/logic/bookmarks.d.ts +5 -0
- package/dist/chat/logic/bookmarks.js +31 -0
- package/dist/chat/logic/context-usage.d.ts +12 -0
- package/dist/chat/logic/context-usage.js +20 -0
- package/dist/chat/logic/fork.d.ts +44 -0
- package/dist/chat/logic/fork.js +138 -0
- package/dist/chat/logic/handback.d.ts +2 -0
- package/dist/chat/logic/handback.js +8 -0
- package/dist/chat/logic/json.d.ts +1 -0
- package/dist/chat/logic/json.js +2 -0
- package/dist/chat/logic/limit.d.ts +7 -0
- package/dist/chat/logic/limit.js +34 -0
- package/dist/chat/logic/resume-compaction.d.ts +14 -0
- package/dist/chat/logic/resume-compaction.js +47 -0
- package/dist/chat/logic/scrubber.d.ts +50 -0
- package/dist/chat/logic/scrubber.js +216 -0
- package/dist/chat/logic/timeline-copy.d.ts +4 -0
- package/dist/chat/logic/timeline-copy.js +51 -0
- package/dist/chat/logic/timeline-target.d.ts +12 -0
- package/dist/chat/logic/timeline-target.js +34 -0
- package/dist/chat/logic/timeline.d.ts +110 -0
- package/dist/chat/logic/timeline.js +339 -0
- package/dist/chat/logic/tool-catalog.d.ts +10 -0
- package/dist/chat/logic/tool-catalog.js +40 -0
- package/dist/chat/logic/tools.d.ts +15 -0
- package/dist/chat/logic/tools.js +79 -0
- package/dist/chat/logic/workflow.d.ts +14 -0
- package/dist/chat/logic/workflow.js +47 -0
- package/dist/chat/mentions.d.ts +46 -0
- package/dist/chat/mentions.js +128 -0
- package/dist/chat/persisted-json.d.ts +5 -0
- package/dist/chat/persisted-json.js +24 -0
- package/dist/chat/preferences.d.ts +23 -0
- package/dist/chat/preferences.js +110 -0
- package/dist/chat/quote.d.ts +2 -0
- package/dist/chat/quote.js +25 -0
- package/dist/chat/recent-messages.d.ts +17 -0
- package/dist/chat/recent-messages.js +28 -0
- package/dist/chat/resume-compaction-dismissals.d.ts +3 -0
- package/dist/chat/resume-compaction-dismissals.js +14 -0
- package/dist/chat/runtime-modes.d.ts +4 -0
- package/dist/chat/runtime-modes.js +11 -0
- package/dist/chat/stash.d.ts +24 -0
- package/dist/chat/stash.js +88 -0
- package/dist/chat/subagent-conversation.d.ts +38 -0
- package/dist/chat/subagent-conversation.js +150 -0
- package/dist/chat/subagent-list.d.ts +16 -0
- package/dist/chat/subagent-list.js +76 -0
- package/dist/chat/subagent-support.d.ts +6 -0
- package/dist/chat/subagent-support.js +14 -0
- package/dist/chat/subagent-view.d.ts +33 -0
- package/dist/chat/subagent-view.js +65 -0
- package/dist/chat/thumbnails.d.ts +10 -0
- package/dist/chat/thumbnails.js +87 -0
- package/dist/chat/timeline-scroll.d.ts +16 -0
- package/dist/chat/timeline-scroll.js +100 -0
- package/dist/chat/ui/AccountPill.d.ts +3 -0
- package/dist/chat/ui/AccountPill.js +24 -0
- package/dist/chat/ui/BookmarkMarker.d.ts +8 -0
- package/dist/chat/ui/BookmarkMarker.js +59 -0
- package/dist/chat/ui/BookmarkSubmenu.d.ts +3 -0
- package/dist/chat/ui/BookmarkSubmenu.js +44 -0
- package/dist/chat/ui/ChatActivity.d.ts +3 -0
- package/dist/chat/ui/ChatActivity.js +87 -0
- package/dist/chat/ui/ChatReferenceChip.d.ts +4 -0
- package/dist/chat/ui/ChatReferenceChip.js +17 -0
- package/dist/chat/ui/CodeBlock.d.ts +4 -0
- package/dist/chat/ui/CodeBlock.js +130 -0
- package/dist/chat/ui/Composer.d.ts +14 -0
- package/dist/chat/ui/Composer.js +821 -0
- package/dist/chat/ui/ComposerInput.d.ts +28 -0
- package/dist/chat/ui/ComposerInput.js +118 -0
- package/dist/chat/ui/DiffPool.d.ts +5 -0
- package/dist/chat/ui/DiffPool.js +26 -0
- package/dist/chat/ui/EditDiff.d.ts +4 -0
- package/dist/chat/ui/EditDiff.js +21 -0
- package/dist/chat/ui/FadingWords.d.ts +3 -0
- package/dist/chat/ui/FadingWords.js +5 -0
- package/dist/chat/ui/ImageView.d.ts +6 -0
- package/dist/chat/ui/ImageView.js +85 -0
- package/dist/chat/ui/LimitState.d.ts +8 -0
- package/dist/chat/ui/LimitState.js +117 -0
- package/dist/chat/ui/Markdown.d.ts +18 -0
- package/dist/chat/ui/Markdown.js +158 -0
- package/dist/chat/ui/MessageActions.d.ts +9 -0
- package/dist/chat/ui/MessageActions.js +56 -0
- package/dist/chat/ui/Pickers.d.ts +15 -0
- package/dist/chat/ui/Pickers.js +110 -0
- package/dist/chat/ui/PromptComposer.d.ts +13 -0
- package/dist/chat/ui/PromptComposer.js +48 -0
- package/dist/chat/ui/QuoteButton.d.ts +7 -0
- package/dist/chat/ui/QuoteButton.js +76 -0
- package/dist/chat/ui/ResumeCompactionDock.d.ts +5 -0
- package/dist/chat/ui/ResumeCompactionDock.js +15 -0
- package/dist/chat/ui/RunSettings.d.ts +25 -0
- package/dist/chat/ui/RunSettings.js +148 -0
- package/dist/chat/ui/Scrubber.d.ts +17 -0
- package/dist/chat/ui/Scrubber.js +121 -0
- package/dist/chat/ui/SubagentControls.d.ts +11 -0
- package/dist/chat/ui/SubagentControls.js +37 -0
- package/dist/chat/ui/SubagentStopButton.d.ts +7 -0
- package/dist/chat/ui/SubagentStopButton.js +36 -0
- package/dist/chat/ui/SubagentTimeline.d.ts +4 -0
- package/dist/chat/ui/SubagentTimeline.js +92 -0
- package/dist/chat/ui/Timeline.d.ts +5 -0
- package/dist/chat/ui/Timeline.js +318 -0
- package/dist/chat/ui/TimelineMenu.d.ts +12 -0
- package/dist/chat/ui/TimelineMenu.js +61 -0
- package/dist/chat/ui/UnifiedDiff.d.ts +8 -0
- package/dist/chat/ui/UnifiedDiff.js +51 -0
- package/dist/chat/ui/UploadThumb.d.ts +4 -0
- package/dist/chat/ui/UploadThumb.js +11 -0
- package/dist/chat/ui/chips.d.ts +4 -0
- package/dist/chat/ui/chips.js +11 -0
- package/dist/chat/ui/code-lines.d.ts +18 -0
- package/dist/chat/ui/code-lines.js +46 -0
- package/dist/chat/ui/code-streaming.d.ts +1 -0
- package/dist/chat/ui/code-streaming.js +3 -0
- package/dist/chat/ui/code-theme.d.ts +3 -0
- package/dist/chat/ui/code-theme.js +10 -0
- package/dist/chat/ui/composer/chips.d.ts +6 -0
- package/dist/chat/ui/composer/chips.js +29 -0
- package/dist/chat/ui/composer/editor.d.ts +11 -0
- package/dist/chat/ui/composer/editor.js +94 -0
- package/dist/chat/ui/composer/keys.d.ts +30 -0
- package/dist/chat/ui/composer/keys.js +98 -0
- package/dist/chat/ui/diff-theme.d.ts +2 -0
- package/dist/chat/ui/diff-theme.js +20 -0
- package/dist/chat/ui/file-links.d.ts +5 -0
- package/dist/chat/ui/file-links.js +18 -0
- package/dist/chat/ui/find-reveal.d.ts +7 -0
- package/dist/chat/ui/find-reveal.js +13 -0
- package/dist/chat/ui/icons.d.ts +4 -0
- package/dist/chat/ui/icons.js +20 -0
- package/dist/chat/ui/markdown-blocks.d.ts +6 -0
- package/dist/chat/ui/markdown-blocks.js +101 -0
- package/dist/chat/ui/quote-selection.d.ts +3 -0
- package/dist/chat/ui/quote-selection.js +44 -0
- package/dist/chat/ui/rehype-chips.d.ts +23 -0
- package/dist/chat/ui/rehype-chips.js +28 -0
- package/dist/chat/ui/rehype-fade.d.ts +23 -0
- package/dist/chat/ui/rehype-fade.js +30 -0
- package/dist/chat/ui/remark-html-as-text.d.ts +8 -0
- package/dist/chat/ui/remark-html-as-text.js +24 -0
- package/dist/chat/ui/reveal.d.ts +13 -0
- package/dist/chat/ui/reveal.js +112 -0
- package/dist/chat/ui/rows/ForksRow.d.ts +4 -0
- package/dist/chat/ui/rows/ForksRow.js +40 -0
- package/dist/chat/ui/rows/MessageRows.d.ts +36 -0
- package/dist/chat/ui/rows/MessageRows.js +190 -0
- package/dist/chat/ui/rows/Rows.d.ts +12 -0
- package/dist/chat/ui/rows/Rows.js +49 -0
- package/dist/chat/ui/rows/SubagentRow.d.ts +15 -0
- package/dist/chat/ui/rows/SubagentRow.js +98 -0
- package/dist/chat/ui/rows/WorkRows.d.ts +46 -0
- package/dist/chat/ui/rows/WorkRows.js +143 -0
- package/dist/chat/ui/rows/WorkflowRow.d.ts +7 -0
- package/dist/chat/ui/rows/WorkflowRow.js +37 -0
- package/dist/chat/ui/rows/row-rhythm.d.ts +3 -0
- package/dist/chat/ui/rows/row-rhythm.js +14 -0
- package/dist/chat/ui/useToggleSet.d.ts +6 -0
- package/dist/chat/ui/useToggleSet.js +25 -0
- package/dist/host.d.ts +167 -0
- package/dist/host.js +41 -0
- package/dist/lazy.d.ts +5 -0
- package/dist/lazy.js +35 -0
- package/dist/locales/en/agent-chat.json +417 -0
- package/dist/locales/en/agent-prompts.json +42 -0
- package/dist/locales/en/agent-providers.json +131 -0
- package/dist/locales/en/agent-usage.json +131 -0
- package/dist/locales/nl/agent-chat.json +417 -0
- package/dist/locales/nl/agent-prompts.json +42 -0
- package/dist/locales/nl/agent-providers.json +131 -0
- package/dist/locales/nl/agent-usage.json +131 -0
- package/dist/locales.d.ts +5 -0
- package/dist/locales.js +18 -0
- package/dist/mounted-registry.d.ts +7 -0
- package/dist/mounted-registry.js +39 -0
- package/dist/port-transport.d.ts +6 -0
- package/dist/port-transport.js +120 -0
- package/dist/prompts/logic/focus.d.ts +1 -0
- package/dist/prompts/logic/focus.js +17 -0
- package/dist/prompts/logic/keys.d.ts +42 -0
- package/dist/prompts/logic/keys.js +84 -0
- package/dist/prompts/logic/prompts.d.ts +34 -0
- package/dist/prompts/logic/prompts.fixtures.d.ts +5 -0
- package/dist/prompts/logic/prompts.fixtures.js +73 -0
- package/dist/prompts/logic/prompts.js +22 -0
- package/dist/prompts/logic/subjects.d.ts +49 -0
- package/dist/prompts/logic/subjects.js +56 -0
- package/dist/prompts/logic/useFocusAfterAnswer.d.ts +6 -0
- package/dist/prompts/logic/useFocusAfterAnswer.js +28 -0
- package/dist/prompts/logic/usePromptSession.d.ts +24 -0
- package/dist/prompts/logic/usePromptSession.js +87 -0
- package/dist/prompts/ui/ApprovalBody.d.ts +13 -0
- package/dist/prompts/ui/ApprovalBody.js +35 -0
- package/dist/prompts/ui/PromptActions.d.ts +26 -0
- package/dist/prompts/ui/PromptActions.js +19 -0
- package/dist/prompts/ui/PromptCard.d.ts +16 -0
- package/dist/prompts/ui/PromptCard.js +50 -0
- package/dist/prompts/ui/PromptView.d.ts +2 -0
- package/dist/prompts/ui/PromptView.js +47 -0
- package/dist/prompts/ui/QuestionBody.d.ts +9 -0
- package/dist/prompts/ui/QuestionBody.js +87 -0
- package/dist/providers/AccountColors.d.ts +5 -0
- package/dist/providers/AccountColors.js +8 -0
- package/dist/providers/AccountDetail.d.ts +12 -0
- package/dist/providers/AccountDetail.js +76 -0
- package/dist/providers/AccountVariables.d.ts +8 -0
- package/dist/providers/AccountVariables.js +48 -0
- package/dist/providers/AddAccountForm.d.ts +10 -0
- package/dist/providers/AddAccountForm.js +58 -0
- package/dist/providers/CliDetail.d.ts +11 -0
- package/dist/providers/CliDetail.js +62 -0
- package/dist/providers/ProvidersPane.d.ts +8 -0
- package/dist/providers/ProvidersPane.js +90 -0
- package/dist/providers/account-actions.d.ts +7 -0
- package/dist/providers/account-actions.js +53 -0
- package/dist/providers/parts.d.ts +9 -0
- package/dist/providers/parts.js +18 -0
- package/dist/providers/provider-abilities.d.ts +2 -0
- package/dist/providers/provider-abilities.js +7 -0
- package/dist/providers/variables.d.ts +13 -0
- package/dist/providers/variables.js +53 -0
- package/dist/scope.d.ts +11 -0
- package/dist/scope.js +10 -0
- package/dist/settings/sections.d.ts +13 -0
- package/dist/settings/sections.js +30 -0
- package/dist/state/chats.d.ts +32 -0
- package/dist/state/chats.js +124 -0
- package/dist/state/provider-accounts.d.ts +17 -0
- package/dist/state/provider-accounts.js +63 -0
- package/dist/state/providers.d.ts +17 -0
- package/dist/state/providers.js +29 -0
- package/dist/state/usage.d.ts +41 -0
- package/dist/state/usage.js +100 -0
- package/dist/theme.css +186 -0
- package/dist/transport.d.ts +24 -0
- package/dist/transport.js +16 -0
- package/dist/ui/PlainTextarea.d.ts +2 -0
- package/dist/ui/PlainTextarea.js +5 -0
- package/dist/usage/LimitsList.d.ts +18 -0
- package/dist/usage/LimitsList.js +69 -0
- package/dist/usage/UsageBreakdown.d.ts +9 -0
- package/dist/usage/UsageBreakdown.js +69 -0
- package/dist/usage/UsageChart.d.ts +10 -0
- package/dist/usage/UsageChart.js +49 -0
- package/dist/usage/UsageDialog.d.ts +6 -0
- package/dist/usage/UsageDialog.js +15 -0
- package/dist/usage/UsageLimits.d.ts +1 -0
- package/dist/usage/UsageLimits.js +62 -0
- package/dist/usage/UsageLimitsCard.d.ts +4 -0
- package/dist/usage/UsageLimitsCard.js +27 -0
- package/dist/usage/UsagePage.d.ts +7 -0
- package/dist/usage/UsagePage.js +161 -0
- package/dist/usage/UsagePane.d.ts +3 -0
- package/dist/usage/UsagePane.js +24 -0
- package/dist/usage/UsageSummary.d.ts +12 -0
- package/dist/usage/UsageSummary.js +15 -0
- package/dist/usage/UsageTiles.d.ts +7 -0
- package/dist/usage/UsageTiles.js +19 -0
- package/dist/usage/format.d.ts +14 -0
- package/dist/usage/format.js +50 -0
- package/dist/usage/limit-groups.d.ts +22 -0
- package/dist/usage/limit-groups.js +78 -0
- package/dist/usage/limits.d.ts +4 -0
- package/dist/usage/limits.js +58 -0
- package/dist/usage/money.d.ts +1 -0
- package/dist/usage/money.js +13 -0
- package/dist/usage/summary.d.ts +34 -0
- package/dist/usage/summary.js +90 -0
- package/package.json +639 -2
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { history, historyKeymap, standardKeymap } from '@codemirror/commands';
|
|
2
|
+
import { Language, defineLanguageFacet, indentUnit, syntaxHighlighting, syntaxTree } from '@codemirror/language';
|
|
3
|
+
import { Annotation } from '@codemirror/state';
|
|
4
|
+
import { Decoration, EditorView, ViewPlugin, keymap } from '@codemirror/view';
|
|
5
|
+
import { tagHighlighter, tags } from '@lezer/highlight';
|
|
6
|
+
import { Strikethrough, Table, parser } from '@lezer/markdown';
|
|
7
|
+
/* Marks a transaction that brings the editor in line with a value set from outside, so it is not reported back. */
|
|
8
|
+
export const externalChange = Annotation.define();
|
|
9
|
+
/*
|
|
10
|
+
* The markdown grammar in a Language of our own. `@codemirror/lang-markdown` builds the same thing
|
|
11
|
+
* but creates the HTML language and autocomplete at module level, which tree shaking cannot drop.
|
|
12
|
+
*/
|
|
13
|
+
export const markdownLanguage = new Language(defineLanguageFacet(), parser.configure([Strikethrough, Table]), [], 'markdown');
|
|
14
|
+
/* Classes rather than generated styles, so the look lives in `styles.css` on the theme's tokens. */
|
|
15
|
+
const markdownClasses = tagHighlighter([
|
|
16
|
+
{ tag: tags.strong, class: 'cm-md-strong' },
|
|
17
|
+
{ tag: tags.emphasis, class: 'cm-md-emphasis' },
|
|
18
|
+
{ tag: tags.strikethrough, class: 'cm-md-strike' },
|
|
19
|
+
{ tag: tags.heading, class: 'cm-md-heading' },
|
|
20
|
+
{ tag: [tags.link, tags.url], class: 'cm-md-link' },
|
|
21
|
+
{ tag: tags.processingInstruction, class: 'cm-md-mark' }
|
|
22
|
+
]);
|
|
23
|
+
/* The code spans and fenced blocks between `from` and `to`, as far as the parser has got. */
|
|
24
|
+
export const codeRanges = (state, from = 0, to = state.doc.length) => {
|
|
25
|
+
const found = [];
|
|
26
|
+
syntaxTree(state).iterate({
|
|
27
|
+
from,
|
|
28
|
+
to,
|
|
29
|
+
enter: (node) => {
|
|
30
|
+
if (node.name === 'InlineCode' || node.name === 'FencedCode') {
|
|
31
|
+
found.push({ from: node.from, to: node.to, kind: node.name === 'InlineCode' ? 'inline' : 'fence' });
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return found;
|
|
38
|
+
};
|
|
39
|
+
const inlineCodeMark = Decoration.mark({ class: 'cm-md-code' });
|
|
40
|
+
const fenceLine = (first, last) => Decoration.line({ class: ['cm-md-fence', first ? 'cm-md-fence-start' : '', last ? 'cm-md-fence-end' : ''].join(' ').trim() });
|
|
41
|
+
/*
|
|
42
|
+
* Code is decorated from the tree rather than highlighted per token: the highlighter styles the
|
|
43
|
+
* backticks apart from what they wrap, and a block's background belongs to its whole lines.
|
|
44
|
+
*/
|
|
45
|
+
const codeDecorations = (view) => {
|
|
46
|
+
const { state } = view;
|
|
47
|
+
const decorations = [];
|
|
48
|
+
const fenceLines = new Set();
|
|
49
|
+
for (const visible of view.visibleRanges) {
|
|
50
|
+
for (const range of codeRanges(state, visible.from, visible.to)) {
|
|
51
|
+
if (range.kind === 'inline') {
|
|
52
|
+
decorations.push(inlineCodeMark.range(range.from, range.to));
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const first = state.doc.lineAt(range.from).number;
|
|
56
|
+
const last = state.doc.lineAt(range.to).number;
|
|
57
|
+
const start = state.doc.lineAt(Math.max(range.from, visible.from)).number;
|
|
58
|
+
const end = state.doc.lineAt(Math.min(range.to, visible.to)).number;
|
|
59
|
+
for (let number = start; number <= end; number++) {
|
|
60
|
+
if (!fenceLines.has(number)) {
|
|
61
|
+
fenceLines.add(number);
|
|
62
|
+
decorations.push(fenceLine(number === first, number === last).range(state.doc.line(number).from));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return Decoration.set(decorations, true);
|
|
68
|
+
};
|
|
69
|
+
const codeDecorationPlugin = ViewPlugin.fromClass(class {
|
|
70
|
+
decorations;
|
|
71
|
+
constructor(view) {
|
|
72
|
+
this.decorations = codeDecorations(view);
|
|
73
|
+
}
|
|
74
|
+
update(update) {
|
|
75
|
+
if (update.docChanged || update.viewportChanged || syntaxTree(update.startState) !== syntaxTree(update.state)) {
|
|
76
|
+
this.decorations = codeDecorations(update.view);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}, { decorations: (plugin) => plugin.decorations });
|
|
80
|
+
/*
|
|
81
|
+
* What every composer editor carries. The standard keymap rather than the default one: the default
|
|
82
|
+
* adds code editor commands (move a line, indent, select the syntax parent) on keys a prompt box
|
|
83
|
+
* has no business taking.
|
|
84
|
+
*/
|
|
85
|
+
export const composerEditorExtensions = () => [
|
|
86
|
+
history(),
|
|
87
|
+
keymap.of([...standardKeymap, ...historyKeymap]),
|
|
88
|
+
EditorView.lineWrapping,
|
|
89
|
+
// What Tab and Shift+Tab in a fence add and take away, so a selection indents in the same steps as a caret.
|
|
90
|
+
indentUnit.of(' '),
|
|
91
|
+
markdownLanguage,
|
|
92
|
+
syntaxHighlighting(markdownClasses),
|
|
93
|
+
codeDecorationPlugin
|
|
94
|
+
];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Tree } from '@lezer/common';
|
|
2
|
+
export type EnterAction = 'send' | 'newline' | 'continue-list' | 'leave-list';
|
|
3
|
+
export interface EnterKeys {
|
|
4
|
+
shift: boolean;
|
|
5
|
+
mod: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ListItem {
|
|
8
|
+
prefix: string;
|
|
9
|
+
next: string;
|
|
10
|
+
empty: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface EnterContext {
|
|
13
|
+
inOpenFence: boolean;
|
|
14
|
+
list: ListItem | null;
|
|
15
|
+
column: number;
|
|
16
|
+
}
|
|
17
|
+
export declare const listItemAt: (line: string) => ListItem | null;
|
|
18
|
+
export declare const enterAction: (keys: EnterKeys, { inOpenFence, list, column }: EnterContext) => EnterAction;
|
|
19
|
+
export declare const inOpenFence: (tree: Tree, pos: number) => boolean;
|
|
20
|
+
export declare const inFenceBody: (tree: Tree, text: string, pos: number) => boolean;
|
|
21
|
+
export declare const tabSpaces: (column: number) => string;
|
|
22
|
+
export declare const inCode: (tree: Tree, text: string, pos: number) => boolean;
|
|
23
|
+
export interface RecallContext {
|
|
24
|
+
key: string;
|
|
25
|
+
text: string;
|
|
26
|
+
from: number;
|
|
27
|
+
to: number;
|
|
28
|
+
recalled: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare const recallDirection: ({ key, text, from, to, recalled }: RecallContext) => -1 | 1 | null;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const LIST_ITEM = /^([ \t]*)(?:([-*+])|(\d{1,9})([.)]))([ \t]+)(\[[ xX]\](?:[ \t]+|$))?/;
|
|
2
|
+
/* The list item a line starts, if it starts one. */
|
|
3
|
+
export const listItemAt = (line) => {
|
|
4
|
+
const match = LIST_ITEM.exec(line);
|
|
5
|
+
if (match === null) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const [prefix, indent, bullet, number, delimiter, space, task] = match;
|
|
9
|
+
const marker = bullet ?? `${Number(number) + 1}${delimiter}`;
|
|
10
|
+
return { prefix, next: `${indent}${marker}${space}${task ? '[ ] ' : ''}`, empty: line.slice(prefix.length).trim() === '' };
|
|
11
|
+
};
|
|
12
|
+
/*
|
|
13
|
+
* Enter sends, except where whoever types is still writing: inside a fence nobody has closed yet
|
|
14
|
+
* it adds a line, and in a list it starts the next item, or leaves the list from an empty one, so
|
|
15
|
+
* a second Enter breaks out. Shift adds a plain line and Mod sends from anywhere.
|
|
16
|
+
*/
|
|
17
|
+
export const enterAction = (keys, { inOpenFence, list, column }) => {
|
|
18
|
+
if (keys.mod) {
|
|
19
|
+
return 'send';
|
|
20
|
+
}
|
|
21
|
+
if (keys.shift || inOpenFence) {
|
|
22
|
+
return 'newline';
|
|
23
|
+
}
|
|
24
|
+
if (list === null) {
|
|
25
|
+
return 'send';
|
|
26
|
+
}
|
|
27
|
+
// A caret in front of the marker moves the item down rather than splitting its marker.
|
|
28
|
+
if (column < list.prefix.length) {
|
|
29
|
+
return 'newline';
|
|
30
|
+
}
|
|
31
|
+
return list.empty ? 'leave-list' : 'continue-list';
|
|
32
|
+
};
|
|
33
|
+
/* Whether `pos` sits inside a fenced code block that has an opening fence and no closing one yet. */
|
|
34
|
+
export const inOpenFence = (tree, pos) => {
|
|
35
|
+
for (let node = tree.resolveInner(pos, -1); node !== null; node = node.parent) {
|
|
36
|
+
if (node.name === 'FencedCode') {
|
|
37
|
+
return pos > node.from && node.getChildren('CodeMark').length < 2;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
};
|
|
42
|
+
/* Whether `pos` sits in the body of a fenced code block, open or closed: below its opening line and before its end. */
|
|
43
|
+
export const inFenceBody = (tree, text, pos) => {
|
|
44
|
+
for (let node = tree.resolveInner(pos, -1); node !== null; node = node.parent) {
|
|
45
|
+
if (node.name === 'FencedCode') {
|
|
46
|
+
const openingEnd = text.indexOf('\n', node.from);
|
|
47
|
+
return openingEnd !== -1 && pos > openingEnd && (pos < node.to || node.getChildren('CodeMark').length < 2);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
};
|
|
52
|
+
/* The spaces Tab inserts at `column`, up to the next stop of four, the way a code editor lines up. */
|
|
53
|
+
export const tabSpaces = (column) => ' '.repeat(4 - (column % 4));
|
|
54
|
+
const INLINE_BLOCK = /^(Paragraph|ATXHeading\d|SetextHeading\d)$/;
|
|
55
|
+
/*
|
|
56
|
+
* Whether `pos` sits inside code: a fence, open or closed, or a code span. The parser only calls a
|
|
57
|
+
* span code once it is closed, so a backtick nobody has closed yet earlier in the same paragraph
|
|
58
|
+
* counts as well: whoever types after it is writing code.
|
|
59
|
+
*/
|
|
60
|
+
export const inCode = (tree, text, pos) => {
|
|
61
|
+
let blockFrom = null;
|
|
62
|
+
for (let node = tree.resolveInner(pos, -1); node !== null; node = node.parent) {
|
|
63
|
+
if (node.name === 'FencedCode') {
|
|
64
|
+
return pos > node.from && (pos < node.to || node.getChildren('CodeMark').length < 2);
|
|
65
|
+
}
|
|
66
|
+
if (node.name === 'InlineCode' && pos > node.from && pos < node.to) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
if (blockFrom === null && INLINE_BLOCK.test(node.name)) {
|
|
70
|
+
blockFrom = node.from;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
let from = blockFrom ?? text.lastIndexOf('\n', pos - 1) + 1;
|
|
74
|
+
tree.iterate({
|
|
75
|
+
from,
|
|
76
|
+
to: pos,
|
|
77
|
+
enter: (node) => {
|
|
78
|
+
if (node.name === 'InlineCode' && node.to <= pos) {
|
|
79
|
+
from = Math.max(from, node.to);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return /(^|[^\\])`/.test(text.slice(from, pos));
|
|
84
|
+
};
|
|
85
|
+
/*
|
|
86
|
+
* Which way the arrow walks through earlier prompts, or null when the arrow just moves the caret.
|
|
87
|
+
* Only an empty box or an unedited recall walks, so an arrow in a prompt being written never
|
|
88
|
+
* throws it away.
|
|
89
|
+
*/
|
|
90
|
+
export const recallDirection = ({ key, text, from, to, recalled }) => {
|
|
91
|
+
if (key === 'ArrowUp' && (text === '' || (recalled && from === 0))) {
|
|
92
|
+
return -1;
|
|
93
|
+
}
|
|
94
|
+
if (key === 'ArrowDown' && recalled && to === text.length) {
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { registerCustomTheme } from '@pierre/diffs';
|
|
3
|
+
import { chatHost } from '../../host';
|
|
4
|
+
let registered = false;
|
|
5
|
+
/* The library resolves Shiki's bundled themes by id on its own; the app's own it only knows by registration, done once the host is set. */
|
|
6
|
+
const registerThemes = () => {
|
|
7
|
+
if (registered) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
registered = true;
|
|
11
|
+
for (const theme of chatHost().code.custom) {
|
|
12
|
+
registerCustomTheme(theme.name, () => Promise.resolve(theme));
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
/* The code themes a diff is drawn in, the same pair a chat's code blocks use. */
|
|
16
|
+
export const useDiffTheme = () => {
|
|
17
|
+
registerThemes();
|
|
18
|
+
const { light, dark } = chatHost().code.useThemes();
|
|
19
|
+
return useMemo(() => ({ light, dark }), [light, dark]);
|
|
20
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type FileRef } from '../../host';
|
|
2
|
+
export declare const FileLinkContext: import("react").Context<string | null>;
|
|
3
|
+
export declare const useFileLinkCwd: () => string | null;
|
|
4
|
+
export declare const useFileLinkTarget: (text: string) => FileRef | null;
|
|
5
|
+
export declare const openFileLink: (cwd: string | null, ref: FileRef) => void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
2
|
+
import { chatHost } from '../../host';
|
|
3
|
+
/*
|
|
4
|
+
* The folder a relative reference in rendered text counts from: a chat's own cwd, which is a
|
|
5
|
+
* worktree as often as it is the project, or the folder of the file an app is drawing. Null where
|
|
6
|
+
* nobody said, and there only an absolute path is a link.
|
|
7
|
+
*/
|
|
8
|
+
export const FileLinkContext = createContext(null);
|
|
9
|
+
export const useFileLinkCwd = () => useContext(FileLinkContext);
|
|
10
|
+
/* The reference a piece of text names, only where this spot on screen could open it. */
|
|
11
|
+
export const useFileLinkTarget = (text) => {
|
|
12
|
+
const cwd = useFileLinkCwd();
|
|
13
|
+
return useMemo(() => chatHost().fileLinks?.target(text, cwd) ?? null, [text, cwd]);
|
|
14
|
+
};
|
|
15
|
+
/* A reference followed, the way the app opens a file or a folder. */
|
|
16
|
+
export const openFileLink = (cwd, ref) => {
|
|
17
|
+
chatHost().fileLinks?.open(cwd, ref);
|
|
18
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type ChatFindField = 'summary' | 'text' | 'output';
|
|
2
|
+
export interface FindReveal {
|
|
3
|
+
itemId: string;
|
|
4
|
+
field: ChatFindField;
|
|
5
|
+
}
|
|
6
|
+
export declare const FindRevealContext: import("react").Context<FindReveal | null>;
|
|
7
|
+
export declare const useOpenForFind: (itemId: string, field: ChatFindField, setOpen: (open: boolean) => void) => void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect } from 'react';
|
|
2
|
+
/* The hit the find bar is on, so the row that folds it away can open. */
|
|
3
|
+
export const FindRevealContext = createContext(null);
|
|
4
|
+
/* Opens a fold of this item while the find bar is on a hit inside it; it stays open once the bar moves on, as if a person opened it. */
|
|
5
|
+
export const useOpenForFind = (itemId, field, setOpen) => {
|
|
6
|
+
const reveal = useContext(FindRevealContext);
|
|
7
|
+
const wanted = reveal !== null && reveal.itemId === itemId && reveal.field === field;
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (wanted) {
|
|
10
|
+
setOpen(true);
|
|
11
|
+
}
|
|
12
|
+
}, [wanted, reveal, setOpen]);
|
|
13
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Hammer, Wrench } from 'lucide-react';
|
|
3
|
+
import { toolEntry } from '../logic/tool-catalog';
|
|
4
|
+
import { Icon } from '@ruimte/ui/Icon';
|
|
5
|
+
const SIZE = 12;
|
|
6
|
+
/* A pending dock draws its mark a step up from the lines of a transcript, at the size a sidebar
|
|
7
|
+
row uses, because it is a thing to answer rather than a thing to read past. */
|
|
8
|
+
export const DOCK_ICON_SIZE = 14;
|
|
9
|
+
/* The gutter every row in the thread opens with: 16 wide, on the line box of its first row of text,
|
|
10
|
+
so the mark of a tool call, a note and a fold all sit in one column and their labels start at the
|
|
11
|
+
same x. The same box the menu rows and the toasts use. */
|
|
12
|
+
export const ROW_GUTTER = 'grid h-5 w-4 shrink-0 place-items-center';
|
|
13
|
+
/* The icon that says what a tool call is about; unknown tools get the wrench. */
|
|
14
|
+
export const toolIcon = (name, size = SIZE) => {
|
|
15
|
+
const known = toolEntry(name);
|
|
16
|
+
if (known !== undefined) {
|
|
17
|
+
return _jsx(Icon, { icon: known.icon, size: size });
|
|
18
|
+
}
|
|
19
|
+
return name.startsWith('mcp__') ? _jsx(Icon, { icon: Hammer, size: size }) : _jsx(Icon, { icon: Wrench, size: size });
|
|
20
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const FENCE = /^([ \t]*)(`{3,}|~{3,})(.*)$/;
|
|
2
|
+
// Spaces and tabs only: a line that holds a no-break space is text to markdown, not a blank line.
|
|
3
|
+
const BLANK = /^[ \t]*$/;
|
|
4
|
+
// A line that continues what stood before the blank line: an indented paragraph of a list item, or
|
|
5
|
+
// the next item of a loose list. Splitting there would change what the markdown means.
|
|
6
|
+
const CONTINUES = /^([ \t]|[-*+][ \t]|\d+[.)][ \t])/;
|
|
7
|
+
// A reference or footnote definition may be used anywhere in the text, so a block cannot stand alone.
|
|
8
|
+
const DEFINITION = /^ {0,3}\[[^\]]+\]:/m;
|
|
9
|
+
// How much deeper than its opening a closing fence may sit; deeper than that it is a line of code.
|
|
10
|
+
const CLOSING_INDENT = 3;
|
|
11
|
+
// A section title: an ATX heading, or a line of only bold text, which models write as a heading too.
|
|
12
|
+
const HEADING = /^ {0,3}#{1,6}(?:[ \t]|$)/;
|
|
13
|
+
const BOLD_LINE = /^ {0,3}\*\*(?:[^*]|\*(?!\*))+\*\*:?[ \t]*$/;
|
|
14
|
+
/*
|
|
15
|
+
* A reply cut into blocks at blank lines outside a code fence, so each block parses on its own and
|
|
16
|
+
* only the one still growing is parsed again. Joined back together the blocks are the text.
|
|
17
|
+
*/
|
|
18
|
+
export const splitMarkdownBlocks = (text) => {
|
|
19
|
+
if (DEFINITION.test(text)) {
|
|
20
|
+
return [{ text, openFence: isFenceOpenAtEnd(text) }];
|
|
21
|
+
}
|
|
22
|
+
const lines = text.split('\n');
|
|
23
|
+
const blocks = [];
|
|
24
|
+
let start = 0;
|
|
25
|
+
let offset = 0;
|
|
26
|
+
let fence = null;
|
|
27
|
+
let afterBlank = false;
|
|
28
|
+
lines.forEach((line, index) => {
|
|
29
|
+
if (fence === null && afterBlank && !BLANK.test(line) && !CONTINUES.test(line)) {
|
|
30
|
+
blocks.push({ text: text.slice(start, offset), openFence: false });
|
|
31
|
+
start = offset;
|
|
32
|
+
}
|
|
33
|
+
fence = nextFence(fence, line);
|
|
34
|
+
afterBlank = fence === null && BLANK.test(line) && index > 0;
|
|
35
|
+
offset += line.length + 1;
|
|
36
|
+
});
|
|
37
|
+
blocks.push({ text: text.slice(start), openFence: fence !== null });
|
|
38
|
+
return blocks;
|
|
39
|
+
};
|
|
40
|
+
/*
|
|
41
|
+
* The blocks of a reply still being written that will not change any more: every block but the last.
|
|
42
|
+
* A block is only known to be closed once the line after its blank line starts, since that line may
|
|
43
|
+
* still turn out to continue it (a list item, an indented paragraph). A section title waits for the
|
|
44
|
+
* block under it, so it never sits alone above a block that is still streaming.
|
|
45
|
+
*/
|
|
46
|
+
export const settledBlocksText = (text) => withoutTrailingTitles(splitMarkdownBlocks(text)
|
|
47
|
+
.slice(0, -1)
|
|
48
|
+
.map((block) => block.text)
|
|
49
|
+
.join(''));
|
|
50
|
+
const withoutTrailingTitles = (text) => {
|
|
51
|
+
const lines = text.split('\n');
|
|
52
|
+
let end = lines.length;
|
|
53
|
+
for (let index = lines.length - 1; index >= 0; index--) {
|
|
54
|
+
const line = lines[index];
|
|
55
|
+
if (BLANK.test(line)) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
// A bold line right under a line of text continues that paragraph.
|
|
59
|
+
const title = HEADING.test(line) || (BOLD_LINE.test(line) && (index === 0 || BLANK.test(lines[index - 1])));
|
|
60
|
+
if (!title) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
end = index;
|
|
64
|
+
}
|
|
65
|
+
if (end === lines.length) {
|
|
66
|
+
return text;
|
|
67
|
+
}
|
|
68
|
+
return lines
|
|
69
|
+
.slice(0, end)
|
|
70
|
+
.map((line) => `${line}\n`)
|
|
71
|
+
.join('');
|
|
72
|
+
};
|
|
73
|
+
/* The fence a line leaves open: it opens one, closes the one that is open, or changes nothing. */
|
|
74
|
+
const nextFence = (fence, line) => {
|
|
75
|
+
const match = FENCE.exec(line);
|
|
76
|
+
if (!match) {
|
|
77
|
+
return fence;
|
|
78
|
+
}
|
|
79
|
+
const indent = match[1].length;
|
|
80
|
+
const run = match[2];
|
|
81
|
+
const rest = match[3];
|
|
82
|
+
if (fence === null) {
|
|
83
|
+
// A backtick fence's info string may not hold a backtick, or the line is inline code.
|
|
84
|
+
if (run[0] === '`' && rest.includes('`')) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return { marker: run[0], length: run.length, indent };
|
|
88
|
+
}
|
|
89
|
+
// A closing fence carries no info string, and a line far deeper than the opening is code.
|
|
90
|
+
if (run[0] === fence.marker && run.length >= fence.length && BLANK.test(rest) && indent <= fence.indent + CLOSING_INDENT) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return fence;
|
|
94
|
+
};
|
|
95
|
+
const isFenceOpenAtEnd = (text) => {
|
|
96
|
+
let fence = null;
|
|
97
|
+
for (const line of text.split('\n')) {
|
|
98
|
+
fence = nextFence(fence, line);
|
|
99
|
+
}
|
|
100
|
+
return fence !== null;
|
|
101
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type QuoteTaker = (quote: string) => void;
|
|
2
|
+
export declare const QuoteTakerContext: import("react").Context<((take: QuoteTaker) => () => void) | null>;
|
|
3
|
+
export declare const selectedAnswerQuote: (selection: Selection | null, thread: HTMLElement | null) => string | null;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
import { quoteOf } from '../quote';
|
|
3
|
+
/* The text of an answer (`rows/MessageRows.tsx`), the only part of a thread a selection quotes from. */
|
|
4
|
+
const ANSWER = '[data-quote-answer]';
|
|
5
|
+
/* Registers the composer below a thread as the one its quote button hands a quote to, until the returned call. */
|
|
6
|
+
export const QuoteTakerContext = createContext(null);
|
|
7
|
+
const answerAt = (node) => (node instanceof Element ? node : node.parentElement)?.closest(ANSWER) ?? null;
|
|
8
|
+
/*
|
|
9
|
+
* Whether the range selects text a person can see outside the answer. A triple-click on a paragraph
|
|
10
|
+
* ends at the start of the next row, past a heading only a screen reader reads, and still means the
|
|
11
|
+
* paragraph alone.
|
|
12
|
+
*/
|
|
13
|
+
const selectsOutside = (range, answer) => {
|
|
14
|
+
const walker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_TEXT);
|
|
15
|
+
for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
|
|
16
|
+
if (!range.intersectsNode(node) || answer.contains(node)) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const text = node.textContent ?? '';
|
|
20
|
+
const start = node === range.startContainer ? range.startOffset : 0;
|
|
21
|
+
const end = node === range.endContainer ? range.endOffset : text.length;
|
|
22
|
+
const parent = node.parentElement;
|
|
23
|
+
if (text.slice(start, end).trim() !== '' && parent !== null && getComputedStyle(parent).userSelect !== 'none') {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
};
|
|
29
|
+
/* The quote the selection makes, when it lies in one answer of this thread. */
|
|
30
|
+
export const selectedAnswerQuote = (selection, thread) => {
|
|
31
|
+
if (thread === null || selection === null || selection.isCollapsed || selection.rangeCount === 0) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const range = selection.getRangeAt(0);
|
|
35
|
+
const answer = answerAt(range.startContainer) ?? answerAt(range.endContainer);
|
|
36
|
+
if (answer === null || !thread.contains(answer)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const inside = answer.contains(range.startContainer) && answer.contains(range.endContainer);
|
|
40
|
+
if (!inside && selectsOutside(range, answer)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return quoteOf(selection.toString());
|
|
44
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface HastText {
|
|
2
|
+
type: 'text';
|
|
3
|
+
value: string;
|
|
4
|
+
}
|
|
5
|
+
interface HastElement {
|
|
6
|
+
type: 'element';
|
|
7
|
+
tagName: string;
|
|
8
|
+
properties: Record<string, unknown>;
|
|
9
|
+
children: HastNode[];
|
|
10
|
+
}
|
|
11
|
+
type HastNode = HastText | HastElement | {
|
|
12
|
+
type: 'comment' | 'doctype' | 'raw';
|
|
13
|
+
value?: string;
|
|
14
|
+
};
|
|
15
|
+
interface HastParent {
|
|
16
|
+
children: HastNode[];
|
|
17
|
+
}
|
|
18
|
+
export interface ChipOptions {
|
|
19
|
+
mentions?: string[];
|
|
20
|
+
skills?: string[];
|
|
21
|
+
}
|
|
22
|
+
export declare const rehypeChips: ({ mentions, skills }?: ChipOptions) => (tree: HastParent) => void;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { chipText, tokenizeChips } from '../mentions';
|
|
2
|
+
// Inline code overrules a chip: `@a.ts` between backticks is code, not a mention.
|
|
3
|
+
const SKIPPED = new Set(['code', 'pre']);
|
|
4
|
+
/* A chip as an element: the renderer reads `data-chip` and `data-value`, the text keeps the sigil. */
|
|
5
|
+
const chipElement = (segment) => ({
|
|
6
|
+
type: 'element',
|
|
7
|
+
tagName: 'span',
|
|
8
|
+
properties: { dataChip: segment.kind, dataValue: segment.kind === 'mention' ? segment.path : segment.name },
|
|
9
|
+
children: [{ type: 'text', value: chipText(segment) }]
|
|
10
|
+
});
|
|
11
|
+
const split = (parent, mentions, skills) => {
|
|
12
|
+
parent.children = parent.children.flatMap((child) => {
|
|
13
|
+
if (child.type === 'text') {
|
|
14
|
+
return tokenizeChips(child.value, mentions, skills).map((segment) => segment.kind === 'text' ? { type: 'text', value: segment.text } : chipElement(segment));
|
|
15
|
+
}
|
|
16
|
+
if (child.type === 'element' && !SKIPPED.has(child.tagName)) {
|
|
17
|
+
split(child, mentions, skills);
|
|
18
|
+
}
|
|
19
|
+
return [child];
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
/* The `@path` and `$name` tokens a person picked in the composer, cut out of the text as chips. */
|
|
23
|
+
export const rehypeChips = ({ mentions = [], skills = [] } = {}) => (tree) => {
|
|
24
|
+
if (mentions.length === 0 && skills.length === 0) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
split(tree, mentions, skills);
|
|
28
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface HastText {
|
|
2
|
+
type: 'text';
|
|
3
|
+
value: string;
|
|
4
|
+
}
|
|
5
|
+
interface HastElement {
|
|
6
|
+
type: 'element';
|
|
7
|
+
tagName: string;
|
|
8
|
+
properties: Record<string, unknown>;
|
|
9
|
+
children: HastNode[];
|
|
10
|
+
}
|
|
11
|
+
type HastNode = HastText | HastElement | {
|
|
12
|
+
type: 'comment' | 'doctype' | 'raw';
|
|
13
|
+
value?: string;
|
|
14
|
+
};
|
|
15
|
+
interface HastParent {
|
|
16
|
+
children: HastNode[];
|
|
17
|
+
}
|
|
18
|
+
export declare const FADE_CLASS = "chat-fade";
|
|
19
|
+
export declare const WHOLE_FADE_CLASS = "chat-fade-whole";
|
|
20
|
+
export declare const wordSegments: (text: string) => string[];
|
|
21
|
+
export declare const isWhitespace: (segment: string) => boolean;
|
|
22
|
+
export declare const rehypeFadeWords: () => (tree: HastParent) => void;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const FADE_CLASS = 'chat-fade';
|
|
2
|
+
// A reply written out of sight, which appears in one piece rather than a word at a time.
|
|
3
|
+
export const WHOLE_FADE_CLASS = 'chat-fade-whole';
|
|
4
|
+
// Code is read as one string by the renderer, so a span inside it would come out as "[object Object]".
|
|
5
|
+
const SKIPPED = new Set(['code', 'pre']);
|
|
6
|
+
const WHITESPACE = /^\s+$/;
|
|
7
|
+
/* A text cut into its words and the whitespace between them, in order. */
|
|
8
|
+
export const wordSegments = (text) => text.split(/(\s+)/).filter((segment) => segment !== '');
|
|
9
|
+
export const isWhitespace = (segment) => WHITESPACE.test(segment);
|
|
10
|
+
const wrap = (parent) => {
|
|
11
|
+
parent.children = parent.children.flatMap((child) => {
|
|
12
|
+
if (child.type === 'text') {
|
|
13
|
+
return wordSegments(child.value).map((segment) => isWhitespace(segment)
|
|
14
|
+
? { type: 'text', value: segment }
|
|
15
|
+
: { type: 'element', tagName: 'span', properties: { className: [FADE_CLASS] }, children: [{ type: 'text', value: segment }] });
|
|
16
|
+
}
|
|
17
|
+
if (child.type === 'element' && !SKIPPED.has(child.tagName)) {
|
|
18
|
+
wrap(child);
|
|
19
|
+
}
|
|
20
|
+
return [child];
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
/*
|
|
24
|
+
* Every word of a reply that is still streaming in a span that fades in. Words only ever arrive at
|
|
25
|
+
* the end, and the renderer keys a span by its place among the spans beside it, so the spans already
|
|
26
|
+
* on screen keep their element and their fade does not start again.
|
|
27
|
+
*/
|
|
28
|
+
export const rehypeFadeWords = () => (tree) => {
|
|
29
|
+
wrap(tree);
|
|
30
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Parents whose children are blocks, where a bare text node would stand outside any paragraph.
|
|
2
|
+
const FLOW_PARENTS = new Set(['root', 'blockquote', 'listItem', 'footnoteDefinition']);
|
|
3
|
+
const literal = (parent) => {
|
|
4
|
+
if (parent.children === undefined) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
const flow = FLOW_PARENTS.has(parent.type);
|
|
8
|
+
parent.children = parent.children.map((child) => {
|
|
9
|
+
if (child.type !== 'html') {
|
|
10
|
+
literal(child);
|
|
11
|
+
return child;
|
|
12
|
+
}
|
|
13
|
+
const text = { ...child, type: 'text' };
|
|
14
|
+
return flow ? { type: 'paragraph', position: child.position, children: [text] } : text;
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
/*
|
|
18
|
+
* Raw HTML in a message as the characters a person typed. react-markdown drops an `html` node, so
|
|
19
|
+
* "the <span> element" without backticks would lose the tag. Runs before `remark-breaks`, so the
|
|
20
|
+
* newlines of an HTML block still become line breaks.
|
|
21
|
+
*/
|
|
22
|
+
export const remarkHtmlAsText = () => (tree) => {
|
|
23
|
+
literal(tree);
|
|
24
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const REVEAL_FADE_MS = 300;
|
|
2
|
+
export declare const REVEAL_TAU_MS = 250;
|
|
3
|
+
export declare const REVEAL_MIN_CPS = 40;
|
|
4
|
+
export declare const REVEAL_FINISH_MIN_CPS = 120;
|
|
5
|
+
export declare const REVEAL_MAX_FRAME_MS = 100;
|
|
6
|
+
export declare const REVEAL_MAX_HELD_WORD = 32;
|
|
7
|
+
export declare const advanceReveal: (position: number, target: number, elapsedMs: number, finished: boolean) => number;
|
|
8
|
+
export declare const revealBoundary: (text: string, position: number, complete: boolean) => number;
|
|
9
|
+
export interface RevealedText {
|
|
10
|
+
text: string;
|
|
11
|
+
active: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare const useRevealedText: (text: string, streaming: boolean) => RevealedText;
|