@adecore/agents-react 0.38.0 → 0.40.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/dist/agents/AgentModelLabel.d.ts +10 -0
- package/dist/agents/AgentModelLabel.js +98 -0
- package/dist/agents/model-name.js +2 -1
- package/dist/agents/provider-paths.d.ts +2 -1
- package/dist/agents/provider-paths.js +7 -3
- package/dist/chat/account-choice.js +57 -28
- package/dist/chat/bookmarks.js +2 -1
- package/dist/chat/drafts.d.ts +0 -1
- package/dist/chat/drafts.js +9 -28
- package/dist/chat/logic/picker-entries.js +2 -8
- package/dist/chat/logic/resume-compaction.js +2 -5
- package/dist/chat/logic/timeline-copy.js +2 -8
- package/dist/chat/logic/timeline.js +9 -10
- package/dist/chat/logic/workflow.js +2 -4
- package/dist/chat/subagent-conversation.js +2 -1
- package/dist/chat/ui/Composer.js +385 -360
- package/dist/chat/ui/GoalDock.js +2 -1
- package/dist/chat/ui/LimitState.js +4 -6
- package/dist/chat/ui/Pickers.d.ts +2 -1
- package/dist/chat/ui/Pickers.js +126 -123
- package/dist/chat/ui/PromptEditor.d.ts +19 -0
- package/dist/chat/ui/PromptEditor.js +115 -0
- package/dist/chat/ui/Timeline.d.ts +2 -1
- package/dist/chat/ui/Timeline.js +13 -1
- package/dist/chat/ui/composer/ComposerControls.d.ts +3 -0
- package/dist/chat/ui/composer/ComposerControls.js +235 -38
- package/dist/chat/ui/composer/key-handler.d.ts +1 -1
- package/dist/chat/ui/composer/key-handler.js +3 -11
- package/dist/chat/ui/composer/prompt-picks.d.ts +4 -0
- package/dist/chat/ui/composer/prompt-picks.js +14 -0
- package/dist/chat/ui/composer/use-composer-menus.d.ts +4 -49
- package/dist/chat/ui/composer/use-composer-menus.js +65 -519
- package/dist/chat/ui/composer/use-continuable-turn.js +0 -1
- package/dist/chat/ui/composer/use-notice.js +2 -1
- package/dist/chat/ui/composer/use-prompt-history.js +1 -1
- package/dist/chat/ui/composer/use-prompt-menus.d.ts +73 -0
- package/dist/chat/ui/composer/use-prompt-menus.js +499 -0
- package/dist/chat/ui/composer/use-take-back.js +2 -2
- package/dist/chat/ui/diff/ApprovalReview.js +1 -1
- package/dist/chat/ui/highlighter.d.ts +0 -6
- package/dist/chat/ui/highlighter.js +0 -12
- package/dist/chat/ui/icons.d.ts +0 -1
- package/dist/chat/ui/icons.js +0 -3
- package/dist/chat/ui/index.d.ts +3 -0
- package/dist/chat/ui/index.js +2 -0
- package/dist/chat/ui/rows/MessageRows.js +2 -5
- package/dist/chat/ui/rows/row-rhythm.js +2 -5
- package/dist/chat/ui/theme-watch.js +3 -7
- package/dist/host-hooks.d.ts +2 -1
- package/dist/host-hooks.js +6 -0
- package/dist/host.d.ts +17 -1
- package/dist/index.d.ts +1 -1
- package/dist/locales/en/agent-chat.d.ts +1 -0
- package/dist/locales/en/agent-chat.js +1 -0
- package/dist/locales/nl/agent-chat.d.ts +1 -0
- package/dist/locales/nl/agent-chat.js +1 -0
- package/dist/prompts/logic/usePromptSession.js +2 -1
- package/dist/providers/AddAccountForm.js +2 -1
- package/dist/providers/CliDetail.js +104 -130
- package/dist/providers/account-actions.js +6 -9
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.js +1 -0
- package/dist/state/chats.d.ts +1 -0
- package/dist/state/chats.js +6 -11
- package/dist/state/provider-accounts.d.ts +1 -1
- package/dist/state/provider-accounts.js +2 -8
- package/dist/theme.css +3 -0
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +14 -0
- package/dist/usage/ChartLogo.d.ts +5 -0
- package/dist/usage/ChartLogo.js +48 -0
- package/dist/usage/LimitsList.js +6 -16
- package/dist/usage/UsageBreakdown.js +17 -40
- package/dist/usage/UsageLimits.js +183 -211
- package/dist/usage/UsagePage.js +3 -17
- package/dist/usage/UsageSummary.js +4 -9
- package/dist/usage/limits.js +16 -31
- package/dist/usage/summary.d.ts +1 -1
- package/dist/usage/summary.js +3 -5
- package/package.json +26 -26
- package/src/agents/AccountDot.tsx +1 -0
- package/src/agents/AgentIcon.tsx +2 -0
- package/src/agents/AgentModelLabel.tsx +38 -0
- package/src/agents/ModelOptionControl.tsx +8 -0
- package/src/agents/account-limits.ts +3 -0
- package/src/agents/accounts.ts +12 -0
- package/src/agents/model-name.ts +13 -1
- package/src/agents/model-options.ts +2 -0
- package/src/agents/model-requirement.ts +4 -0
- package/src/agents/option-words.ts +2 -0
- package/src/agents/provider-paths.ts +8 -3
- package/src/agents/status-look.ts +1 -0
- package/src/chat/account-choice.ts +25 -19
- package/src/chat/actions.ts +5 -0
- package/src/chat/attachments.ts +8 -0
- package/src/chat/bookmarks.ts +13 -1
- package/src/chat/chat-client.ts +63 -0
- package/src/chat/chat-references.ts +3 -0
- package/src/chat/composer-effort.ts +5 -0
- package/src/chat/composer-send.ts +4 -0
- package/src/chat/drafts.ts +21 -17
- package/src/chat/forks.ts +4 -0
- package/src/chat/guards.ts +2 -0
- package/src/chat/index.ts +7 -0
- package/src/chat/logic/ansi.ts +47 -0
- package/src/chat/logic/bookmarks.ts +7 -0
- package/src/chat/logic/command-looks.ts +29 -0
- package/src/chat/logic/context-usage.ts +3 -0
- package/src/chat/logic/continue-after-stop.ts +5 -0
- package/src/chat/logic/effort.ts +4 -0
- package/src/chat/logic/embedded-code.ts +63 -0
- package/src/chat/logic/fork.ts +22 -0
- package/src/chat/logic/generated-image.ts +7 -0
- package/src/chat/logic/goal.ts +7 -0
- package/src/chat/logic/handback.ts +2 -0
- package/src/chat/logic/limit.ts +8 -0
- package/src/chat/logic/picker-entries.ts +16 -4
- package/src/chat/logic/resume-compaction.ts +22 -5
- package/src/chat/logic/scrubber.ts +38 -5
- package/src/chat/logic/thought.ts +5 -0
- package/src/chat/logic/thread-cards.ts +6 -0
- package/src/chat/logic/timeline-copy.ts +4 -9
- package/src/chat/logic/timeline-target.ts +15 -0
- package/src/chat/logic/timeline.ts +113 -10
- package/src/chat/logic/tools.ts +30 -0
- package/src/chat/logic/visual-height.ts +5 -0
- package/src/chat/logic/visual-viewport.ts +1 -0
- package/src/chat/logic/welcome.ts +3 -0
- package/src/chat/logic/workflow.ts +26 -7
- package/src/chat/mentions.ts +56 -0
- package/src/chat/pending-prompts.ts +16 -0
- package/src/chat/persisted-json.ts +15 -0
- package/src/chat/preferences.ts +16 -0
- package/src/chat/prompt-hold.ts +2 -0
- package/src/chat/quote.ts +7 -0
- package/src/chat/reading-width.ts +1 -0
- package/src/chat/recent-messages.ts +5 -0
- package/src/chat/resume-compaction-dismissals.ts +1 -0
- package/src/chat/stash.ts +12 -0
- package/src/chat/subagent-conversation.ts +24 -4
- package/src/chat/subagent-list.ts +14 -0
- package/src/chat/subagent-support.ts +1 -0
- package/src/chat/subagent-view.ts +9 -0
- package/src/chat/thumbnails.ts +14 -0
- package/src/chat/timeline-flash.ts +2 -0
- package/src/chat/timeline-scroll.ts +23 -0
- package/src/chat/ui/AccountPill.tsx +4 -0
- package/src/chat/ui/AnsiOutput.tsx +1 -0
- package/src/chat/ui/BookmarkMarker.tsx +5 -0
- package/src/chat/ui/BookmarkSubmenu.tsx +1 -0
- package/src/chat/ui/ChatActivity.tsx +13 -0
- package/src/chat/ui/CodeBlock.tsx +8 -0
- package/src/chat/ui/Composer.tsx +27 -3
- package/src/chat/ui/ComposerInput.tsx +22 -0
- package/src/chat/ui/DiffPool.tsx +3 -0
- package/src/chat/ui/EditDiff.tsx +5 -0
- package/src/chat/ui/GoalDock.tsx +13 -1
- package/src/chat/ui/ImageView.tsx +11 -0
- package/src/chat/ui/LimitState.tsx +23 -6
- package/src/chat/ui/Markdown.tsx +41 -0
- package/src/chat/ui/MermaidBlock.tsx +16 -0
- package/src/chat/ui/MessageActions.tsx +8 -0
- package/src/chat/ui/ModelMarks.tsx +1 -0
- package/src/chat/ui/Pickers.tsx +30 -1
- package/src/chat/ui/PromptComposer.tsx +2 -0
- package/src/chat/ui/PromptEditor.tsx +136 -0
- package/src/chat/ui/QuoteButton.tsx +12 -0
- package/src/chat/ui/ReferenceChip.tsx +3 -0
- package/src/chat/ui/ResumeCompactionDock.tsx +1 -0
- package/src/chat/ui/RowTime.tsx +1 -0
- package/src/chat/ui/RunSettings.tsx +38 -0
- package/src/chat/ui/Scrubber.tsx +14 -0
- package/src/chat/ui/SessionLine.tsx +1 -0
- package/src/chat/ui/ShellCommand.tsx +32 -0
- package/src/chat/ui/SubagentControls.tsx +7 -0
- package/src/chat/ui/SubagentInfo.tsx +1 -0
- package/src/chat/ui/SubagentStopButton.tsx +6 -0
- package/src/chat/ui/SubagentTimeline.tsx +10 -0
- package/src/chat/ui/Timeline.tsx +141 -1
- package/src/chat/ui/TimelineMenu.tsx +5 -0
- package/src/chat/ui/UnifiedDiff.tsx +6 -0
- package/src/chat/ui/UploadThumb.tsx +2 -0
- package/src/chat/ui/VisualDialogs.tsx +3 -0
- package/src/chat/ui/VisualFrame.tsx +15 -0
- package/src/chat/ui/Welcome.tsx +1 -0
- package/src/chat/ui/chip-clipboard.ts +20 -0
- package/src/chat/ui/code-lines.ts +14 -0
- package/src/chat/ui/code-theme.ts +1 -0
- package/src/chat/ui/composer/ComposerControls.tsx +70 -15
- package/src/chat/ui/composer/ComposerMenu.tsx +12 -0
- package/src/chat/ui/composer/DraftParts.tsx +2 -0
- package/src/chat/ui/composer/QueuedMessages.tsx +4 -0
- package/src/chat/ui/composer/chip-paste.ts +16 -0
- package/src/chat/ui/composer/chips.ts +6 -0
- package/src/chat/ui/composer/editor.ts +9 -0
- package/src/chat/ui/composer/insert.ts +2 -0
- package/src/chat/ui/composer/key-handler.ts +42 -9
- package/src/chat/ui/composer/keys.ts +23 -0
- package/src/chat/ui/composer/placeholder.ts +6 -0
- package/src/chat/ui/composer/prompt-picks.ts +18 -0
- package/src/chat/ui/composer/surface.ts +5 -0
- package/src/chat/ui/composer/use-chat-controls.ts +13 -0
- package/src/chat/ui/composer/use-compaction-offer.ts +3 -0
- package/src/chat/ui/composer/use-composer-menus.ts +20 -275
- package/src/chat/ui/composer/use-composer-submit.ts +27 -0
- package/src/chat/ui/composer/use-continuable-turn.ts +3 -5
- package/src/chat/ui/composer/use-draft-files.ts +24 -0
- package/src/chat/ui/composer/use-effort-stepper.ts +8 -0
- package/src/chat/ui/composer/use-notice.ts +4 -1
- package/src/chat/ui/composer/use-pending-requests.ts +3 -0
- package/src/chat/ui/composer/use-prompt-history.ts +20 -11
- package/src/chat/ui/composer/use-prompt-hold.ts +3 -0
- package/src/chat/ui/composer/use-prompt-menus.ts +361 -0
- package/src/chat/ui/composer/use-take-back.ts +20 -2
- package/src/chat/ui/diff/ApprovalReview.tsx +18 -15
- package/src/chat/ui/diff/DiffFallbackBoundary.tsx +1 -0
- package/src/chat/ui/diff/DiffOmitted.tsx +1 -0
- package/src/chat/ui/diff/DiffStack.tsx +2 -0
- package/src/chat/ui/diff/DiffUnavailable.tsx +1 -0
- package/src/chat/ui/diff/EditorEditDiff.tsx +13 -0
- package/src/chat/ui/diff/EditorPatchDiff.tsx +4 -0
- package/src/chat/ui/diff/LegacyDiffPool.tsx +2 -0
- package/src/chat/ui/diff/LegacyEditDiff.tsx +2 -0
- package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +8 -0
- package/src/chat/ui/diff/RawDiff.tsx +1 -0
- package/src/chat/ui/diff/ReviewDiff.tsx +1 -0
- package/src/chat/ui/diff/diff-source-budget.ts +8 -0
- package/src/chat/ui/diff/editor-edit-model.ts +4 -0
- package/src/chat/ui/diff/review-plan.ts +9 -0
- package/src/chat/ui/diff/review-settle.ts +1 -0
- package/src/chat/ui/diff/syntax-runtime.ts +52 -0
- package/src/chat/ui/diff/use-review-stack.ts +9 -0
- package/src/chat/ui/diff-syntax.ts +12 -0
- package/src/chat/ui/diff-theme.ts +2 -0
- package/src/chat/ui/file-links.ts +2 -0
- package/src/chat/ui/find-reveal.ts +1 -0
- package/src/chat/ui/full-diff.ts +1 -0
- package/src/chat/ui/highlighter.ts +29 -15
- package/src/chat/ui/icons.tsx +2 -4
- package/src/chat/ui/index.ts +7 -0
- package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +25 -0
- package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +1 -0
- package/src/chat/ui/intelligent-ui/UiReply.tsx +63 -0
- package/src/chat/ui/intelligent-ui/block-local.ts +5 -0
- package/src/chat/ui/intelligent-ui/chart-data.ts +15 -0
- package/src/chat/ui/intelligent-ui/node-text.ts +17 -0
- package/src/chat/ui/intelligent-ui/renderers/binding.ts +1 -0
- package/src/chat/ui/intelligent-ui/renderers/chart.tsx +31 -0
- package/src/chat/ui/intelligent-ui/renderers/choices.tsx +4 -0
- package/src/chat/ui/intelligent-ui/renderers/content.tsx +18 -0
- package/src/chat/ui/intelligent-ui/renderers/data.tsx +11 -0
- package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +13 -0
- package/src/chat/ui/intelligent-ui/renderers/links.tsx +9 -0
- package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +15 -0
- package/src/chat/ui/intelligent-ui/renderers/map-style.ts +15 -0
- package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +48 -0
- package/src/chat/ui/intelligent-ui/renderers/map.tsx +20 -0
- package/src/chat/ui/intelligent-ui/renderers/place-point.ts +3 -0
- package/src/chat/ui/intelligent-ui/renderers/structure.tsx +3 -0
- package/src/chat/ui/intelligent-ui/renderers/text.tsx +8 -0
- package/src/chat/ui/intelligent-ui/reply-parts.ts +5 -0
- package/src/chat/ui/intelligent-ui/table-data.ts +17 -0
- package/src/chat/ui/intelligent-ui/use-in-view.ts +4 -0
- package/src/chat/ui/intelligent-ui/use-later.ts +4 -0
- package/src/chat/ui/intelligent-ui/use-truncated.ts +5 -0
- package/src/chat/ui/intelligent-ui/use-ui-links.ts +29 -0
- package/src/chat/ui/intelligent-ui/use-ui-queries.ts +60 -0
- package/src/chat/ui/markdown-blocks.ts +64 -0
- package/src/chat/ui/mermaid.ts +4 -0
- package/src/chat/ui/quote-selection.ts +10 -0
- package/src/chat/ui/rehype-chips.ts +5 -0
- package/src/chat/ui/rehype-fade.ts +2 -0
- package/src/chat/ui/remark-html-as-text.ts +5 -0
- package/src/chat/ui/reveal.ts +18 -0
- package/src/chat/ui/rows/ForksRow.tsx +5 -0
- package/src/chat/ui/rows/GeneratedImageRow.tsx +10 -0
- package/src/chat/ui/rows/MessageRows.tsx +29 -5
- package/src/chat/ui/rows/RowContainer.tsx +1 -0
- package/src/chat/ui/rows/Rows.tsx +2 -0
- package/src/chat/ui/rows/SubagentRow.tsx +13 -0
- package/src/chat/ui/rows/TaskRow.tsx +7 -0
- package/src/chat/ui/rows/VisualRow.tsx +4 -0
- package/src/chat/ui/rows/WorkRows.tsx +46 -0
- package/src/chat/ui/rows/WorkflowRow.tsx +10 -0
- package/src/chat/ui/rows/row-rhythm.ts +7 -5
- package/src/chat/ui/theme-watch.ts +8 -7
- package/src/chat/ui/thread-references.ts +7 -0
- package/src/chat/ui/use-diff-source-tokens.ts +8 -0
- package/src/chat/ui/useToggleSet.ts +3 -0
- package/src/chat/ui/visual-theme.ts +30 -0
- package/src/chat/visual-bridge.ts +23 -0
- package/src/chat/visual-viewport.ts +12 -0
- package/src/host-hooks.ts +15 -5
- package/src/host.ts +29 -1
- package/src/index.ts +5 -0
- package/src/lazy.tsx +9 -0
- package/src/locales/en/agent-chat.ts +1 -0
- package/src/locales/nl/agent-chat.ts +1 -0
- package/src/mounted-registry.ts +4 -0
- package/src/port-transport.ts +32 -0
- package/src/prompts/index.ts +2 -0
- package/src/prompts/logic/focus.ts +5 -0
- package/src/prompts/logic/keys.ts +12 -0
- package/src/prompts/logic/prompts.ts +8 -0
- package/src/prompts/logic/subjects.ts +8 -0
- package/src/prompts/logic/useFocusAfterAnswer.ts +2 -0
- package/src/prompts/logic/usePromptSession.ts +13 -1
- package/src/prompts/ui/ApprovalBody.tsx +2 -0
- package/src/prompts/ui/PromptActions.tsx +2 -0
- package/src/prompts/ui/PromptCard.tsx +6 -0
- package/src/prompts/ui/PromptView.tsx +6 -0
- package/src/prompts/ui/QuestionBody.tsx +11 -0
- package/src/providers/AccountColors.tsx +1 -0
- package/src/providers/AccountDetail.tsx +7 -0
- package/src/providers/AccountVariables.tsx +2 -0
- package/src/providers/AddAccountForm.tsx +5 -1
- package/src/providers/CliDetail.tsx +6 -5
- package/src/providers/InstallPage.tsx +11 -0
- package/src/providers/ProviderList.tsx +3 -0
- package/src/providers/ProvidersPane.tsx +18 -0
- package/src/providers/account-actions.ts +10 -8
- package/src/providers/index.ts +1 -0
- package/src/providers/install-steps.ts +3 -0
- package/src/providers/pane-target.ts +5 -0
- package/src/providers/provider-abilities.ts +1 -0
- package/src/providers/provider-groups.ts +6 -0
- package/src/providers/variables.ts +12 -0
- package/src/scope.ts +2 -0
- package/src/state/chats.ts +61 -14
- package/src/state/index.ts +2 -0
- package/src/state/provider-accounts.ts +7 -9
- package/src/state/providers.ts +1 -0
- package/src/state/usage.ts +6 -0
- package/src/storage.ts +18 -0
- package/src/testing/prompts.ts +5 -0
- package/src/theme.css +3 -0
- package/src/transport.ts +18 -0
- package/src/usage/ChartLogo.tsx +12 -0
- package/src/usage/LimitsList.tsx +7 -8
- package/src/usage/UsageBreakdown.tsx +11 -4
- package/src/usage/UsageChart.tsx +5 -0
- package/src/usage/UsageLimits.tsx +10 -7
- package/src/usage/UsageLimitsCard.tsx +2 -0
- package/src/usage/UsagePage.tsx +16 -19
- package/src/usage/UsageSummary.tsx +4 -5
- package/src/usage/UsageTiles.tsx +2 -0
- package/src/usage/format.ts +7 -0
- package/src/usage/limit-groups.ts +14 -0
- package/src/usage/limits.ts +11 -13
- package/src/usage/money.ts +1 -0
- package/src/usage/summary.ts +15 -7
|
@@ -1,48 +1,17 @@
|
|
|
1
|
-
import { useEffect,
|
|
1
|
+
import { useEffect, useState, type RefObject } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import { ensureSyntaxTree, syntaxTree } from '@codemirror/language';
|
|
4
|
-
import type { EditorState } from '@codemirror/state';
|
|
5
3
|
import type { ChatInfo, ChatSkill, ProviderCapabilities } from '@adecore/agent-contracts';
|
|
6
|
-
import { useChatHost } from '../../../host';
|
|
7
4
|
import { useChatScope } from '../../../scope';
|
|
8
|
-
import { referenceSuggestions, referenceTokens, type Referable } from '../../chat-references';
|
|
9
|
-
import { usableSlashCommands } from '../../guards';
|
|
10
|
-
import {
|
|
11
|
-
chipRanges,
|
|
12
|
-
findMentionQuery,
|
|
13
|
-
findReferenceQuery,
|
|
14
|
-
findSkillQuery,
|
|
15
|
-
insertMention,
|
|
16
|
-
insertReference,
|
|
17
|
-
insertSkill,
|
|
18
|
-
mentionPick,
|
|
19
|
-
NO_MENTION_SEARCH,
|
|
20
|
-
queryOutsideChips,
|
|
21
|
-
type MentionQuery,
|
|
22
|
-
type MentionSearch,
|
|
23
|
-
type TextRange
|
|
24
|
-
} from '../../mentions';
|
|
25
5
|
import type { ComposerInputHandle } from '../ComposerInput';
|
|
26
6
|
import type { ComposerDraft } from './use-composer-draft';
|
|
27
|
-
import {
|
|
28
|
-
import { clampMenuIndex, composerMenuAria } from './menu-aria';
|
|
7
|
+
import { usePromptMenus, type PromptMenus } from './use-prompt-menus';
|
|
29
8
|
import { useHostReferables } from '../../../host-hooks';
|
|
30
9
|
|
|
31
|
-
|
|
32
|
-
// What fits above the composer without turning the picker into a file tree.
|
|
33
|
-
const MENTION_RESULTS = 8;
|
|
34
|
-
const REFERENCE_RESULTS = 8;
|
|
10
|
+
export type { ComposerCommand } from './use-prompt-menus';
|
|
35
11
|
|
|
36
12
|
// Commands the composer handles itself; the CLI's own ones are sent through as text.
|
|
37
13
|
export const LOCAL_COMMANDS = ['model', 'compact', 'clear'];
|
|
38
14
|
|
|
39
|
-
export interface ComposerCommand {
|
|
40
|
-
name: string;
|
|
41
|
-
hint: string;
|
|
42
|
-
local: boolean;
|
|
43
|
-
skill: boolean;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
15
|
interface MenusInput {
|
|
47
16
|
chatId: string;
|
|
48
17
|
info: ChatInfo;
|
|
@@ -52,62 +21,12 @@ interface MenusInput {
|
|
|
52
21
|
inputRef: RefObject<ComposerInputHandle | null>;
|
|
53
22
|
}
|
|
54
23
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/*
|
|
60
|
-
* The pickers over the box: `/` for a command, `$` for a skill, `@` for a file and `#` for a view,
|
|
61
|
-
* a node or another chat. One is open at a time, and the arrow keys and the editor's ARIA attributes
|
|
62
|
-
* follow that one.
|
|
63
|
-
*/
|
|
64
|
-
export function useComposerMenus({ chatId, info, capabilities, providerName, draft: { draft, setDraft, setText }, inputRef }: MenusInput) {
|
|
24
|
+
/* The pickers over a chat's composer, fed by what the chat's CLI has: its skills, its commands and its folder. */
|
|
25
|
+
export function useComposerMenus({ chatId, info, capabilities, providerName, draft, inputRef }: MenusInput): PromptMenus {
|
|
65
26
|
const { t } = useTranslation('agent-chat');
|
|
66
27
|
const scope = useChatScope();
|
|
67
|
-
const host = useChatHost();
|
|
68
|
-
const { searchFiles } = host;
|
|
69
28
|
const referables = useHostReferables(scope.id, chatId);
|
|
70
|
-
const [menuIndex, setMenuIndex] = useState(0);
|
|
71
|
-
const [mention, setMention] = useState<MentionQuery | null>(null);
|
|
72
|
-
const [referenceQuery, setReferenceQuery] = useState<MentionQuery | null>(null);
|
|
73
|
-
const [skillQuery, setSkillQuery] = useState<MentionQuery | null>(null);
|
|
74
29
|
const [skills, setSkills] = useState<ChatSkill[]>([]);
|
|
75
|
-
const [searched, setSearched] = useState<MentionSearch>(NO_MENTION_SEARCH);
|
|
76
|
-
const text = draft.text;
|
|
77
|
-
const goals = capabilities?.goals;
|
|
78
|
-
|
|
79
|
-
// Every keystroke inside an `@word` searches again; a slow answer must not overwrite a newer one.
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
if (mention === null) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
let stale = false;
|
|
85
|
-
const search = (): void => {
|
|
86
|
-
if (searchFiles === null) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
searchFiles(scope.id, info.cwd, mention.query, MENTION_RESULTS)
|
|
90
|
-
.then((files) => {
|
|
91
|
-
if (!stale) {
|
|
92
|
-
setSearched({ query: mention.query, files });
|
|
93
|
-
setMenuIndex(0);
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
.catch(() => undefined);
|
|
97
|
-
};
|
|
98
|
-
// A bare `@` is not a keystroke in a query. It opens the list of what is there, at once.
|
|
99
|
-
if (mention.query === '') {
|
|
100
|
-
search();
|
|
101
|
-
return () => {
|
|
102
|
-
stale = true;
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
const timer = setTimeout(search, SEARCH_DEBOUNCE_MS);
|
|
106
|
-
return () => {
|
|
107
|
-
stale = true;
|
|
108
|
-
clearTimeout(timer);
|
|
109
|
-
};
|
|
110
|
-
}, [scope, info.cwd, mention, searchFiles]);
|
|
111
30
|
|
|
112
31
|
/*
|
|
113
32
|
* The host's skill list, once per chat, again once the CLI announced its own after the first message,
|
|
@@ -115,6 +34,7 @@ export function useComposerMenus({ chatId, info, capabilities, providerName, dra
|
|
|
115
34
|
*/
|
|
116
35
|
useEffect(() => {
|
|
117
36
|
let stale = false;
|
|
37
|
+
|
|
118
38
|
scope.chats
|
|
119
39
|
.listSkills(chatId)
|
|
120
40
|
.then((found) => {
|
|
@@ -123,201 +43,26 @@ export function useComposerMenus({ chatId, info, capabilities, providerName, dra
|
|
|
123
43
|
}
|
|
124
44
|
})
|
|
125
45
|
.catch(() => undefined);
|
|
46
|
+
|
|
126
47
|
return () => {
|
|
127
48
|
stale = true;
|
|
128
49
|
};
|
|
129
50
|
}, [scope, chatId, info.skills, info.running]);
|
|
130
51
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return [];
|
|
135
|
-
}
|
|
136
|
-
const known = new Set(skills.map((skill) => skill.name));
|
|
137
|
-
const own = LOCAL_COMMANDS.map((name) => ({ name, hint: t(`composer.commands.${name}`), local: true, skill: false }));
|
|
138
|
-
// Every CLI with goals reads `/goal`, also one that lists no commands of its own.
|
|
139
|
-
const goal = goals === undefined ? [] : [{ name: 'goal', hint: t('composer.commands.goal'), local: false, skill: false }];
|
|
140
|
-
const cli = usableSlashCommands(info.slashCommands)
|
|
141
|
-
.filter((name) => !LOCAL_COMMANDS.includes(name) && (goals === undefined || name !== 'goal'))
|
|
142
|
-
.map((name) => ({
|
|
143
|
-
name,
|
|
144
|
-
hint: known.has(name) ? t('composer.commands.skill') : t('composer.commands.cli', { cli: providerName ?? t('composer.commands.anyCli') }),
|
|
145
|
-
local: false,
|
|
146
|
-
skill: known.has(name)
|
|
147
|
-
}));
|
|
148
|
-
return [...own, ...goal, ...cli].filter((command) => command.name.startsWith(commandQuery)).slice(0, 8);
|
|
149
|
-
}, [commandQuery, goals, info.slashCommands, providerName, skills, t]);
|
|
150
|
-
|
|
151
|
-
const skillMatches = useMemo(() => {
|
|
152
|
-
if (skillQuery === null) {
|
|
153
|
-
return [];
|
|
154
|
-
}
|
|
155
|
-
const query = skillQuery.query.toLowerCase();
|
|
156
|
-
return skills.filter((skill) => skill.name.toLowerCase().includes(query)).slice(0, 8);
|
|
157
|
-
}, [skillQuery, skills]);
|
|
158
|
-
|
|
159
|
-
const commandMenuOpen = commandQuery !== null && commands.length > 0;
|
|
160
|
-
const skillMenuOpen = !commandMenuOpen && skillQuery !== null && skillMatches.length > 0;
|
|
161
|
-
const mentionMenuOpen = !commandMenuOpen && !skillMenuOpen && mention !== null;
|
|
162
|
-
// Results belong to the query that asked for them; a closed picker shows none while the next answer is on its way.
|
|
163
|
-
const files = mention === null ? [] : searched.files;
|
|
164
|
-
const mentionCount = files.length;
|
|
165
|
-
// A host with nothing to point at keeps `#fff` out of the way rather than saying nothing matches.
|
|
166
|
-
const offersReferences = referables.items.some((item) => !referables.chatsOnly || item.kind === 'chat');
|
|
167
|
-
const referenceMenuOpen = !commandMenuOpen && !skillMenuOpen && !mentionMenuOpen && referenceQuery !== null && offersReferences;
|
|
168
|
-
const referenceMatches = referenceMenuOpen
|
|
169
|
-
? referenceSuggestions(referables, referenceQuery.query, [chatId, ...draft.chats, ...draft.references], REFERENCE_RESULTS)
|
|
170
|
-
: [];
|
|
171
|
-
// What the editor draws as chips and the send checks are still in the text, under the name each was picked with.
|
|
172
|
-
const referenceChips = referenceTokens([...draft.chats, ...draft.references], referables.items, draft.referenceNames).map((token) => ({
|
|
173
|
-
...token,
|
|
174
|
-
kind: referables.items.find((item) => item.id === token.id)?.kind ?? null
|
|
175
|
-
}));
|
|
176
|
-
// One menu is open at a time, so they share an id.
|
|
177
|
-
const listId = useId();
|
|
178
|
-
const menuOpen = commandMenuOpen || skillMenuOpen || mentionMenuOpen || referenceMenuOpen;
|
|
179
|
-
const menuCount = commandMenuOpen ? commands.length : skillMenuOpen ? skillMatches.length : mentionMenuOpen ? mentionCount : referenceMatches.length;
|
|
180
|
-
// The list can shrink under a stale index; every reader takes the one that still points at an option.
|
|
181
|
-
const activeIndex = clampMenuIndex(menuIndex, menuCount);
|
|
182
|
-
const inputAria = composerMenuAria({ label: t('composer.inputLabel'), listId, open: menuOpen, activeIndex, count: menuCount });
|
|
183
|
-
|
|
184
|
-
/* Which picker the caret opens: `@` for a file, `$` for a skill, `#` for a reference, or none. In code a sigil is just text. */
|
|
185
|
-
const trackTriggers = (value: string, selection: TextRange, state: EditorState): void => {
|
|
186
|
-
const empty = selection.from === selection.to;
|
|
187
|
-
const tree = empty ? (ensureSyntaxTree(state, selection.from, 50) ?? syntaxTree(state)) : null;
|
|
188
|
-
const caret = tree !== null && !inCode(tree, value, selection.from) ? selection.from : null;
|
|
189
|
-
const chips = caret === null ? [] : chipRanges(value, draft.mentions, draft.skills, [], referenceChips);
|
|
190
|
-
const skill = caret === null ? null : queryOutsideChips(findSkillQuery(value, caret), chips);
|
|
191
|
-
setSkillQuery((current) => (sameQuery(current, skill) ? current : skill));
|
|
52
|
+
return usePromptMenus({
|
|
53
|
+
draft,
|
|
54
|
+
inputRef,
|
|
192
55
|
// A CLI that does not expand `@path` gets the text as it is, so the picker stays out of the way.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
// The inserted text reaches the editor with the render it causes, so the caret waits a frame for it.
|
|
200
|
-
const moveCaret = (caret: number): void => {
|
|
201
|
-
requestAnimationFrame(() => inputRef.current?.setCaret(caret));
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const close = (): void => {
|
|
205
|
-
setMention(null);
|
|
206
|
-
setSkillQuery(null);
|
|
207
|
-
setReferenceQuery(null);
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const chooseMention = (path: string): void => {
|
|
211
|
-
if (!mention) {
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
const result = insertMention(text, mention, path);
|
|
215
|
-
setText(result.text, draft.mentions.includes(path) ? draft.mentions : [...draft.mentions, path]);
|
|
216
|
-
setMention(null);
|
|
217
|
-
moveCaret(result.caret);
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
const chooseMentionAt = (index: number): void => {
|
|
221
|
-
const path = mention === null ? null : mentionPick(index, searched, mention.query);
|
|
222
|
-
if (path !== null) {
|
|
223
|
-
chooseMention(path);
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
/*
|
|
228
|
-
* A picked view, node or chat becomes a `#name` chip in the text. The draft keeps the name it was
|
|
229
|
-
* spelled with, since a rename would otherwise unmake the chip. A chat goes apart, as `chats`.
|
|
230
|
-
*/
|
|
231
|
-
const chooseReference = (item: Referable): void => {
|
|
232
|
-
if (!referenceQuery) {
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
const result = insertReference(text, referenceQuery, item.name);
|
|
236
|
-
const chat = item.kind === 'chat';
|
|
237
|
-
const add = (ids: string[]): string[] => (ids.includes(item.id) ? ids : [...ids, item.id]);
|
|
238
|
-
setDraft((current) => ({
|
|
239
|
-
...current,
|
|
240
|
-
text: result.text,
|
|
241
|
-
chats: chat ? add(current.chats) : current.chats,
|
|
242
|
-
references: chat ? current.references : add(current.references),
|
|
243
|
-
referenceNames: { ...current.referenceNames, [item.id]: item.name }
|
|
244
|
-
}));
|
|
245
|
-
setReferenceQuery(null);
|
|
246
|
-
moveCaret(result.caret);
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
const chooseReferenceAt = (index: number): void => {
|
|
250
|
-
const item = referenceMatches[index] ?? referenceMatches[0];
|
|
251
|
-
if (item !== undefined) {
|
|
252
|
-
chooseReference(item);
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
/*
|
|
257
|
-
* A picked skill becomes a `$name` chip in the text and a name on the send, which is what
|
|
258
|
-
* dispatches it. The `/` menu lands here too, so both spellings end up on the same path.
|
|
259
|
-
*/
|
|
260
|
-
const chooseSkill = (name: string): void => {
|
|
261
|
-
const result = skillQuery ? insertSkill(text, skillQuery, name) : { text: `$${name} `, caret: name.length + 2 };
|
|
262
|
-
setText(result.text, draft.mentions, draft.skills.includes(name) ? draft.skills : [...draft.skills, name]);
|
|
263
|
-
setSkillQuery(null);
|
|
264
|
-
moveCaret(result.caret);
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
/* A clicked command; Tab completes one with a space after it instead. */
|
|
268
|
-
const pickCommand = (command: ComposerCommand, index: number): void => {
|
|
269
|
-
setMenuIndex(index);
|
|
270
|
-
if (command.skill) {
|
|
271
|
-
chooseSkill(command.name);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
setText(`/${command.name}`);
|
|
275
|
-
inputRef.current?.focus();
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
/* The arrows walk an open picker round; true when the key was one of them. */
|
|
279
|
-
const moveInMenu = (key: string, count: number): boolean => {
|
|
280
|
-
if (key === 'ArrowDown') {
|
|
281
|
-
setMenuIndex((i) => (i + 1) % count);
|
|
282
|
-
return true;
|
|
283
|
-
}
|
|
284
|
-
if (key === 'ArrowUp') {
|
|
285
|
-
setMenuIndex((i) => (i - 1 + count) % count);
|
|
286
|
-
return true;
|
|
287
|
-
}
|
|
288
|
-
return false;
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
return {
|
|
292
|
-
listId,
|
|
293
|
-
activeIndex,
|
|
294
|
-
inputAria,
|
|
295
|
-
hasSkills: skills.length > 0,
|
|
296
|
-
skillNames: skills.map((skill) => skill.name),
|
|
297
|
-
commandQuery,
|
|
298
|
-
commands,
|
|
299
|
-
commandMenuOpen,
|
|
300
|
-
skillMatches,
|
|
301
|
-
skillMenuOpen,
|
|
302
|
-
mention,
|
|
303
|
-
mentionMenuOpen,
|
|
304
|
-
mentionCount,
|
|
305
|
-
files,
|
|
306
|
-
referenceMenuOpen,
|
|
307
|
-
referenceMatches,
|
|
56
|
+
cwd: capabilities?.mentions === false ? null : info.cwd,
|
|
57
|
+
skills,
|
|
58
|
+
slashCommands: info.slashCommands,
|
|
59
|
+
localCommands: LOCAL_COMMANDS,
|
|
60
|
+
goals: capabilities?.goals !== undefined,
|
|
61
|
+
providerName,
|
|
308
62
|
referables,
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
close,
|
|
313
|
-
chooseMention,
|
|
314
|
-
chooseMentionAt,
|
|
315
|
-
chooseReference,
|
|
316
|
-
chooseReferenceAt,
|
|
317
|
-
chooseSkill,
|
|
318
|
-
pickCommand,
|
|
319
|
-
moveInMenu
|
|
320
|
-
};
|
|
63
|
+
ownId: chatId,
|
|
64
|
+
label: t('composer.inputLabel')
|
|
65
|
+
});
|
|
321
66
|
}
|
|
322
67
|
|
|
323
|
-
export type ComposerMenus =
|
|
68
|
+
export type ComposerMenus = PromptMenus;
|
|
@@ -62,9 +62,11 @@ export function useComposerSubmit({
|
|
|
62
62
|
/* Opens the host's new chat once the host took the message; a host that answers nothing took it. */
|
|
63
63
|
const afterSend = (sent: void | Promise<boolean>, andNew: boolean): void => {
|
|
64
64
|
const openNewChat = host.openNewChat;
|
|
65
|
+
|
|
65
66
|
if (!andNew || openNewChat === null) {
|
|
66
67
|
return;
|
|
67
68
|
}
|
|
69
|
+
|
|
68
70
|
void Promise.resolve(sent)
|
|
69
71
|
.then((taken) => {
|
|
70
72
|
if (taken !== false) {
|
|
@@ -79,39 +81,53 @@ export function useComposerSubmit({
|
|
|
79
81
|
const delivery = steerable && (alternate ? sendDelivery === 'queue' : sendDelivery === 'steer') ? 'steer' : 'queue';
|
|
80
82
|
const { text, quote, quoteSource } = draft;
|
|
81
83
|
const trimmed = text.trim();
|
|
84
|
+
|
|
82
85
|
if (isEmptyDraft(draft) || !writable || guard.tooLong) {
|
|
83
86
|
return;
|
|
84
87
|
}
|
|
88
|
+
|
|
85
89
|
// Recheck restored drafts and files that finished reading concurrently.
|
|
86
90
|
const rejected = checkAttachmentLimits(
|
|
87
91
|
0,
|
|
88
92
|
draft.attachments.map((attachment) => ({ ...attachment, bytes: uploadBytes(attachment) }))
|
|
89
93
|
).rejected[0];
|
|
94
|
+
|
|
90
95
|
if (rejected) {
|
|
91
96
|
setNotice(`${rejected.name}: ${rejected.reason}`);
|
|
97
|
+
|
|
92
98
|
return;
|
|
93
99
|
}
|
|
100
|
+
|
|
94
101
|
hold.release('send');
|
|
95
102
|
const { commandQuery } = menus;
|
|
96
103
|
const chosen = menus.commands[menus.activeIndex];
|
|
104
|
+
|
|
97
105
|
if (commandQuery !== null && chosen?.local && controls.runCommand(chosen.name)) {
|
|
98
106
|
box.clear();
|
|
107
|
+
|
|
99
108
|
return;
|
|
100
109
|
}
|
|
110
|
+
|
|
101
111
|
if (commandQuery !== null && chosen?.skill) {
|
|
102
112
|
menus.chooseSkill(chosen.name);
|
|
113
|
+
|
|
103
114
|
return;
|
|
104
115
|
}
|
|
116
|
+
|
|
105
117
|
// Whoever sends wants to see what comes back, wherever they were reading.
|
|
106
118
|
scrollTimelineToEnd(chatId, 'instant');
|
|
119
|
+
|
|
107
120
|
if (commandQuery !== null && chosen && !chosen.local) {
|
|
108
121
|
afterSend(onSend(`/${chosen.name}`, {}), how === 'and-new');
|
|
109
122
|
box.clear();
|
|
123
|
+
|
|
110
124
|
return;
|
|
111
125
|
}
|
|
126
|
+
|
|
112
127
|
const chats = presentReferences(trimmed, draft.chats, menus.referenceChips);
|
|
113
128
|
// A machine without `viewReferences` drops the field, so only the chats it does know reach it.
|
|
114
129
|
const references = menus.referables.chatsOnly ? [] : presentReferences(trimmed, draft.references, menus.referenceChips);
|
|
130
|
+
|
|
115
131
|
const sent = onSend(withQuote(quote, trimmed), {
|
|
116
132
|
delivery,
|
|
117
133
|
mentions: presentMentions(trimmed, draft.mentions),
|
|
@@ -121,6 +137,7 @@ export function useComposerSubmit({
|
|
|
121
137
|
attachments: draft.attachments,
|
|
122
138
|
references: references.length > 0 ? references : undefined
|
|
123
139
|
});
|
|
140
|
+
|
|
124
141
|
afterSend(sent, how === 'and-new');
|
|
125
142
|
box.clear();
|
|
126
143
|
menus.close();
|
|
@@ -129,9 +146,11 @@ export function useComposerSubmit({
|
|
|
129
146
|
|
|
130
147
|
// A menu row means the draft as it stands when it is picked, so the registration reads the latest submit.
|
|
131
148
|
const submitRef = useRef(submit);
|
|
149
|
+
|
|
132
150
|
useEffect(() => {
|
|
133
151
|
submitRef.current = submit;
|
|
134
152
|
});
|
|
153
|
+
|
|
135
154
|
useEffect(() => registerComposerSender(scope.keyOf(chatId), (how) => submitRef.current(how)), [scope, chatId]);
|
|
136
155
|
|
|
137
156
|
/*
|
|
@@ -142,11 +161,13 @@ export function useComposerSubmit({
|
|
|
142
161
|
const restoreStashed = (prompt: StashedPrompt): void => {
|
|
143
162
|
const stashedQuote = prompt.quote;
|
|
144
163
|
const here = stashedQuote !== undefined && stashedQuote.chatId === chatId;
|
|
164
|
+
|
|
145
165
|
const { chats, dropped } = restorableChats(
|
|
146
166
|
prompt.chats,
|
|
147
167
|
new Set(menus.referables.items.filter((item) => item.kind === 'chat').map((item) => item.id)),
|
|
148
168
|
chatId
|
|
149
169
|
);
|
|
170
|
+
|
|
150
171
|
setDraft((current) => ({
|
|
151
172
|
...current,
|
|
152
173
|
text: stashedQuote === undefined || here ? prompt.text : withQuote(stashedQuote.text, prompt.text),
|
|
@@ -156,9 +177,11 @@ export function useComposerSubmit({
|
|
|
156
177
|
references: [...prompt.references],
|
|
157
178
|
...(here ? { quote: stashedQuote.text, quoteSource: stashedQuote.itemId } : {})
|
|
158
179
|
}));
|
|
180
|
+
|
|
159
181
|
if (dropped > 0) {
|
|
160
182
|
setNotice(t('composer.notice.stashChatsDropped', { count: dropped }), 'info');
|
|
161
183
|
}
|
|
184
|
+
|
|
162
185
|
history.forget();
|
|
163
186
|
inputRef.current?.focus();
|
|
164
187
|
};
|
|
@@ -168,6 +191,7 @@ export function useComposerSubmit({
|
|
|
168
191
|
if (!writable) {
|
|
169
192
|
return;
|
|
170
193
|
}
|
|
194
|
+
|
|
171
195
|
scrollTimelineToEnd(chatId, 'instant');
|
|
172
196
|
afterSend(onSend(CONTINUE_PROMPT, {}), false);
|
|
173
197
|
};
|
|
@@ -176,11 +200,14 @@ export function useComposerSubmit({
|
|
|
176
200
|
const toggleStash = (): void => {
|
|
177
201
|
if (isEmptyDraft(draft)) {
|
|
178
202
|
const newest = useStash.getState().prompts[0];
|
|
203
|
+
|
|
179
204
|
if (newest) {
|
|
180
205
|
restoreStashed(newest);
|
|
181
206
|
}
|
|
207
|
+
|
|
182
208
|
return;
|
|
183
209
|
}
|
|
210
|
+
|
|
184
211
|
if (stashDraft(draft, chatId)) {
|
|
185
212
|
box.clear();
|
|
186
213
|
menus.close();
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
1
|
import type { ChatInfo, ChatItem } from '@adecore/agent-contracts';
|
|
3
2
|
import { useChatRow } from '../../../state/chats';
|
|
4
3
|
import { continuableTurn } from '../../logic/continue-after-stop';
|
|
@@ -7,8 +6,7 @@ import { continuableTurn } from '../../logic/continue-after-stop';
|
|
|
7
6
|
export function useContinuableTurn(chatId: string, info: ChatInfo): string | null {
|
|
8
7
|
const order = useChatRow(chatId, (row) => row?.order);
|
|
9
8
|
const structure = useChatRow(chatId, (row) => row?.structure);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}, [info, order, structure]);
|
|
9
|
+
const items = (order ?? []).map((id) => structure?.[id]).filter((item): item is ChatItem => item !== undefined);
|
|
10
|
+
|
|
11
|
+
return continuableTurn(info, items);
|
|
14
12
|
}
|
|
@@ -54,9 +54,11 @@ export function useDraftFiles({
|
|
|
54
54
|
/* Paths dragged in from a list of files. With no mention query to insert into, they land at the end of the prompt. */
|
|
55
55
|
const addMentions = (paths: string[]): void => {
|
|
56
56
|
const fresh = paths.filter((path) => path !== '');
|
|
57
|
+
|
|
57
58
|
if (fresh.length === 0 || capabilities?.mentions === false) {
|
|
58
59
|
return;
|
|
59
60
|
}
|
|
61
|
+
|
|
60
62
|
const appended = fresh.map((path) => `@${path}`).join(' ');
|
|
61
63
|
const before = draft.text.replace(/\s+$/, '');
|
|
62
64
|
setText(before === '' ? `${appended} ` : `${before} ${appended} `, [...draft.mentions, ...fresh.filter((path) => !draft.mentions.includes(path))]);
|
|
@@ -66,21 +68,27 @@ export function useDraftFiles({
|
|
|
66
68
|
if (incoming.length === 0) {
|
|
67
69
|
return;
|
|
68
70
|
}
|
|
71
|
+
|
|
69
72
|
if (capabilities?.attachments === false) {
|
|
70
73
|
setNotice(t('composer.notice.noAttachments', { agent: providerName ?? t('composer.notice.thisAgent') }));
|
|
74
|
+
|
|
71
75
|
return;
|
|
72
76
|
}
|
|
77
|
+
|
|
73
78
|
const checked = checkAttachmentLimits(
|
|
74
79
|
draft.attachments.length,
|
|
75
80
|
incoming.map((file) => ({ name: file.name, mime: file.type, bytes: file.size, file })),
|
|
76
81
|
draft.attachments.reduce((bytes, attachment) => bytes + uploadBytes(attachment), 0)
|
|
77
82
|
);
|
|
83
|
+
|
|
78
84
|
if (checked.rejected[0]) {
|
|
79
85
|
setNotice(t('composer.notice.rejected', { name: checked.rejected[0].name || t('composer.notice.thatFile'), reason: checked.rejected[0].reason }));
|
|
80
86
|
}
|
|
87
|
+
|
|
81
88
|
if (checked.accepted.length === 0) {
|
|
82
89
|
return;
|
|
83
90
|
}
|
|
91
|
+
|
|
84
92
|
readAttachments(checked.accepted.map((entry) => entry.file))
|
|
85
93
|
.then((attachments) => setDraft((current) => ({ ...current, attachments: [...current.attachments, ...attachments] })))
|
|
86
94
|
.catch(() => setNotice(t('composer.notice.unreadable')));
|
|
@@ -94,24 +102,32 @@ export function useDraftFiles({
|
|
|
94
102
|
/* Files become attachments and a long text a file of its own; anything else the editor pastes as plain text. */
|
|
95
103
|
const onPaste = (e: ClipboardEvent, view: EditorView): boolean => {
|
|
96
104
|
const data = e.clipboardData;
|
|
105
|
+
|
|
97
106
|
if (!data) {
|
|
98
107
|
return false;
|
|
99
108
|
}
|
|
109
|
+
|
|
100
110
|
const pasted = filesOf(data);
|
|
111
|
+
|
|
101
112
|
if (pasted.length > 0) {
|
|
102
113
|
addFiles(pasted);
|
|
114
|
+
|
|
103
115
|
return true;
|
|
104
116
|
}
|
|
117
|
+
|
|
105
118
|
const inline = pasteInline.current;
|
|
106
119
|
pasteInline.current = false;
|
|
107
120
|
const clip = data.getData('text/plain');
|
|
108
121
|
const mentions = capabilities?.mentions === false ? [] : pastedMentions(clip);
|
|
122
|
+
|
|
109
123
|
if (mentions.length > 0) {
|
|
110
124
|
// The editor's own change reads the draft as it was rendered, so the mentions join it after that change.
|
|
111
125
|
view.dispatch(view.state.replaceSelection(clip), { scrollIntoView: true, userEvent: 'input.paste' });
|
|
112
126
|
setDraft((current) => ({ ...current, mentions: [...current.mentions, ...mentions.filter((path) => !current.mentions.includes(path))] }));
|
|
127
|
+
|
|
113
128
|
return true;
|
|
114
129
|
}
|
|
130
|
+
|
|
115
131
|
// Without attachments there is nowhere else for the text to go, so it pastes as it always did.
|
|
116
132
|
if (inline || capabilities?.attachments === false || !pasteBecomesAttachment(clip)) {
|
|
117
133
|
const choices: PasteChoices = {
|
|
@@ -120,21 +136,29 @@ export function useDraftFiles({
|
|
|
120
136
|
referenced: [...draft.chats, ...draft.references],
|
|
121
137
|
mentions: capabilities?.mentions !== false
|
|
122
138
|
};
|
|
139
|
+
|
|
123
140
|
const chips = pasteWithChips(view, clip, readClipboardChips(data), choices);
|
|
141
|
+
|
|
124
142
|
if (chips === null) {
|
|
125
143
|
return false;
|
|
126
144
|
}
|
|
145
|
+
|
|
127
146
|
// After the editor's own change, which reads the draft as it was rendered.
|
|
128
147
|
setDraft((current) => withPastedChips(current, chips, choices));
|
|
148
|
+
|
|
129
149
|
return true;
|
|
130
150
|
}
|
|
151
|
+
|
|
131
152
|
// The paste still replaces what was selected; it just puts nothing in its place.
|
|
132
153
|
const { from, to } = view.state.selection.main;
|
|
154
|
+
|
|
133
155
|
if (from !== to) {
|
|
134
156
|
view.dispatch({ changes: { from, to }, userEvent: 'delete' });
|
|
135
157
|
}
|
|
158
|
+
|
|
136
159
|
const name = pastedTextName(draft.attachments.map((attachment) => attachment.name));
|
|
137
160
|
addFiles([new File([clip], name, { type: 'text/plain' })]);
|
|
161
|
+
|
|
138
162
|
return true;
|
|
139
163
|
};
|
|
140
164
|
|
|
@@ -27,12 +27,16 @@ export function useEffortStepper({ chatId, info, model, writable, controls, setN
|
|
|
27
27
|
if (!writable || controls.configurationPending) {
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
|
+
|
|
30
31
|
const name = model?.name ?? info.selection.model;
|
|
31
32
|
const result = stepEffort(model, info.selection, direction);
|
|
33
|
+
|
|
32
34
|
if (result.kind === 'none') {
|
|
33
35
|
setNotice(t('composer.notice.noEffort', { model: name }), 'info');
|
|
36
|
+
|
|
34
37
|
return;
|
|
35
38
|
}
|
|
39
|
+
|
|
36
40
|
if (result.kind === 'edge') {
|
|
37
41
|
setNotice(
|
|
38
42
|
t(direction === 'up' ? 'composer.notice.effortHighest' : 'composer.notice.effortLowest', {
|
|
@@ -41,16 +45,20 @@ export function useEffortStepper({ chatId, info, model, writable, controls, setN
|
|
|
41
45
|
}),
|
|
42
46
|
'info'
|
|
43
47
|
);
|
|
48
|
+
|
|
44
49
|
return;
|
|
45
50
|
}
|
|
51
|
+
|
|
46
52
|
controls.configure({ selection: result.selection });
|
|
47
53
|
setNotice(t('composer.notice.effort', { effort: choiceWord(EFFORT_OPTION, result.choice) }), 'info');
|
|
48
54
|
};
|
|
49
55
|
|
|
50
56
|
// A command means the chat as it stands when it is given, so the registration reads the latest step.
|
|
51
57
|
const stepRef = useRef(step);
|
|
58
|
+
|
|
52
59
|
useEffect(() => {
|
|
53
60
|
stepRef.current = step;
|
|
54
61
|
});
|
|
62
|
+
|
|
55
63
|
useEffect(() => registerEffortStepper(scope.keyOf(chatId), (direction) => stepRef.current(direction)), [scope, chatId]);
|
|
56
64
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
|
+
import { errorMessage } from '../../../transport';
|
|
2
3
|
|
|
3
4
|
const NOTICE_MS = 4000;
|
|
4
5
|
|
|
@@ -20,7 +21,9 @@ export function useNotice(): ComposerNotice {
|
|
|
20
21
|
if (!shown) {
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
24
|
+
|
|
23
25
|
const timer = setTimeout(() => setShown(null), NOTICE_MS);
|
|
26
|
+
|
|
24
27
|
return () => clearTimeout(timer);
|
|
25
28
|
}, [shown]);
|
|
26
29
|
|
|
@@ -30,6 +33,6 @@ export function useNotice(): ComposerNotice {
|
|
|
30
33
|
notice: shown?.text ?? null,
|
|
31
34
|
tone: shown?.tone ?? 'error',
|
|
32
35
|
setNotice,
|
|
33
|
-
showError: (error) => setNotice(
|
|
36
|
+
showError: (error) => setNotice(errorMessage(error))
|
|
34
37
|
};
|
|
35
38
|
}
|
|
@@ -6,10 +6,13 @@ import { ChatPromptFeed } from '../../pending-prompts';
|
|
|
6
6
|
export function usePendingRequests(chatId: string): PromptSubject[] {
|
|
7
7
|
const scope = useChatScope();
|
|
8
8
|
const [subjects, setSubjects] = useState<PromptSubject[]>([]);
|
|
9
|
+
|
|
9
10
|
useEffect(() => {
|
|
10
11
|
const feed = new ChatPromptFeed(scope, chatId, setSubjects);
|
|
11
12
|
feed.start();
|
|
13
|
+
|
|
12
14
|
return () => feed.dispose();
|
|
13
15
|
}, [scope, chatId]);
|
|
16
|
+
|
|
14
17
|
return subjects;
|
|
15
18
|
}
|