@adecore/agents-react 0.23.1 → 0.24.1
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/README.md +14 -11
- package/dist/agents/AccountDot.js +3 -1
- package/dist/agents/accounts.d.ts +3 -2
- package/dist/agents/accounts.js +5 -6
- package/dist/chat/actions.d.ts +2 -2
- package/dist/chat/actions.js +8 -5
- package/dist/chat/bookmarks.d.ts +5 -3
- package/dist/chat/bookmarks.js +13 -14
- package/dist/chat/chat-client.d.ts +1 -0
- package/dist/chat/chat-client.js +7 -0
- package/dist/chat/composer-send.d.ts +3 -0
- package/dist/chat/composer-send.js +22 -0
- package/dist/chat/forks.js +22 -5
- package/dist/chat/index.d.ts +31 -0
- package/dist/chat/index.js +28 -0
- package/dist/chat/logic/embedded-code.d.ts +8 -0
- package/dist/chat/logic/embedded-code.js +371 -0
- package/dist/chat/logic/fork.js +1 -0
- package/dist/chat/logic/goal.d.ts +6 -0
- package/dist/chat/logic/goal.js +26 -0
- package/dist/chat/logic/scrubber.d.ts +1 -1
- package/dist/chat/logic/scrubber.js +6 -5
- package/dist/chat/logic/thought.d.ts +3 -0
- package/dist/chat/logic/thought.js +35 -0
- package/dist/chat/logic/thread-cards.js +2 -0
- package/dist/chat/logic/timeline.d.ts +5 -0
- package/dist/chat/logic/timeline.js +22 -1
- package/dist/chat/logic/tool-catalog.d.ts +1 -0
- package/dist/chat/logic/tool-catalog.js +3 -1
- package/dist/chat/logic/tools.d.ts +14 -0
- package/dist/chat/logic/tools.js +57 -0
- package/dist/chat/preferences.d.ts +6 -0
- package/dist/chat/preferences.js +19 -3
- package/dist/chat/reading-width.d.ts +9 -0
- package/dist/chat/reading-width.js +27 -0
- package/dist/chat/send-key.d.ts +10 -0
- package/dist/chat/send-key.js +24 -0
- package/dist/chat/ui/BookmarkMarker.js +4 -2
- package/dist/chat/ui/BookmarkSubmenu.js +4 -3
- package/dist/chat/ui/ChatActivity.js +14 -10
- package/dist/chat/ui/ChatReferenceChip.js +3 -4
- package/dist/chat/ui/CodeBlock.d.ts +2 -1
- package/dist/chat/ui/CodeBlock.js +14 -96
- package/dist/chat/ui/Composer.d.ts +1 -1
- package/dist/chat/ui/Composer.js +130 -907
- package/dist/chat/ui/ComposerInput.js +5 -4
- package/dist/chat/ui/EditDiff.js +3 -2
- package/dist/chat/ui/GoalDock.d.ts +9 -0
- package/dist/chat/ui/GoalDock.js +63 -0
- package/dist/chat/ui/LimitState.js +5 -3
- package/dist/chat/ui/Markdown.d.ts +2 -1
- package/dist/chat/ui/Markdown.js +70 -31
- package/dist/chat/ui/MermaidBlock.d.ts +3 -0
- package/dist/chat/ui/MermaidBlock.js +73 -0
- package/dist/chat/ui/MessageActions.js +12 -7
- package/dist/chat/ui/Pickers.js +6 -5
- package/dist/chat/ui/PromptComposer.js +4 -3
- package/dist/chat/ui/RowTime.d.ts +3 -0
- package/dist/chat/ui/RowTime.js +12 -0
- package/dist/chat/ui/RunSettings.js +5 -4
- package/dist/chat/ui/ShellCommand.d.ts +3 -0
- package/dist/chat/ui/ShellCommand.js +110 -0
- package/dist/chat/ui/SubagentInfo.js +4 -3
- package/dist/chat/ui/SubagentStopButton.js +4 -3
- package/dist/chat/ui/SubagentTimeline.js +4 -1
- package/dist/chat/ui/Timeline.js +70 -40
- package/dist/chat/ui/TimelineMenu.d.ts +2 -1
- package/dist/chat/ui/TimelineMenu.js +18 -8
- package/dist/chat/ui/UnifiedDiff.js +3 -2
- package/dist/chat/ui/VisualFrame.js +6 -7
- package/dist/chat/ui/code-theme.d.ts +2 -1
- package/dist/chat/ui/code-theme.js +4 -4
- package/dist/chat/ui/composer/ComposerControls.d.ts +27 -0
- package/dist/chat/ui/composer/ComposerControls.js +34 -0
- package/dist/chat/ui/composer/ComposerMenu.d.ts +4 -0
- package/dist/chat/ui/composer/ComposerMenu.js +46 -0
- package/dist/chat/ui/composer/DraftParts.d.ts +18 -0
- package/dist/chat/ui/composer/DraftParts.js +25 -0
- package/dist/chat/ui/composer/QueuedMessages.d.ts +10 -0
- package/dist/chat/ui/composer/QueuedMessages.js +20 -0
- package/dist/chat/ui/composer/key-handler.d.ts +20 -0
- package/dist/chat/ui/composer/key-handler.js +150 -0
- package/dist/chat/ui/composer/keys.d.ts +7 -2
- package/dist/chat/ui/composer/keys.js +19 -9
- package/dist/chat/ui/composer/placeholder.d.ts +9 -0
- package/dist/chat/ui/composer/placeholder.js +38 -0
- package/dist/chat/ui/composer/surface.d.ts +1 -0
- package/dist/chat/ui/composer/surface.js +44 -0
- package/dist/chat/ui/composer/use-chat-controls.d.ts +28 -0
- package/dist/chat/ui/composer/use-chat-controls.js +112 -0
- package/dist/chat/ui/composer/use-compaction-offer.d.ts +2 -0
- package/dist/chat/ui/composer/use-compaction-offer.js +23 -0
- package/dist/chat/ui/composer/use-composer-draft.d.ts +14 -0
- package/dist/chat/ui/composer/use-composer-draft.js +39 -0
- package/dist/chat/ui/composer/use-composer-menus.d.ts +52 -0
- package/dist/chat/ui/composer/use-composer-menus.js +232 -0
- package/dist/chat/ui/composer/use-composer-submit.d.ts +32 -0
- package/dist/chat/ui/composer/use-composer-submit.js +113 -0
- package/dist/chat/ui/composer/use-draft-files.d.ts +27 -0
- package/dist/chat/ui/composer/use-draft-files.js +111 -0
- package/dist/chat/ui/composer/use-notice.d.ts +6 -0
- package/dist/chat/ui/composer/use-notice.js +18 -0
- package/dist/chat/ui/composer/use-pending-requests.d.ts +2 -0
- package/dist/chat/ui/composer/use-pending-requests.js +22 -0
- package/dist/chat/ui/composer/use-prompt-history.d.ts +8 -0
- package/dist/chat/ui/composer/use-prompt-history.js +57 -0
- package/dist/chat/ui/composer/use-prompt-hold.d.ts +6 -0
- package/dist/chat/ui/composer/use-prompt-hold.js +48 -0
- package/dist/chat/ui/composer/use-take-back.d.ts +13 -0
- package/dist/chat/ui/composer/use-take-back.js +82 -0
- package/dist/chat/ui/diff/EditorEditDiff.js +4 -3
- package/dist/chat/ui/diff/LegacyDiffPool.js +3 -2
- package/dist/chat/ui/diff/LegacyEditDiff.js +3 -2
- package/dist/chat/ui/diff/LegacyUnifiedDiff.js +3 -2
- package/dist/chat/ui/diff/syntax-runtime.d.ts +2 -1
- package/dist/chat/ui/diff/syntax-runtime.js +9 -5
- package/dist/chat/ui/diff-syntax.d.ts +2 -2
- package/dist/chat/ui/diff-syntax.js +1 -2
- package/dist/chat/ui/diff-theme.js +10 -9
- package/dist/chat/ui/file-links.d.ts +1 -1
- package/dist/chat/ui/file-links.js +7 -5
- package/dist/chat/ui/highlighter.d.ts +27 -0
- package/dist/chat/ui/highlighter.js +151 -0
- package/dist/chat/ui/index.d.ts +67 -0
- package/dist/chat/ui/index.js +64 -0
- package/dist/chat/ui/intelligent-ui/UiReply.js +3 -2
- package/dist/chat/ui/intelligent-ui/renderers/content.js +3 -2
- package/dist/chat/ui/intelligent-ui/use-ui-links.js +2 -2
- package/dist/chat/ui/intelligent-ui/use-ui-queries.js +4 -3
- package/dist/chat/ui/markdown-blocks.d.ts +8 -0
- package/dist/chat/ui/markdown-blocks.js +260 -12
- package/dist/chat/ui/mermaid.d.ts +23 -0
- package/dist/chat/ui/mermaid.js +93 -0
- package/dist/chat/ui/rows/ForksRow.js +5 -3
- package/dist/chat/ui/rows/GeneratedImageRow.js +6 -4
- package/dist/chat/ui/rows/MessageRows.d.ts +4 -0
- package/dist/chat/ui/rows/MessageRows.js +36 -13
- package/dist/chat/ui/rows/Rows.js +3 -1
- package/dist/chat/ui/rows/SubagentRow.js +3 -2
- package/dist/chat/ui/rows/VisualRow.js +3 -2
- package/dist/chat/ui/rows/WorkRows.d.ts +6 -3
- package/dist/chat/ui/rows/WorkRows.js +57 -20
- package/dist/chat/ui/rows/WorkflowRow.js +3 -2
- package/dist/chat/ui/timestamp-format.d.ts +1 -0
- package/dist/chat/ui/timestamp-format.js +2 -0
- package/dist/chat/ui/use-diff-source-tokens.js +6 -3
- package/dist/chat/ui/visual-theme.js +3 -2
- package/dist/host.d.ts +9 -6
- package/dist/host.js +19 -8
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -0
- package/dist/locales/en/agent-chat.d.ts +658 -0
- package/dist/locales/en/agent-chat.js +657 -0
- package/dist/locales/en/agent-prompts.d.ts +44 -0
- package/dist/locales/en/agent-prompts.js +43 -0
- package/dist/locales/en/agent-providers.d.ts +132 -0
- package/dist/locales/en/agent-providers.js +131 -0
- package/dist/locales/en/agent-usage.d.ts +132 -0
- package/dist/locales/en/agent-usage.js +131 -0
- package/dist/locales/nl/agent-chat.d.ts +658 -0
- package/dist/locales/nl/agent-chat.js +657 -0
- package/dist/locales/nl/agent-prompts.d.ts +44 -0
- package/dist/locales/nl/agent-prompts.js +43 -0
- package/dist/locales/nl/agent-providers.d.ts +132 -0
- package/dist/locales/nl/agent-providers.js +131 -0
- package/dist/locales/nl/agent-usage.d.ts +132 -0
- package/dist/locales/nl/agent-usage.js +131 -0
- package/dist/locales.js +9 -8
- package/dist/prompts/index.d.ts +11 -0
- package/dist/prompts/index.js +12 -0
- package/dist/prompts/logic/subjects.d.ts +2 -1
- package/dist/prompts/logic/subjects.js +6 -4
- package/dist/prompts/ui/ApprovalBody.js +6 -4
- package/dist/prompts/ui/PromptCard.js +5 -4
- package/dist/prompts/ui/PromptView.js +3 -2
- package/dist/prompts/ui/QuestionBody.js +3 -2
- package/dist/providers/AccountColors.js +2 -2
- package/dist/providers/AccountDetail.js +7 -6
- package/dist/providers/AccountVariables.js +3 -1
- package/dist/providers/AddAccountForm.js +3 -2
- package/dist/providers/ProvidersPane.js +4 -3
- package/dist/providers/account-actions.d.ts +4 -3
- package/dist/providers/account-actions.js +6 -7
- package/dist/providers/index.d.ts +16 -0
- package/dist/providers/index.js +17 -0
- package/dist/providers/provider-abilities.d.ts +2 -1
- package/dist/providers/provider-abilities.js +4 -0
- package/dist/state/chats.js +4 -2
- package/dist/state/index.d.ts +4 -0
- package/dist/state/index.js +5 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +2 -0
- package/dist/{prompts/logic/prompts.fixtures.d.ts → testing/prompts.d.ts} +1 -1
- package/dist/theme.css +13 -3
- package/dist/usage/UsageBreakdown.js +3 -2
- package/dist/usage/UsageLimits.js +4 -3
- package/dist/usage/UsagePage.js +3 -2
- package/dist/usage/index.d.ts +15 -0
- package/dist/usage/index.js +15 -0
- package/package.json +53 -1031
- package/src/agents/AccountDot.tsx +3 -1
- package/src/agents/accounts.ts +6 -6
- package/src/chat/actions.ts +8 -5
- package/src/chat/bookmarks.ts +23 -14
- package/src/chat/chat-client.ts +8 -0
- package/src/chat/composer-send.ts +27 -0
- package/src/chat/forks.ts +27 -6
- package/src/chat/index.ts +98 -0
- package/src/chat/logic/embedded-code.ts +409 -0
- package/src/chat/logic/fork.ts +1 -0
- package/src/chat/logic/goal.ts +33 -0
- package/src/chat/logic/scrubber.ts +6 -5
- package/src/chat/logic/thought.ts +40 -0
- package/src/chat/logic/thread-cards.ts +2 -0
- package/src/chat/logic/timeline.ts +25 -1
- package/src/chat/logic/tool-catalog.ts +5 -1
- package/src/chat/logic/tools.ts +76 -0
- package/src/chat/preferences.ts +26 -3
- package/src/chat/reading-width.ts +39 -0
- package/src/chat/send-key.ts +38 -0
- package/src/chat/ui/BookmarkMarker.tsx +4 -2
- package/src/chat/ui/BookmarkSubmenu.tsx +4 -3
- package/src/chat/ui/ChatActivity.tsx +14 -10
- package/src/chat/ui/ChatReferenceChip.tsx +3 -4
- package/src/chat/ui/CodeBlock.tsx +33 -124
- package/src/chat/ui/Composer.tsx +212 -1358
- package/src/chat/ui/ComposerInput.tsx +5 -4
- package/src/chat/ui/EditDiff.tsx +3 -2
- package/src/chat/ui/GoalDock.tsx +104 -0
- package/src/chat/ui/LimitState.tsx +5 -3
- package/src/chat/ui/Markdown.tsx +113 -39
- package/src/chat/ui/MermaidBlock.tsx +119 -0
- package/src/chat/ui/MessageActions.tsx +13 -8
- package/src/chat/ui/Pickers.tsx +8 -7
- package/src/chat/ui/PromptComposer.tsx +4 -3
- package/src/chat/ui/RowTime.tsx +21 -0
- package/src/chat/ui/RunSettings.tsx +9 -4
- package/src/chat/ui/ShellCommand.tsx +134 -0
- package/src/chat/ui/SubagentInfo.tsx +4 -3
- package/src/chat/ui/SubagentStopButton.tsx +4 -3
- package/src/chat/ui/SubagentTimeline.tsx +5 -1
- package/src/chat/ui/Timeline.tsx +167 -105
- package/src/chat/ui/TimelineMenu.tsx +42 -7
- package/src/chat/ui/UnifiedDiff.tsx +3 -2
- package/src/chat/ui/VisualFrame.tsx +6 -7
- package/src/chat/ui/code-theme.ts +4 -4
- package/src/chat/ui/composer/ComposerControls.tsx +147 -0
- package/src/chat/ui/composer/ComposerMenu.tsx +138 -0
- package/src/chat/ui/composer/DraftParts.tsx +86 -0
- package/src/chat/ui/composer/QueuedMessages.tsx +64 -0
- package/src/chat/ui/composer/key-handler.ts +174 -0
- package/src/chat/ui/composer/keys.ts +32 -10
- package/src/chat/ui/composer/placeholder.ts +48 -0
- package/src/chat/ui/composer/surface.ts +45 -0
- package/src/chat/ui/composer/use-chat-controls.ts +134 -0
- package/src/chat/ui/composer/use-compaction-offer.ts +25 -0
- package/src/chat/ui/composer/use-composer-draft.ts +71 -0
- package/src/chat/ui/composer/use-composer-menus.ts +289 -0
- package/src/chat/ui/composer/use-composer-submit.ts +163 -0
- package/src/chat/ui/composer/use-draft-files.ts +135 -0
- package/src/chat/ui/composer/use-notice.ts +28 -0
- package/src/chat/ui/composer/use-pending-requests.ts +23 -0
- package/src/chat/ui/composer/use-prompt-history.ts +76 -0
- package/src/chat/ui/composer/use-prompt-hold.ts +58 -0
- package/src/chat/ui/composer/use-take-back.ts +91 -0
- package/src/chat/ui/diff/DiffFallbackBoundary.tsx +2 -2
- package/src/chat/ui/diff/EditorEditDiff.tsx +4 -3
- package/src/chat/ui/diff/LegacyDiffPool.tsx +3 -2
- package/src/chat/ui/diff/LegacyEditDiff.tsx +3 -2
- package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +3 -2
- package/src/chat/ui/diff/syntax-runtime.ts +10 -5
- package/src/chat/ui/diff-syntax.ts +2 -2
- package/src/chat/ui/diff-theme.ts +10 -9
- package/src/chat/ui/file-links.ts +7 -5
- package/src/chat/ui/highlighter.ts +185 -0
- package/src/chat/ui/index.ts +109 -0
- package/src/chat/ui/intelligent-ui/UiReply.tsx +5 -4
- package/src/chat/ui/intelligent-ui/renderers/content.tsx +3 -2
- package/src/chat/ui/intelligent-ui/use-ui-links.ts +2 -2
- package/src/chat/ui/intelligent-ui/use-ui-queries.ts +4 -3
- package/src/chat/ui/markdown-blocks.ts +300 -12
- package/src/chat/ui/mermaid.ts +118 -0
- package/src/chat/ui/rows/ForksRow.tsx +5 -3
- package/src/chat/ui/rows/GeneratedImageRow.tsx +6 -4
- package/src/chat/ui/rows/MessageRows.tsx +73 -18
- package/src/chat/ui/rows/Rows.tsx +14 -1
- package/src/chat/ui/rows/SubagentRow.tsx +3 -2
- package/src/chat/ui/rows/VisualRow.tsx +3 -2
- package/src/chat/ui/rows/WorkRows.tsx +148 -47
- package/src/chat/ui/rows/WorkflowRow.tsx +3 -2
- package/src/chat/ui/timestamp-format.ts +2 -0
- package/src/chat/ui/use-diff-source-tokens.ts +10 -4
- package/src/chat/ui/visual-theme.ts +3 -2
- package/src/host.ts +31 -15
- package/src/index.ts +49 -0
- package/src/locales/en/agent-chat.ts +657 -0
- package/src/locales/en/agent-prompts.ts +43 -0
- package/src/locales/en/agent-providers.ts +131 -0
- package/src/locales/en/agent-usage.ts +131 -0
- package/src/locales/nl/agent-chat.ts +657 -0
- package/src/locales/nl/agent-prompts.ts +43 -0
- package/src/locales/nl/agent-providers.ts +131 -0
- package/src/locales/nl/agent-usage.ts +132 -0
- package/src/locales.ts +9 -8
- package/src/prompts/index.ts +35 -0
- package/src/prompts/logic/subjects.ts +12 -4
- package/src/prompts/ui/ApprovalBody.tsx +6 -4
- package/src/prompts/ui/PromptCard.tsx +11 -4
- package/src/prompts/ui/PromptView.tsx +3 -2
- package/src/prompts/ui/QuestionBody.tsx +3 -2
- package/src/providers/AccountColors.tsx +2 -2
- package/src/providers/AccountDetail.tsx +13 -7
- package/src/providers/AccountVariables.tsx +3 -1
- package/src/providers/AddAccountForm.tsx +3 -2
- package/src/providers/ProvidersPane.tsx +4 -3
- package/src/providers/account-actions.ts +7 -7
- package/src/providers/index.ts +17 -0
- package/src/providers/provider-abilities.ts +6 -1
- package/src/state/chats.ts +4 -2
- package/src/state/index.ts +39 -0
- package/src/testing/index.ts +2 -0
- package/src/{prompts/logic/prompts.fixtures.ts → testing/prompts.ts} +1 -1
- package/src/theme.css +13 -3
- package/src/usage/UsageBreakdown.tsx +3 -2
- package/src/usage/UsageLimits.tsx +4 -3
- package/src/usage/UsagePage.tsx +3 -2
- package/src/usage/index.ts +16 -0
- package/dist/locales/en/agent-chat.json +0 -597
- package/dist/locales/en/agent-prompts.json +0 -43
- package/dist/locales/en/agent-providers.json +0 -131
- package/dist/locales/en/agent-usage.json +0 -131
- package/dist/locales/nl/agent-chat.json +0 -597
- package/dist/locales/nl/agent-prompts.json +0 -43
- package/dist/locales/nl/agent-providers.json +0 -131
- package/dist/locales/nl/agent-usage.json +0 -131
- package/src/locales/en/agent-chat.json +0 -597
- package/src/locales/en/agent-prompts.json +0 -43
- package/src/locales/en/agent-providers.json +0 -131
- package/src/locales/en/agent-usage.json +0 -131
- package/src/locales/nl/agent-chat.json +0 -597
- package/src/locales/nl/agent-prompts.json +0 -43
- package/src/locales/nl/agent-providers.json +0 -131
- package/src/locales/nl/agent-usage.json +0 -131
- /package/dist/{prompts/logic/prompts.fixtures.js → testing/prompts.js} +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
|
+
import { useChatRow } from '../../../state/chats';
|
|
3
|
+
|
|
4
|
+
// How many sent prompts the arrow keys walk back through, per chat.
|
|
5
|
+
const PROMPT_HISTORY = 50;
|
|
6
|
+
|
|
7
|
+
export interface PromptHistory {
|
|
8
|
+
/* Whether a prompt was recalled and the box still holds it unedited. */
|
|
9
|
+
recalled(text: string): boolean;
|
|
10
|
+
/* Walks one prompt back (-1) or forward (1); false when there is nothing to walk through. */
|
|
11
|
+
recall(direction: -1 | 1): boolean;
|
|
12
|
+
/* Leaves a recalled prompt in the box as a draft of its own. */
|
|
13
|
+
forget(): void;
|
|
14
|
+
/* Leaves the walk and empties the box; false when no prompt was recalled. */
|
|
15
|
+
leave(): boolean;
|
|
16
|
+
/* Forgets the walk once the text no longer is the recalled prompt. */
|
|
17
|
+
edited(text: string): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* The prompts sent in this chat, for the arrow keys to put back in an empty box. */
|
|
21
|
+
export function usePromptHistory(chatId: string, setText: (next: string, mentions?: string[], skills?: string[]) => void): PromptHistory {
|
|
22
|
+
const order = useChatRow(chatId, (row) => row?.order);
|
|
23
|
+
const items = useChatRow(chatId, (row) => row?.structure);
|
|
24
|
+
const [index, setIndex] = useState<number | null>(null);
|
|
25
|
+
|
|
26
|
+
const history = useMemo(() => {
|
|
27
|
+
const prompts: Array<{ text: string; mentions: string[] }> = [];
|
|
28
|
+
for (const id of order ?? []) {
|
|
29
|
+
const item = items?.[id];
|
|
30
|
+
if (item?.kind === 'user') {
|
|
31
|
+
prompts.push({ text: item.text, mentions: item.mentions ?? [] });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Walking back further than this is faster with the timeline than with the arrow key.
|
|
35
|
+
return prompts.slice(-PROMPT_HISTORY);
|
|
36
|
+
}, [order, items]);
|
|
37
|
+
|
|
38
|
+
const recall = (direction: -1 | 1): boolean => {
|
|
39
|
+
if (history.length === 0) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const next = index === null ? (direction === -1 ? history.length - 1 : null) : index + direction;
|
|
43
|
+
if (next === null || next >= history.length) {
|
|
44
|
+
setIndex(null);
|
|
45
|
+
setText('', [], []);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (next < 0) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
setIndex(next);
|
|
52
|
+
setText(history[next]!.text, history[next]!.mentions);
|
|
53
|
+
return true;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const leave = (): boolean => {
|
|
57
|
+
if (index === null) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
setIndex(null);
|
|
61
|
+
setText('', [], []);
|
|
62
|
+
return true;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
recalled: (text) => index !== null && text === history[index]?.text,
|
|
67
|
+
recall,
|
|
68
|
+
forget: () => setIndex(null),
|
|
69
|
+
leave,
|
|
70
|
+
edited: (text) => {
|
|
71
|
+
if (index !== null && text !== history[index]?.text) {
|
|
72
|
+
setIndex(null);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { holdLiftsIn, type HoldRelease } from '../../prompt-hold';
|
|
3
|
+
import type { PromptHold } from '../PromptComposer';
|
|
4
|
+
|
|
5
|
+
export type ComposerPromptHold = PromptHold & { noteInput(): void; setComposing(composing: boolean): void };
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* Keeps a waiting prompt card behind the draft while the person types. Typing, pasting and dictating
|
|
9
|
+
* all change the draft, so `noteInput` is called from its change handler; the hold lifts after a pause.
|
|
10
|
+
*/
|
|
11
|
+
export function usePromptHold(): ComposerPromptHold {
|
|
12
|
+
const typedAt = useRef<number | null>(null);
|
|
13
|
+
const composing = useRef(false);
|
|
14
|
+
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
15
|
+
const [state, setState] = useState<{ held: boolean; releasedBy: HoldRelease | null }>({ held: false, releasedBy: null });
|
|
16
|
+
|
|
17
|
+
const clearTimer = (): void => {
|
|
18
|
+
if (timer.current !== null) {
|
|
19
|
+
clearTimeout(timer.current);
|
|
20
|
+
timer.current = null;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const release = (reason: HoldRelease): void => {
|
|
25
|
+
clearTimer();
|
|
26
|
+
typedAt.current = null;
|
|
27
|
+
composing.current = false;
|
|
28
|
+
setState((current) => (current.held ? { held: false, releasedBy: reason } : current));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const schedule = (): void => {
|
|
32
|
+
clearTimer();
|
|
33
|
+
const wait = holdLiftsIn({ typedAt: typedAt.current, composing: composing.current }, Date.now());
|
|
34
|
+
if (wait !== null) {
|
|
35
|
+
timer.current = setTimeout(() => release('pause'), wait);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const noteInput = (): void => {
|
|
40
|
+
typedAt.current = Date.now();
|
|
41
|
+
setState({ held: true, releasedBy: null });
|
|
42
|
+
schedule();
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const setComposing = (next: boolean): void => {
|
|
46
|
+
composing.current = next;
|
|
47
|
+
if (next) {
|
|
48
|
+
setState({ held: true, releasedBy: null });
|
|
49
|
+
} else {
|
|
50
|
+
typedAt.current = Date.now();
|
|
51
|
+
}
|
|
52
|
+
schedule();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
useEffect(() => clearTimer, []);
|
|
56
|
+
|
|
57
|
+
return { held: state.held, releasedBy: state.releasedBy, release, noteInput, setComposing };
|
|
58
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import type { ChatAttachmentUpload, ChatQueuedMessage } from '@adecore/agent-contracts';
|
|
4
|
+
import { useChatHost } from '../../../host';
|
|
5
|
+
import { useChatScope } from '../../../scope';
|
|
6
|
+
import { errorCode } from '../../../transport';
|
|
7
|
+
import { useChatActions } from '../../actions';
|
|
8
|
+
import { readStoredAttachments } from '../../attachments';
|
|
9
|
+
import { takeBackIntoDraft, type ChatDraft } from '../../drafts';
|
|
10
|
+
import type { ComposerDraft } from './use-composer-draft';
|
|
11
|
+
|
|
12
|
+
interface TakeBackInput {
|
|
13
|
+
chatId: string;
|
|
14
|
+
draft: ComposerDraft;
|
|
15
|
+
setNotice(notice: string): void;
|
|
16
|
+
/* The message is back in the draft. */
|
|
17
|
+
onTaken(): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Takes a queued message out of the queue and back into the draft, to be edited before it goes. */
|
|
21
|
+
export function useTakeBack({ chatId, draft: { latest, setDraft }, setNotice, onTaken }: TakeBackInput) {
|
|
22
|
+
const { t } = useTranslation('agent-chat');
|
|
23
|
+
const host = useChatHost();
|
|
24
|
+
const scope = useChatScope();
|
|
25
|
+
const actions = useChatActions();
|
|
26
|
+
const [takingBack, setTakingBack] = useState<string | null>(null);
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* The files come back from the machine before the message leaves the queue, since the machine only
|
|
30
|
+
* serves a file some message still holds. The machine's refusal is what says it already went out.
|
|
31
|
+
*/
|
|
32
|
+
const takeBack = async (message: ChatQueuedMessage): Promise<void> => {
|
|
33
|
+
if (takingBack !== null) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const failed = (e: unknown): void => {
|
|
37
|
+
host.notify({ kind: 'error', title: t('composer.queue.editFailed'), description: e instanceof Error ? e.message : String(e) });
|
|
38
|
+
};
|
|
39
|
+
const readFiles = async (): Promise<ChatAttachmentUpload[]> => {
|
|
40
|
+
const stored = message.attachments ?? [];
|
|
41
|
+
if (stored.length === 0) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const files = host.attachments;
|
|
45
|
+
return readStoredAttachments((attachment) => files.read(scope.id, chatId, attachment.id), stored);
|
|
46
|
+
};
|
|
47
|
+
setTakingBack(message.id);
|
|
48
|
+
try {
|
|
49
|
+
let uploads: ChatAttachmentUpload[];
|
|
50
|
+
try {
|
|
51
|
+
uploads = await readFiles();
|
|
52
|
+
} catch (e) {
|
|
53
|
+
failed(e);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const taken: Omit<ChatDraft, 'quote' | 'quoteSource'> = {
|
|
57
|
+
text: message.text,
|
|
58
|
+
mentions: message.mentions ?? [],
|
|
59
|
+
skills: message.skills ?? [],
|
|
60
|
+
chats: message.chats ?? [],
|
|
61
|
+
attachments: uploads
|
|
62
|
+
};
|
|
63
|
+
// A file that would not fit is refused here, while the message still holds it on the machine.
|
|
64
|
+
const wouldReject = takeBackIntoDraft(latest.current, taken).rejected[0];
|
|
65
|
+
if (wouldReject) {
|
|
66
|
+
setNotice(t('composer.notice.queuedDoesNotFit', { name: wouldReject.name || t('composer.notice.thatFile'), reason: wouldReject.reason }));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
await actions.unqueue(chatId, message.id);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
if (errorCode(e) === 'request-not-found') {
|
|
73
|
+
host.notify({ kind: 'error', title: t('composer.queue.alreadySent') });
|
|
74
|
+
} else {
|
|
75
|
+
failed(e);
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const merged = takeBackIntoDraft(latest.current, taken);
|
|
80
|
+
setDraft(merged.draft);
|
|
81
|
+
if (merged.rejected[0]) {
|
|
82
|
+
setNotice(t('composer.notice.rejected', { name: merged.rejected[0].name, reason: merged.rejected[0].reason }));
|
|
83
|
+
}
|
|
84
|
+
onTaken();
|
|
85
|
+
} finally {
|
|
86
|
+
setTakingBack(null);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return { takingBack, takeBack };
|
|
91
|
+
}
|
|
@@ -13,7 +13,7 @@ interface State {
|
|
|
13
13
|
|
|
14
14
|
/* A failed view or lazy chunk must leave the containing review card usable. */
|
|
15
15
|
export class DiffFallbackBoundary extends Component<Props, State> {
|
|
16
|
-
state: State = { failed: false, resetKeys: this.props.resetKeys };
|
|
16
|
+
override state: State = { failed: false, resetKeys: this.props.resetKeys };
|
|
17
17
|
|
|
18
18
|
static getDerivedStateFromError(): Partial<State> {
|
|
19
19
|
return { failed: true };
|
|
@@ -26,7 +26,7 @@ export class DiffFallbackBoundary extends Component<Props, State> {
|
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
render(): ReactNode {
|
|
29
|
+
override render(): ReactNode {
|
|
30
30
|
return this.state.failed ? this.props.fallback : this.props.children;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -5,7 +5,7 @@ import { getFiletypeFromFileName } from '@pierre/diffs';
|
|
|
5
5
|
import type { EditorEngine } from '@adecore/editor';
|
|
6
6
|
import { DiffView } from '@adecore/editor-react';
|
|
7
7
|
import { diffDocument, patchOf, type DiffDocument } from '@adecore/merge';
|
|
8
|
-
import {
|
|
8
|
+
import { useChatHost } from '../../../host';
|
|
9
9
|
import type { FileChange } from '../../logic/tools';
|
|
10
10
|
import { useDiffSourceTokens } from '../use-diff-source-tokens';
|
|
11
11
|
import { diffSourceBudget } from './diff-source-budget';
|
|
@@ -34,10 +34,11 @@ function ReadyDiff({
|
|
|
34
34
|
layout: 'unified' | 'split';
|
|
35
35
|
maxHeight: number;
|
|
36
36
|
}) {
|
|
37
|
+
const host = useChatHost();
|
|
37
38
|
useReviewSettled();
|
|
38
39
|
const { t } = useTranslation('agent-chat');
|
|
39
|
-
const mode =
|
|
40
|
-
const theme =
|
|
40
|
+
const mode = host.code.useMode();
|
|
41
|
+
const theme = host.code.useThemes()[mode];
|
|
41
42
|
const language = getFiletypeFromFileName(path);
|
|
42
43
|
const tokens = useDiffSourceTokens(document, language, theme);
|
|
43
44
|
return (
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { useWorkerPool } from '@pierre/diffs/react';
|
|
3
|
-
import {
|
|
3
|
+
import { useChatHost } from '../../../host';
|
|
4
4
|
import DiffPool from '../DiffPool';
|
|
5
5
|
|
|
6
6
|
export function LegacyDiffPool({ children }: { children: ReactNode }) {
|
|
7
|
+
const host = useChatHost();
|
|
7
8
|
const outer = useWorkerPool();
|
|
8
|
-
const workerFactory =
|
|
9
|
+
const workerFactory = host.code.legacyDiffWorker;
|
|
9
10
|
if (outer !== undefined || workerFactory === undefined) {
|
|
10
11
|
return children;
|
|
11
12
|
}
|
|
@@ -2,13 +2,14 @@ import { LegacyDiffPool } from './LegacyDiffPool';
|
|
|
2
2
|
import { useReviewSettled } from './review-settle';
|
|
3
3
|
import { useMemo } from 'react';
|
|
4
4
|
import { MultiFileDiff } from '@pierre/diffs/react';
|
|
5
|
-
import {
|
|
5
|
+
import { useChatHost } from '../../../host';
|
|
6
6
|
import { useDiffTheme } from '../diff-theme';
|
|
7
7
|
import type { FileChange } from '../../logic/tools';
|
|
8
8
|
|
|
9
9
|
export default function LegacyEditDiff({ change, maxHeight }: { change: FileChange; maxHeight?: number }) {
|
|
10
|
+
const host = useChatHost();
|
|
10
11
|
useReviewSettled();
|
|
11
|
-
const resolved =
|
|
12
|
+
const resolved = host.code.useMode();
|
|
12
13
|
const theme = useDiffTheme();
|
|
13
14
|
const oldFile = useMemo(() => ({ name: change.path, contents: change.before }), [change.path, change.before]);
|
|
14
15
|
const newFile = useMemo(() => ({ name: change.path, contents: change.after }), [change.path, change.after]);
|
|
@@ -4,7 +4,7 @@ import { useMemo } from 'react';
|
|
|
4
4
|
import clsx from 'clsx';
|
|
5
5
|
import { FileDiff, PatchDiff } from '@pierre/diffs/react';
|
|
6
6
|
import type { ChatFileChange } from '@adecore/agent-contracts';
|
|
7
|
-
import {
|
|
7
|
+
import { useChatHost } from '../../../host';
|
|
8
8
|
import { useDiffTheme } from '../diff-theme';
|
|
9
9
|
import { fullFileDiff } from '../full-diff';
|
|
10
10
|
import type { UnifiedDiffProps } from './diff-props';
|
|
@@ -45,8 +45,9 @@ function lineClass(line: string): string {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export default function LegacyUnifiedDiff({ change, overflow = 'wrap', diffStyle = 'unified', fill = false, contents, maxHeight }: UnifiedDiffProps) {
|
|
48
|
+
const host = useChatHost();
|
|
48
49
|
useReviewSettled();
|
|
49
|
-
const resolved =
|
|
50
|
+
const resolved = host.code.useMode();
|
|
50
51
|
const theme = useDiffTheme();
|
|
51
52
|
const patch = useMemo(() => asPatch(change), [change]);
|
|
52
53
|
const oldText = contents?.old;
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type DiffSyntaxService,
|
|
8
8
|
type DiffSyntaxTokens
|
|
9
9
|
} from '@adecore/editor/shiki-worker';
|
|
10
|
-
import {
|
|
10
|
+
import type { ChatHost } from '../../../host';
|
|
11
11
|
|
|
12
12
|
interface Pending {
|
|
13
13
|
promise: Promise<DiffSyntaxTokens | null>;
|
|
@@ -39,11 +39,16 @@ export interface DiffSyntaxRuntimeOptions {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
let nextGeneration = 0;
|
|
42
|
-
|
|
42
|
+
const shared = new WeakMap<ChatHost['code'], DiffSyntaxRuntime>();
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
/* The runtime of a host's `code`, made on first use and shared by every diff under that host. */
|
|
45
|
+
export function diffSyntaxRuntime(code: ChatHost['code']): DiffSyntaxRuntime {
|
|
46
|
+
let runtime = shared.get(code);
|
|
47
|
+
if (runtime === undefined) {
|
|
48
|
+
runtime = createDiffSyntaxRuntime({ workerFactory: code.syntaxWorker, themes: code.custom, highlighter: code.highlighter });
|
|
49
|
+
shared.set(code, runtime);
|
|
50
|
+
}
|
|
51
|
+
return runtime;
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
export function createDiffSyntaxRuntime(options: DiffSyntaxRuntimeOptions): DiffSyntaxRuntime {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { LineToken } from '@adecore/editor';
|
|
2
2
|
import { lineTokensOf, type DiffSyntaxDocument } from '@adecore/editor/shiki-worker';
|
|
3
3
|
import type { DiffDocument } from '@adecore/merge';
|
|
4
|
-
import {
|
|
4
|
+
import type { DiffSyntaxRuntime } from './diff/syntax-runtime';
|
|
5
5
|
|
|
6
6
|
export { createDiffSyntaxRuntime, diffSyntaxRuntime } from './diff/syntax-runtime';
|
|
7
7
|
export type { DiffSyntaxRuntime, DiffSyntaxRuntimeOptions, DiffSyntaxSession } from './diff/syntax-runtime';
|
|
@@ -18,7 +18,7 @@ export interface DiffSourceTokenSession {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/* Structurally accepts the editor's source mapping without depending on its React renderer. */
|
|
21
|
-
export function diffSourceTokens(document: DiffDocument, mapping: DiffSyntaxMapping, runtime: DiffSyntaxRuntime
|
|
21
|
+
export function diffSourceTokens(document: DiffDocument, mapping: DiffSyntaxMapping, runtime: DiffSyntaxRuntime): DiffSourceTokenSession {
|
|
22
22
|
const sides = { old: runtime.open(mapping.documents.old), new: runtime.open(mapping.documents.new) };
|
|
23
23
|
const cache = { old: new Map<number, ReadonlyArray<readonly LineToken[] | null>>(), new: new Map<number, ReadonlyArray<readonly LineToken[] | null>>() };
|
|
24
24
|
const pending = { old: new Set<number>(), new: new Set<number>() };
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import { registerCustomTheme, type ThemesType } from '@pierre/diffs';
|
|
3
|
-
import {
|
|
3
|
+
import { useChatHost, type ChatHost } from '../../host';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const registered = new WeakSet<ChatHost['code']['custom']>();
|
|
6
6
|
|
|
7
|
-
/* The library resolves Shiki's bundled themes by id on its own; the app's own it only knows by registration, done once
|
|
8
|
-
function registerThemes(): void {
|
|
9
|
-
if (registered) {
|
|
7
|
+
/* The library resolves Shiki's bundled themes by id on its own; the app's own it only knows by registration, done once per host. */
|
|
8
|
+
function registerThemes(custom: ChatHost['code']['custom']): void {
|
|
9
|
+
if (registered.has(custom)) {
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
|
-
registered
|
|
13
|
-
for (const theme of
|
|
12
|
+
registered.add(custom);
|
|
13
|
+
for (const theme of custom) {
|
|
14
14
|
registerCustomTheme(theme.name, () => Promise.resolve(theme));
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/* The code themes a diff is drawn in, the same pair a chat's code blocks use. */
|
|
19
19
|
export function useDiffTheme(): ThemesType {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const { code } = useChatHost();
|
|
21
|
+
registerThemes(code.custom);
|
|
22
|
+
const { light, dark } = code.useThemes();
|
|
22
23
|
return useMemo(() => ({ light, dark }), [light, dark]);
|
|
23
24
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext, useContext, useMemo } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useChatHost, type FileRef } from '../../host';
|
|
3
3
|
import { ChatScopeContext } from '../../scope';
|
|
4
4
|
|
|
5
5
|
/*
|
|
@@ -21,10 +21,12 @@ export function useFileLinkScopeId(): string | null {
|
|
|
21
21
|
export function useFileLinkTarget(text: string): FileRef | null {
|
|
22
22
|
const cwd = useFileLinkCwd();
|
|
23
23
|
const scopeId = useFileLinkScopeId();
|
|
24
|
-
|
|
24
|
+
const { fileLinks } = useChatHost();
|
|
25
|
+
return useMemo(() => fileLinks?.target(text, cwd, scopeId) ?? null, [fileLinks, text, cwd, scopeId]);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
/*
|
|
28
|
-
export function
|
|
29
|
-
|
|
28
|
+
/* Follows a reference the way the app opens a file or a folder. */
|
|
29
|
+
export function useOpenFileLink(): (cwd: string | null, ref: FileRef, scopeId?: string | null) => void {
|
|
30
|
+
const { fileLinks } = useChatHost();
|
|
31
|
+
return (cwd, ref, scopeId = null) => fileLinks?.open(cwd, ref, scopeId);
|
|
30
32
|
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
import type { BundledLanguage, BundledTheme, Highlighter, ThemeRegistration } from 'shiki';
|
|
3
|
+
import type { CodeToken, Tokenize } from './code-lines';
|
|
4
|
+
import { useChatHost, type ChatHost } from '../../host';
|
|
5
|
+
import { shikiThemeOf } from './code-theme';
|
|
6
|
+
|
|
7
|
+
export type { Highlighter };
|
|
8
|
+
export type GrammarState = ReturnType<Highlighter['getLastGrammarState']>;
|
|
9
|
+
// A plain language needs no grammar; shiki draws it in the theme's own foreground.
|
|
10
|
+
const PLAIN = 'text';
|
|
11
|
+
|
|
12
|
+
/* One highlighter with the grammars and themes asked of it, each loaded once. */
|
|
13
|
+
export interface CodeHighlighter {
|
|
14
|
+
loadLanguage(lang: string): Promise<void>;
|
|
15
|
+
/* Loads a theme: one of `custom` by its name, or a bundled one by its id. */
|
|
16
|
+
loadTheme(theme: string, custom: ChatHost['code']['custom']): Promise<void>;
|
|
17
|
+
/* The highlighter once every one of `langs` and `theme` is in, or null while one is still on its way. */
|
|
18
|
+
ready(langs: Iterable<string>, theme: string): Highlighter | null;
|
|
19
|
+
/* The language shiki draws `lang` in: its own grammar, or plain text. Only meaningful once a load settled. */
|
|
20
|
+
language(lang: string): string;
|
|
21
|
+
/* The tokenizer for a language in a theme, or null while shiki, the grammar or the theme is still on its way. */
|
|
22
|
+
tokenizer(lang: string, theme: string): { tokenize: Tokenize<GrammarState>; fg: string | undefined } | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Plain text when the highlighter has no grammar for `lang`, as a bundle without that language answers. */
|
|
26
|
+
async function grammarOf(loaded: Highlighter, lang: string): Promise<string> {
|
|
27
|
+
if (lang === PLAIN || loaded.getLoadedLanguages().includes(lang)) {
|
|
28
|
+
return lang;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
await loaded.loadLanguage(lang as BundledLanguage);
|
|
32
|
+
return lang;
|
|
33
|
+
} catch {
|
|
34
|
+
return PLAIN;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createCodeHighlighter(source: () => Promise<Highlighter>): CodeHighlighter {
|
|
39
|
+
let highlighter: Highlighter | null = null;
|
|
40
|
+
let highlighterLoad: Promise<Highlighter> | null = null;
|
|
41
|
+
const languages = new Map<string, string>();
|
|
42
|
+
const languageLoads = new Map<string, Promise<void>>();
|
|
43
|
+
const themes = new Set<string>();
|
|
44
|
+
const themeLoads = new Map<string, Promise<void>>();
|
|
45
|
+
const tokenizers = new Map<string, { tokenize: Tokenize<GrammarState>; fg: string | undefined }>();
|
|
46
|
+
|
|
47
|
+
const load = (): Promise<Highlighter> => {
|
|
48
|
+
highlighterLoad ??= source().then((loaded) => {
|
|
49
|
+
highlighter = loaded;
|
|
50
|
+
return loaded;
|
|
51
|
+
});
|
|
52
|
+
return highlighterLoad;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const ready = (langs: Iterable<string>, theme: string): Highlighter | null => {
|
|
56
|
+
if (highlighter === null || !themes.has(theme)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
for (const lang of langs) {
|
|
60
|
+
if (!languages.has(lang)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return highlighter;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const language = (lang: string): string => languages.get(lang) ?? PLAIN;
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
loadLanguage(lang) {
|
|
71
|
+
let pending = languageLoads.get(lang);
|
|
72
|
+
if (!pending) {
|
|
73
|
+
pending = load().then(async (loaded) => {
|
|
74
|
+
languages.set(lang, await grammarOf(loaded, lang));
|
|
75
|
+
});
|
|
76
|
+
languageLoads.set(lang, pending);
|
|
77
|
+
}
|
|
78
|
+
return pending;
|
|
79
|
+
},
|
|
80
|
+
loadTheme(theme, custom) {
|
|
81
|
+
let pending = themeLoads.get(theme);
|
|
82
|
+
if (!pending) {
|
|
83
|
+
pending = load().then(async (loaded) => {
|
|
84
|
+
// Loading a theme the highlighter already has replaces it, under whatever else of the host draws with it.
|
|
85
|
+
if (!loaded.getLoadedThemes().includes(theme)) {
|
|
86
|
+
await loaded.loadTheme(shikiThemeOf(theme, custom) as BundledTheme | ThemeRegistration);
|
|
87
|
+
}
|
|
88
|
+
themes.add(theme);
|
|
89
|
+
});
|
|
90
|
+
themeLoads.set(theme, pending);
|
|
91
|
+
}
|
|
92
|
+
return pending;
|
|
93
|
+
},
|
|
94
|
+
ready,
|
|
95
|
+
language,
|
|
96
|
+
tokenizer(lang, theme) {
|
|
97
|
+
const loaded = ready([lang], theme);
|
|
98
|
+
if (loaded === null) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const resolved = language(lang);
|
|
102
|
+
const key = `${resolved}:${theme}`;
|
|
103
|
+
let entry = tokenizers.get(key);
|
|
104
|
+
if (!entry) {
|
|
105
|
+
const options = { lang: resolved as BundledLanguage, theme };
|
|
106
|
+
entry = {
|
|
107
|
+
tokenize: (code, state) => {
|
|
108
|
+
const result = loaded.codeToTokens(code, state ? { ...options, grammarState: state } : options);
|
|
109
|
+
return { lines: result.tokens, state: result.grammarState };
|
|
110
|
+
},
|
|
111
|
+
fg: loaded.getTheme(theme).fg
|
|
112
|
+
};
|
|
113
|
+
tokenizers.set(key, entry);
|
|
114
|
+
}
|
|
115
|
+
return entry;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Shiki loads on the first code it draws, not with the app; the web bundle covers the languages a coding agent writes.
|
|
121
|
+
function loadBundled(): Promise<Highlighter> {
|
|
122
|
+
return import('shiki/bundle/web').then(({ createHighlighter }) => createHighlighter({ themes: [], langs: [] }) as Promise<Highlighter>);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let bundled: CodeHighlighter | null = null;
|
|
126
|
+
const borrowed = new WeakMap<() => Promise<Highlighter>, CodeHighlighter>();
|
|
127
|
+
|
|
128
|
+
/* The highlighter a host lends, which stays the host's, or without one the web bundle's of this package. */
|
|
129
|
+
export function codeHighlighter(source?: () => Promise<Highlighter>): CodeHighlighter {
|
|
130
|
+
if (source === undefined) {
|
|
131
|
+
bundled ??= createCodeHighlighter(loadBundled);
|
|
132
|
+
return bundled;
|
|
133
|
+
}
|
|
134
|
+
let highlighter = borrowed.get(source);
|
|
135
|
+
if (highlighter === undefined) {
|
|
136
|
+
highlighter = createCodeHighlighter(source);
|
|
137
|
+
borrowed.set(source, highlighter);
|
|
138
|
+
}
|
|
139
|
+
return highlighter;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* The code highlighter of the host this is rendered under. */
|
|
143
|
+
export function useCodeHighlighter(): CodeHighlighter {
|
|
144
|
+
return codeHighlighter(useChatHost().code.highlighter);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* Loads the grammar of one language into the web bundle's highlighter, once. */
|
|
148
|
+
export function loadLanguage(lang: string): Promise<void> {
|
|
149
|
+
return codeHighlighter().loadLanguage(lang);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Loads a theme into the web bundle's highlighter, once: one of `custom` by its name, or a bundled one by its id. */
|
|
153
|
+
export function loadTheme(theme: string, custom: ChatHost['code']['custom']): Promise<void> {
|
|
154
|
+
return codeHighlighter().loadTheme(theme, custom);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* The web bundle's highlighter once every one of `langs` and `theme` is in, or null while one is still on its way. */
|
|
158
|
+
export function readyHighlighter(langs: Iterable<string>, theme: string): Highlighter | null {
|
|
159
|
+
return codeHighlighter().ready(langs, theme);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* The language the web bundle draws `lang` in: the bundled grammar, or plain text. Only meaningful once a load settled. */
|
|
163
|
+
export function loadedLanguage(lang: string): string {
|
|
164
|
+
return codeHighlighter().language(lang);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* The web bundle's tokenizer for a language in a theme, or null while shiki, the grammar or the theme is still on its way. */
|
|
168
|
+
export function tokenizerFor(lang: string, theme: string): { tokenize: Tokenize<GrammarState>; fg: string | undefined } | null {
|
|
169
|
+
return codeHighlighter().tokenizer(lang, theme);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* The inline style of one token; undefined when it only has the surrounding color. */
|
|
173
|
+
export function tokenStyle(token: CodeToken): CSSProperties | undefined {
|
|
174
|
+
const style = token.fontStyle ?? 0;
|
|
175
|
+
if (token.color === undefined && style <= 0) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
const decorations = [style & 4 ? 'underline' : '', style & 8 ? 'line-through' : ''].filter(Boolean).join(' ');
|
|
179
|
+
return {
|
|
180
|
+
color: token.color,
|
|
181
|
+
...(style & 1 ? { fontStyle: 'italic' } : {}),
|
|
182
|
+
...(style & 2 ? { fontWeight: 'bold' } : {}),
|
|
183
|
+
...(decorations ? { textDecoration: decorations } : {})
|
|
184
|
+
};
|
|
185
|
+
}
|