@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,64 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import { chatHost } from '../host';
|
|
3
|
+
import { useChatScope } from '../scope';
|
|
4
|
+
import { ChatTransportError } from '../transport';
|
|
5
|
+
/* Every action as the request a chat host answers, for an app that runs them through nothing of its own. */
|
|
6
|
+
export const transportActions = (transport) => ({
|
|
7
|
+
clear: async (chatId, force) => {
|
|
8
|
+
await transport.request('chat.clear', { chatId, ...(force ? { force: true } : {}) });
|
|
9
|
+
},
|
|
10
|
+
stopTurn: async (chatId, subagents) => {
|
|
11
|
+
await transport.request('chat.cancel', { chatId, ...(subagents ? { subagents: true } : {}) });
|
|
12
|
+
},
|
|
13
|
+
unqueue: async (chatId, messageId) => {
|
|
14
|
+
const { message } = await transport.request('chat.unqueue', { chatId, messageId });
|
|
15
|
+
// A host answers without the message once it went out, which is what the composer tells a person.
|
|
16
|
+
if (message === undefined) {
|
|
17
|
+
throw new ChatTransportError('request-not-found', i18next.t('agent-chat:composer.queue.alreadySent'));
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
sendNow: async (chatId, messageId) => {
|
|
21
|
+
await transport.request('chat.sendNow', { chatId, messageId });
|
|
22
|
+
},
|
|
23
|
+
compact: async (chatId) => {
|
|
24
|
+
await transport.request('chat.compact', { chatId });
|
|
25
|
+
},
|
|
26
|
+
configure: async (chatId, patch) => {
|
|
27
|
+
await transport.request('chat.configure', { chatId, ...patch });
|
|
28
|
+
},
|
|
29
|
+
continueOn: async (chatId, account) => {
|
|
30
|
+
await transport.request('chat.continueOn', { chatId, account });
|
|
31
|
+
},
|
|
32
|
+
turnDiff: async (chatId, turnId) => (await transport.request('chat.turnDiff', { chatId, turnId })).diff,
|
|
33
|
+
stopSubagent: async (chatId, toolUseId) => {
|
|
34
|
+
await transport.request('chat.stopSubagent', { chatId, toolUseId });
|
|
35
|
+
},
|
|
36
|
+
stopTask: async (chatId, taskId) => {
|
|
37
|
+
await transport.request('chat.stopTask', { chatId, taskId });
|
|
38
|
+
},
|
|
39
|
+
approve: async (chatId, requestId, decision, message) => {
|
|
40
|
+
await transport.request('chat.approve', { chatId, requestId, decision, ...(message === undefined ? {} : { message }) });
|
|
41
|
+
},
|
|
42
|
+
answer: async (chatId, requestId, answers) => {
|
|
43
|
+
await transport.request('chat.answer', { chatId, requestId, answers });
|
|
44
|
+
},
|
|
45
|
+
dismiss: async (chatId, itemId) => {
|
|
46
|
+
await transport.request('chat.dismiss', { chatId, itemId });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const byTransport = new WeakMap();
|
|
50
|
+
/* The app's own actions when it has them, and the requests of this scope's host otherwise. */
|
|
51
|
+
export const actionsOf = (scope) => {
|
|
52
|
+
const own = chatHost().actions;
|
|
53
|
+
if (own !== null) {
|
|
54
|
+
return own;
|
|
55
|
+
}
|
|
56
|
+
const { transport } = scope;
|
|
57
|
+
let actions = byTransport.get(transport);
|
|
58
|
+
if (actions === undefined) {
|
|
59
|
+
actions = transportActions(transport);
|
|
60
|
+
byTransport.set(transport, actions);
|
|
61
|
+
}
|
|
62
|
+
return actions;
|
|
63
|
+
};
|
|
64
|
+
export const useChatActions = () => actionsOf(useChatScope());
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ChatAttachment, type ChatAttachmentUpload } from '@ruimte/agent-contracts';
|
|
2
|
+
interface IncomingFile {
|
|
3
|
+
name: string;
|
|
4
|
+
mime: string;
|
|
5
|
+
bytes: number;
|
|
6
|
+
}
|
|
7
|
+
interface AttachmentCheck<T extends IncomingFile> {
|
|
8
|
+
accepted: T[];
|
|
9
|
+
rejected: Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
reason: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export declare const isImageAttachment: (mime: string) => boolean;
|
|
15
|
+
export declare const fileBadge: (name: string) => string | null;
|
|
16
|
+
export declare const formatBytes: (bytes: number) => string;
|
|
17
|
+
export declare const checkAttachmentLimits: <T extends IncomingFile>(current: number, incoming: T[], currentBytes?: number) => AttachmentCheck<T>;
|
|
18
|
+
export declare const filesOf: (transfer: DataTransfer | null) => File[];
|
|
19
|
+
export declare const readAttachments: (files: File[]) => Promise<ChatAttachmentUpload[]>;
|
|
20
|
+
export declare const readStoredAttachments: (read: (attachment: ChatAttachment) => Promise<Blob>, stored: readonly ChatAttachment[]) => Promise<ChatAttachmentUpload[]>;
|
|
21
|
+
export declare const uploadPreviewUrl: (upload: ChatAttachmentUpload) => string;
|
|
22
|
+
export declare const uploadBytes: (upload: ChatAttachmentUpload) => number;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import { attachmentBytes, attachmentImageMime, CHAT_ATTACHMENTS_MAX_BYTES, CHAT_ATTACHMENTS_MAX_COUNT, CHAT_ATTACHMENT_MAX_BYTES } from '@ruimte/agent-contracts';
|
|
3
|
+
import { formatBytes as bytesOf } from '@ruimte/ui/format/number';
|
|
4
|
+
export const isImageAttachment = (mime) => mime.startsWith('image/');
|
|
5
|
+
/* The type on a file card: the extension, as long as it is short enough to read as one. */
|
|
6
|
+
export const fileBadge = (name) => {
|
|
7
|
+
const dot = name.lastIndexOf('.');
|
|
8
|
+
if (dot <= 0 || dot === name.length - 1) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const extension = name.slice(dot + 1);
|
|
12
|
+
return extension.length <= 5 ? extension.toUpperCase() : null;
|
|
13
|
+
};
|
|
14
|
+
/* Whole kilobytes, which is all a row under a file name has room for, and a decimal once it runs into megabytes. */
|
|
15
|
+
export const formatBytes = (bytes) => bytesOf(bytes, bytes < 1024 * 1024);
|
|
16
|
+
/* Which of the incoming files fit next to what the composer already holds, and why the rest do not. */
|
|
17
|
+
export const checkAttachmentLimits = (current, incoming, currentBytes = 0) => {
|
|
18
|
+
const accepted = [];
|
|
19
|
+
const rejected = [];
|
|
20
|
+
let bytes = currentBytes;
|
|
21
|
+
for (const file of incoming) {
|
|
22
|
+
if (file.bytes === 0) {
|
|
23
|
+
rejected.push({ name: file.name, reason: i18next.t('agent-chat:attachments.rejected.empty') });
|
|
24
|
+
}
|
|
25
|
+
else if (file.bytes > CHAT_ATTACHMENT_MAX_BYTES) {
|
|
26
|
+
rejected.push({ name: file.name, reason: i18next.t('agent-chat:attachments.rejected.tooLarge', { size: formatBytes(CHAT_ATTACHMENT_MAX_BYTES) }) });
|
|
27
|
+
}
|
|
28
|
+
else if (current + accepted.length >= CHAT_ATTACHMENTS_MAX_COUNT) {
|
|
29
|
+
rejected.push({ name: file.name, reason: i18next.t('agent-chat:attachments.rejected.tooMany', { count: CHAT_ATTACHMENTS_MAX_COUNT }) });
|
|
30
|
+
}
|
|
31
|
+
else if (bytes + file.bytes > CHAT_ATTACHMENTS_MAX_BYTES) {
|
|
32
|
+
rejected.push({
|
|
33
|
+
name: file.name,
|
|
34
|
+
reason: i18next.t('agent-chat:attachments.rejected.tooHeavy', { size: formatBytes(CHAT_ATTACHMENTS_MAX_BYTES) })
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
accepted.push(file);
|
|
39
|
+
bytes += file.bytes;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { accepted, rejected };
|
|
43
|
+
};
|
|
44
|
+
/* The files in a paste or a drop; a paste of plain text gives none. */
|
|
45
|
+
export const filesOf = (transfer) => (transfer ? [...transfer.files] : []);
|
|
46
|
+
const readAsBase64 = (file) => new Promise((resolve, reject) => {
|
|
47
|
+
const reader = new FileReader();
|
|
48
|
+
reader.onerror = () => reject(reader.error ?? new Error(i18next.t('agent-chat:attachments.unreadable', { name: file.name })));
|
|
49
|
+
reader.onload = () => {
|
|
50
|
+
const url = String(reader.result);
|
|
51
|
+
resolve(url.slice(url.indexOf(',') + 1));
|
|
52
|
+
};
|
|
53
|
+
reader.readAsDataURL(file);
|
|
54
|
+
});
|
|
55
|
+
// A pasted screenshot has no name; the clock gives it one so the row and the agent can tell them apart.
|
|
56
|
+
const nameFor = (file, index) => file.name || `pasted-${Date.now()}-${index + 1}.${file.type.split('/')[1] ?? 'bin'}`;
|
|
57
|
+
export const readAttachments = async (files) => Promise.all(files.map(async (file, index) => ({
|
|
58
|
+
name: nameFor(file, index),
|
|
59
|
+
mime: attachmentImageMime({ name: file.name, mime: file.type }) ?? (file.type || 'application/octet-stream'),
|
|
60
|
+
data: await readAsBase64(file)
|
|
61
|
+
})));
|
|
62
|
+
/* Files the host already stored for a chat, back as uploads the composer can hold again. */
|
|
63
|
+
export const readStoredAttachments = async (read, stored) => {
|
|
64
|
+
const files = await Promise.all(stored.map(async (attachment) => new File([await read(attachment)], attachment.name, { type: attachment.mime })));
|
|
65
|
+
return readAttachments(files);
|
|
66
|
+
};
|
|
67
|
+
/* A preview of a file the composer still holds; the bytes have not reached the host yet. */
|
|
68
|
+
export const uploadPreviewUrl = (upload) => `data:${upload.mime};base64,${upload.data}`;
|
|
69
|
+
/* What the file weighs, from the base64 the composer is holding: three bytes per four characters. */
|
|
70
|
+
export const uploadBytes = (upload) => attachmentBytes(upload.data);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ChatBookmark } from '@ruimte/agent-contracts';
|
|
2
|
+
import type { ChatScope } from '../scope';
|
|
3
|
+
interface BookmarkNaming {
|
|
4
|
+
chatKey: string | null;
|
|
5
|
+
itemId: string | null;
|
|
6
|
+
open(chatKey: string, itemId: string): void;
|
|
7
|
+
close(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare const useBookmarkNaming: import("zustand").UseBoundStore<import("zustand").StoreApi<BookmarkNaming>>;
|
|
10
|
+
export declare const placeBookmark: (scope: ChatScope, chatId: string, itemId: string) => Promise<void>;
|
|
11
|
+
export declare const nameBookmark: (scope: ChatScope, chatId: string, itemId: string, name: string, previous: string | undefined) => Promise<void>;
|
|
12
|
+
export declare const removeBookmark: (scope: ChatScope, chatId: string, bookmark: ChatBookmark) => Promise<void>;
|
|
13
|
+
export declare const goToBookmark: (scope: ChatScope, chatId: string, itemId: string, rename?: boolean) => boolean;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import { create } from 'zustand';
|
|
3
|
+
import { chatHost } from '../host';
|
|
4
|
+
import { jumpToTimelineItem } from './timeline-scroll';
|
|
5
|
+
export const useBookmarkNaming = create((set) => ({
|
|
6
|
+
chatKey: null,
|
|
7
|
+
itemId: null,
|
|
8
|
+
open: (chatKey, itemId) => set({ chatKey, itemId }),
|
|
9
|
+
close: () => set({ chatKey: null, itemId: null })
|
|
10
|
+
}));
|
|
11
|
+
const failed = (title, e) => {
|
|
12
|
+
chatHost().notify({ title, description: e instanceof Error ? e.message : String(e), kind: 'error' });
|
|
13
|
+
};
|
|
14
|
+
const add = async (scope, chatId, itemId, name) => {
|
|
15
|
+
try {
|
|
16
|
+
await scope.chats.addBookmark(chatId, itemId, name);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
failed(i18next.t('agent-chat:bookmarks.addFailed'), e);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/* Marks a message and opens the field for its name at once, before the host answered. */
|
|
25
|
+
export const placeBookmark = async (scope, chatId, itemId) => {
|
|
26
|
+
useBookmarkNaming.getState().open(scope.keyOf(chatId), itemId);
|
|
27
|
+
if (!(await add(scope, chatId, itemId))) {
|
|
28
|
+
useBookmarkNaming.getState().close();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
/*
|
|
32
|
+
* The name a field settles on. A name goes through `addBookmark`, which also names a bookmark that
|
|
33
|
+
* already stands, so a name typed before the mark itself landed is not refused.
|
|
34
|
+
*/
|
|
35
|
+
export const nameBookmark = async (scope, chatId, itemId, name, previous) => {
|
|
36
|
+
const next = name.trim();
|
|
37
|
+
if (next === (previous ?? '')) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (next !== '') {
|
|
41
|
+
await add(scope, chatId, itemId, next);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
await scope.chats.renameBookmark(chatId, itemId, '');
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
failed(i18next.t('agent-chat:bookmarks.renameFailed'), e);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
/* Takes a bookmark away at once; the toast puts it back, name and all. */
|
|
52
|
+
export const removeBookmark = async (scope, chatId, bookmark) => {
|
|
53
|
+
try {
|
|
54
|
+
await scope.chats.removeBookmark(chatId, bookmark.itemId);
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
failed(i18next.t('agent-chat:bookmarks.removeFailed'), e);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
chatHost().notify({
|
|
61
|
+
title: i18next.t('agent-chat:bookmarks.removed'),
|
|
62
|
+
kind: 'deleted',
|
|
63
|
+
action: { label: i18next.t('agent-chat:common.action.undo'), run: () => void add(scope, chatId, bookmark.itemId, bookmark.name) }
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
/* Scrolls the chat's thread to the message, and opens its name field for a rename. False when no thread of it is on screen yet. */
|
|
67
|
+
export const goToBookmark = (scope, chatId, itemId, rename = false) => {
|
|
68
|
+
const key = scope.keyOf(chatId);
|
|
69
|
+
if (rename) {
|
|
70
|
+
useBookmarkNaming.getState().open(key, itemId);
|
|
71
|
+
}
|
|
72
|
+
return jumpToTimelineItem(key, itemId);
|
|
73
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AgentKind, ChatAttachmentUpload, ChatAttachResult, ChatBookmark, ChatPreferencesPayload, ChatSkill, ModelSelection, ProviderInfo, RuntimeMode } from '@ruimte/agent-contracts';
|
|
2
|
+
import type { ChatSink } from '../state/chats';
|
|
3
|
+
import { type ChatTransport } from '../transport';
|
|
4
|
+
interface ChatOpenOptions {
|
|
5
|
+
provider?: AgentKind;
|
|
6
|
+
account?: string;
|
|
7
|
+
cwd?: string;
|
|
8
|
+
resume?: string;
|
|
9
|
+
selection?: ModelSelection;
|
|
10
|
+
runtimeMode?: RuntimeMode;
|
|
11
|
+
}
|
|
12
|
+
export interface ChatSendExtras {
|
|
13
|
+
mentions?: string[];
|
|
14
|
+
skills?: string[];
|
|
15
|
+
chats?: string[];
|
|
16
|
+
attachments?: ChatAttachmentUpload[];
|
|
17
|
+
}
|
|
18
|
+
interface ProviderSink {
|
|
19
|
+
setProviders(providers: ProviderInfo[]): void;
|
|
20
|
+
}
|
|
21
|
+
export declare class ChatClient {
|
|
22
|
+
private readonly transport;
|
|
23
|
+
private readonly sink;
|
|
24
|
+
private readonly providers;
|
|
25
|
+
private readonly mounted;
|
|
26
|
+
private readonly unsubscribe;
|
|
27
|
+
private preferences;
|
|
28
|
+
constructor(transport: ChatTransport, sink: ChatSink, providers?: ProviderSink | null);
|
|
29
|
+
private readonly inspections;
|
|
30
|
+
inspect(chatId: string): Promise<ChatAttachResult>;
|
|
31
|
+
open(chatId: string, options: ChatOpenOptions): Promise<boolean>;
|
|
32
|
+
retarget(chatId: string, provider: AgentKind, selection: ModelSelection): Promise<void>;
|
|
33
|
+
setPreferences(preferences: ChatPreferencesPayload): void;
|
|
34
|
+
detach(chatId: string): Promise<void>;
|
|
35
|
+
send(chatId: string, text: string, extras?: ChatSendExtras): Promise<{
|
|
36
|
+
queued: boolean;
|
|
37
|
+
turnId?: string;
|
|
38
|
+
}>;
|
|
39
|
+
addBookmark(chatId: string, itemId: string, name?: string): Promise<ChatBookmark[]>;
|
|
40
|
+
renameBookmark(chatId: string, itemId: string, name: string): Promise<ChatBookmark[]>;
|
|
41
|
+
removeBookmark(chatId: string, itemId: string): Promise<ChatBookmark[]>;
|
|
42
|
+
listSkills(chatId: string): Promise<ChatSkill[]>;
|
|
43
|
+
clear(chatId: string, force?: boolean): Promise<void>;
|
|
44
|
+
kill(chatId: string): Promise<void>;
|
|
45
|
+
loadStatuses(): Promise<void>;
|
|
46
|
+
loadProviders(): Promise<void>;
|
|
47
|
+
isMounted(chatId: string): boolean;
|
|
48
|
+
dispose(): void;
|
|
49
|
+
private attach;
|
|
50
|
+
private sendPreferences;
|
|
51
|
+
private onStatus;
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { MountedRegistry } from '../mounted-registry';
|
|
2
|
+
import { isConnectionError } from '../transport';
|
|
3
|
+
/*
|
|
4
|
+
* One chat per chat id on one host. A chat opens on mount and detaches on unmount, and every mounted
|
|
5
|
+
* chat is attached again when the transport comes back. The provider list is fetched once per
|
|
6
|
+
* connection and handed to its own store. The transport it is given never changes hosts, so a
|
|
7
|
+
* reattach stays on the host these chats run on.
|
|
8
|
+
*/
|
|
9
|
+
export class ChatClient {
|
|
10
|
+
transport;
|
|
11
|
+
sink;
|
|
12
|
+
providers;
|
|
13
|
+
mounted = new MountedRegistry();
|
|
14
|
+
unsubscribe = [];
|
|
15
|
+
preferences = null;
|
|
16
|
+
constructor(transport, sink, providers = null) {
|
|
17
|
+
this.transport = transport;
|
|
18
|
+
this.sink = sink;
|
|
19
|
+
this.providers = providers;
|
|
20
|
+
this.unsubscribe.push(transport.on('chat.event', ({ chatId, event, seq }) => {
|
|
21
|
+
this.sink.apply(chatId, event);
|
|
22
|
+
const entry = this.mounted.get(chatId);
|
|
23
|
+
if (entry && seq !== undefined) {
|
|
24
|
+
entry.seq = seq;
|
|
25
|
+
}
|
|
26
|
+
}),
|
|
27
|
+
// Every chat on the machine, attached or not, so a node waiting on a person says so on a view nobody has open.
|
|
28
|
+
transport.on('chat.status', ({ chatId, info }) => this.sink.status(chatId, info)), transport.on('chat.bookmarks', ({ chatId, bookmarks }) => this.sink.bookmarks(chatId, bookmarks)), transport.subscribeStatus((status) => this.onStatus(status)));
|
|
29
|
+
if (transport.status === 'open') {
|
|
30
|
+
void this.loadProviders();
|
|
31
|
+
void this.loadStatuses();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
inspections = new Map();
|
|
35
|
+
inspect(chatId) {
|
|
36
|
+
const pending = this.inspections.get(chatId);
|
|
37
|
+
if (pending) {
|
|
38
|
+
return pending;
|
|
39
|
+
}
|
|
40
|
+
const read = this.transport.request('chat.attach', { chatId, historyLimit: 100 }).finally(async () => {
|
|
41
|
+
this.inspections.delete(chatId);
|
|
42
|
+
// A visible chat owns its attachment; an inspection must never release it.
|
|
43
|
+
if (!this.mounted.get(chatId)) {
|
|
44
|
+
await this.transport.request('chat.detach', { chatId }).catch(() => undefined);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
this.inspections.set(chatId, read);
|
|
48
|
+
return read;
|
|
49
|
+
}
|
|
50
|
+
/* Answers false when the transport is not connected; the chat opens once it is. */
|
|
51
|
+
async open(chatId, options) {
|
|
52
|
+
this.mounted.set(chatId, { ...options, attached: false });
|
|
53
|
+
try {
|
|
54
|
+
await this.attach(chatId);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
if (isConnectionError(e)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/*
|
|
65
|
+
* Points a chat that has not spoken yet at another CLI. The host fixes a chat's provider when
|
|
66
|
+
* it registers the chat, so the only way over is to drop the empty one and register it again.
|
|
67
|
+
*/
|
|
68
|
+
async retarget(chatId, provider, selection) {
|
|
69
|
+
const entry = this.mounted.get(chatId);
|
|
70
|
+
if (!entry) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// The account belonged to the CLI the chat leaves.
|
|
74
|
+
const { attached: _attached, account: _account, ...options } = entry;
|
|
75
|
+
this.mounted.delete(chatId);
|
|
76
|
+
await this.transport.request('chat.kill', { chatId });
|
|
77
|
+
this.sink.forget(chatId);
|
|
78
|
+
await this.open(chatId, { ...options, provider, selection });
|
|
79
|
+
}
|
|
80
|
+
/*
|
|
81
|
+
* Tells the host what a chat it starts on its own is made with while this socket is connected,
|
|
82
|
+
* since no client mounting that chat sends a composer preference. Said again on every fresh socket.
|
|
83
|
+
*/
|
|
84
|
+
setPreferences(preferences) {
|
|
85
|
+
this.preferences = preferences;
|
|
86
|
+
this.sendPreferences();
|
|
87
|
+
}
|
|
88
|
+
async detach(chatId) {
|
|
89
|
+
const entry = this.mounted.get(chatId);
|
|
90
|
+
this.mounted.delete(chatId);
|
|
91
|
+
if (!entry?.attached) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
await this.transport.request('chat.detach', { chatId });
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// The socket closing detaches every chat server-side anyway.
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/* Current hosts return the stable turn id; older compatible ones only report whether the message queued. */
|
|
102
|
+
async send(chatId, text, extras = {}) {
|
|
103
|
+
return this.transport.request('chat.send', {
|
|
104
|
+
chatId,
|
|
105
|
+
text,
|
|
106
|
+
mentions: extras.mentions,
|
|
107
|
+
skills: extras.skills,
|
|
108
|
+
chats: extras.chats,
|
|
109
|
+
attachments: extras.attachments
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/* Marks a message; the list every client of the chat holds comes back as `chat.bookmarks`. */
|
|
113
|
+
async addBookmark(chatId, itemId, name) {
|
|
114
|
+
const { bookmarks } = await this.transport.request('chat.addBookmark', { chatId, itemId, ...(name === undefined ? {} : { name }) });
|
|
115
|
+
return bookmarks;
|
|
116
|
+
}
|
|
117
|
+
/* An empty name takes the name away. */
|
|
118
|
+
async renameBookmark(chatId, itemId, name) {
|
|
119
|
+
const { bookmarks } = await this.transport.request('chat.renameBookmark', { chatId, itemId, name });
|
|
120
|
+
return bookmarks;
|
|
121
|
+
}
|
|
122
|
+
async removeBookmark(chatId, itemId) {
|
|
123
|
+
const { bookmarks } = await this.transport.request('chat.removeBookmark', { chatId, itemId });
|
|
124
|
+
return bookmarks;
|
|
125
|
+
}
|
|
126
|
+
/* What this chat's CLI would run as a skill, for the composer's `$` picker. */
|
|
127
|
+
async listSkills(chatId) {
|
|
128
|
+
const { skills } = await this.transport.request('skills.list', { chatId });
|
|
129
|
+
return skills;
|
|
130
|
+
}
|
|
131
|
+
/* Starts the chat over; refused with `chat-busy` while a turn runs, unless `force` stops that turn. */
|
|
132
|
+
async clear(chatId, force = false) {
|
|
133
|
+
await this.transport.request('chat.clear', { chatId, ...(force ? { force: true } : {}) });
|
|
134
|
+
}
|
|
135
|
+
async kill(chatId) {
|
|
136
|
+
this.mounted.delete(chatId);
|
|
137
|
+
this.sink.forget(chatId);
|
|
138
|
+
await this.transport.request('chat.kill', { chatId });
|
|
139
|
+
}
|
|
140
|
+
/*
|
|
141
|
+
* What every chat on this machine is doing right now. `chat.status` only carries a change, so a
|
|
142
|
+
* window that just opened or came back from a lost socket asks for the standing answer.
|
|
143
|
+
*/
|
|
144
|
+
async loadStatuses() {
|
|
145
|
+
try {
|
|
146
|
+
const { chats } = await this.transport.request('chat.list', {});
|
|
147
|
+
for (const info of chats) {
|
|
148
|
+
this.sink.status(info.chatId, info);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// The next status event says it instead; an older host never answers this at all.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async loadProviders() {
|
|
156
|
+
if (!this.providers) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const { providers } = await this.transport.request('provider.list', {});
|
|
161
|
+
this.providers.setProviders(providers);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// The list comes with the next connection; pickers show what they had.
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
isMounted(chatId) {
|
|
168
|
+
return this.mounted.has(chatId);
|
|
169
|
+
}
|
|
170
|
+
/* Lets go of the machine; the chats keep running there, the host only stops streaming them here. */
|
|
171
|
+
dispose() {
|
|
172
|
+
for (const off of this.unsubscribe) {
|
|
173
|
+
off();
|
|
174
|
+
}
|
|
175
|
+
this.unsubscribe.length = 0;
|
|
176
|
+
for (const chatId of [...this.mounted.keys()]) {
|
|
177
|
+
void this.detach(chatId);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/*
|
|
181
|
+
* A reattach offers the last seq the store holds. The host answers only what came after it when
|
|
182
|
+
* it still has all of that, and the whole thread otherwise, so a short drop costs a few events.
|
|
183
|
+
*/
|
|
184
|
+
async attach(chatId) {
|
|
185
|
+
const entry = this.mounted.get(chatId);
|
|
186
|
+
await this.transport.request('chat.create', {
|
|
187
|
+
chatId,
|
|
188
|
+
provider: entry?.provider,
|
|
189
|
+
account: entry?.account,
|
|
190
|
+
cwd: entry?.cwd,
|
|
191
|
+
resume: entry?.resume,
|
|
192
|
+
selection: entry?.selection,
|
|
193
|
+
runtimeMode: entry?.runtimeMode
|
|
194
|
+
});
|
|
195
|
+
const since = entry?.seq;
|
|
196
|
+
const result = await this.transport.request('chat.attach', { chatId, ...(since === undefined ? {} : { since }) });
|
|
197
|
+
const current = this.mounted.get(chatId);
|
|
198
|
+
if (!current) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
current.attached = true;
|
|
202
|
+
if (result.seq !== undefined) {
|
|
203
|
+
current.seq = result.seq;
|
|
204
|
+
}
|
|
205
|
+
if (result.events && since !== undefined && current === entry) {
|
|
206
|
+
for (const event of result.events) {
|
|
207
|
+
this.sink.apply(chatId, event);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
this.sink.reset(chatId, result.info, result.items);
|
|
212
|
+
}
|
|
213
|
+
// A host without bookmarks sends none, and then this chat has none.
|
|
214
|
+
this.sink.bookmarks(chatId, result.bookmarks ?? []);
|
|
215
|
+
}
|
|
216
|
+
sendPreferences() {
|
|
217
|
+
if (this.preferences === null) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
void this.transport.request('chat.setPreferences', this.preferences).catch(() => undefined);
|
|
221
|
+
}
|
|
222
|
+
onStatus(status) {
|
|
223
|
+
if (status === 'open') {
|
|
224
|
+
this.sendPreferences();
|
|
225
|
+
void this.loadProviders();
|
|
226
|
+
void this.loadStatuses();
|
|
227
|
+
void this.mounted.reattachAll((chatId) => this.attach(chatId).then(() => undefined));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
this.mounted.detachAll();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/* The chats an `@query` offers: never the chat typing it or one already attached. */
|
|
2
|
+
export const chatSuggestions = (chats, query, exclude, limit) => {
|
|
3
|
+
const needle = query.toLowerCase();
|
|
4
|
+
return chats.filter((chat) => !exclude.includes(chat.id) && chat.title.toLowerCase().includes(needle)).slice(0, limit);
|
|
5
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ChatAttachmentUpload } from '@ruimte/agent-contracts';
|
|
2
|
+
export interface ChatDraft {
|
|
3
|
+
text: string;
|
|
4
|
+
mentions: string[];
|
|
5
|
+
skills: string[];
|
|
6
|
+
chats: string[];
|
|
7
|
+
attachments: ChatAttachmentUpload[];
|
|
8
|
+
quote: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const EMPTY_DRAFT: ChatDraft;
|
|
11
|
+
export declare const isEmptyDraft: (draft: ChatDraft) => boolean;
|
|
12
|
+
export declare const useDrafts: import("zustand").UseBoundStore<import("zustand").StoreApi<{
|
|
13
|
+
ids: string[];
|
|
14
|
+
}>>;
|
|
15
|
+
export declare const useHasDraft: (chatId: string) => boolean;
|
|
16
|
+
export declare const readDraft: (chatId: string) => ChatDraft;
|
|
17
|
+
export declare const writeDraft: (chatId: string, draft: ChatDraft) => void;
|
|
18
|
+
export declare const joinDraftText: (current: string, added: string) => string;
|
|
19
|
+
export declare const takeBackIntoDraft: (current: ChatDraft, taken: Omit<ChatDraft, 'quote'>) => {
|
|
20
|
+
draft: ChatDraft;
|
|
21
|
+
rejected: Array<{
|
|
22
|
+
name: string;
|
|
23
|
+
reason: string;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
type DraftTaker = (text: string) => void;
|
|
27
|
+
export declare const takeDraftOffers: (chatId: string, take: DraftTaker) => (() => void);
|
|
28
|
+
export declare const offerDraft: (chatId: string, text: string) => void;
|
|
29
|
+
export {};
|