@mseep/obsidian-agent-client 0.10.6
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/.claude/hooks/gh-setup.sh +49 -0
- package/.claude/settings.json +15 -0
- package/.claude/skills/release-notes/SKILL.md +331 -0
- package/.editorconfig +10 -0
- package/.github/FUNDING.yml +2 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +90 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +59 -0
- package/.github/copilot-instructions.md +45 -0
- package/.github/pull_request_template.md +32 -0
- package/.github/workflows/ci.yaml +25 -0
- package/.github/workflows/docs.yml +58 -0
- package/.github/workflows/relay_to_openclaw.yml +59 -0
- package/.github/workflows/release.yaml +45 -0
- package/.prettierignore +10 -0
- package/.prettierrc +13 -0
- package/.vscode/extensions.json +7 -0
- package/.vscode/settings.json +37 -0
- package/.zed/settings.json +42 -0
- package/AGENTS.md +330 -0
- package/ARCHITECTURE.md +390 -0
- package/CONTRIBUTING.md +216 -0
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.ja.md +121 -0
- package/README.md +125 -0
- package/docs/.vitepress/config.mts +124 -0
- package/docs/.vitepress/theme/custom.css +111 -0
- package/docs/.vitepress/theme/index.ts +4 -0
- package/docs/agent-setup/claude-code.md +84 -0
- package/docs/agent-setup/codex.md +76 -0
- package/docs/agent-setup/custom-agents.md +67 -0
- package/docs/agent-setup/gemini-cli.md +99 -0
- package/docs/agent-setup/index.md +34 -0
- package/docs/announcements/gemini-cli-deprecation.md +73 -0
- package/docs/getting-started/index.md +78 -0
- package/docs/getting-started/quick-start.md +38 -0
- package/docs/help/faq.md +181 -0
- package/docs/help/troubleshooting.md +221 -0
- package/docs/index.md +63 -0
- package/docs/public/apple-touch-icon.png +0 -0
- package/docs/public/demo.mp4 +0 -0
- package/docs/public/favicon-16x16.png +0 -0
- package/docs/public/favicon-32x32.png +0 -0
- package/docs/public/favicon.ico +0 -0
- package/docs/public/images/editing.webp +0 -0
- package/docs/public/images/export.webp +0 -0
- package/docs/public/images/floating-chat-button.webp +0 -0
- package/docs/public/images/floating-chat-instance-menu.webp +0 -0
- package/docs/public/images/floating-chat-view.webp +0 -0
- package/docs/public/images/mode-selection.webp +0 -0
- package/docs/public/images/model-selection.webp +0 -0
- package/docs/public/images/multi-session.webp +0 -0
- package/docs/public/images/remove-image.webp +0 -0
- package/docs/public/images/ribbon-icon.webp +0 -0
- package/docs/public/images/selection-context.gif +0 -0
- package/docs/public/images/sending-images.webp +0 -0
- package/docs/public/images/sending-messages.webp +0 -0
- package/docs/public/images/session-history-button.webp +0 -0
- package/docs/public/images/slash-commands-1.webp +0 -0
- package/docs/public/images/slash-commands-2.webp +0 -0
- package/docs/public/images/switch-agent.webp +0 -0
- package/docs/public/images/switch-default-agent.webp +0 -0
- package/docs/public/images/temporary-disable.gif +0 -0
- package/docs/reference/acp-support.md +110 -0
- package/docs/usage/chat-export.md +80 -0
- package/docs/usage/commands.md +51 -0
- package/docs/usage/context-files.md +57 -0
- package/docs/usage/editing.md +69 -0
- package/docs/usage/floating-chat.md +84 -0
- package/docs/usage/index.md +97 -0
- package/docs/usage/mcp-tools.md +33 -0
- package/docs/usage/mentions.md +70 -0
- package/docs/usage/mode-selection.md +28 -0
- package/docs/usage/model-selection.md +32 -0
- package/docs/usage/multi-session.md +68 -0
- package/docs/usage/sending-images.md +64 -0
- package/docs/usage/session-history.md +91 -0
- package/docs/usage/slash-commands.md +44 -0
- package/esbuild.config.mjs +49 -0
- package/eslint.config.mjs +25 -0
- package/main.js +228 -0
- package/manifest.json +11 -0
- package/package.json +52 -0
- package/src/acp/acp-client.ts +921 -0
- package/src/acp/acp-handler.ts +252 -0
- package/src/acp/permission-handler.ts +282 -0
- package/src/acp/terminal-handler.ts +264 -0
- package/src/acp/type-converter.ts +272 -0
- package/src/hooks/useAgent.ts +250 -0
- package/src/hooks/useAgentMessages.ts +470 -0
- package/src/hooks/useAgentSession.ts +544 -0
- package/src/hooks/useChatActions.ts +400 -0
- package/src/hooks/useHistoryModal.ts +219 -0
- package/src/hooks/useSessionHistory.ts +863 -0
- package/src/hooks/useSettings.ts +19 -0
- package/src/hooks/useSuggestions.ts +342 -0
- package/src/main.ts +9 -0
- package/src/plugin.ts +1126 -0
- package/src/services/chat-exporter.ts +552 -0
- package/src/services/message-sender.ts +755 -0
- package/src/services/message-state.ts +375 -0
- package/src/services/session-helpers.ts +211 -0
- package/src/services/session-state.ts +130 -0
- package/src/services/session-storage.ts +267 -0
- package/src/services/settings-normalizer.ts +255 -0
- package/src/services/settings-service.ts +285 -0
- package/src/services/update-checker.ts +128 -0
- package/src/services/vault-service.ts +558 -0
- package/src/services/view-registry.ts +345 -0
- package/src/types/agent.ts +92 -0
- package/src/types/chat.ts +351 -0
- package/src/types/errors.ts +136 -0
- package/src/types/obsidian-internals.d.ts +14 -0
- package/src/types/session.ts +731 -0
- package/src/ui/ChangeDirectoryModal.ts +137 -0
- package/src/ui/ChatContext.ts +25 -0
- package/src/ui/ChatHeader.tsx +295 -0
- package/src/ui/ChatPanel.tsx +1162 -0
- package/src/ui/ChatView.tsx +348 -0
- package/src/ui/ErrorBanner.tsx +104 -0
- package/src/ui/FloatingButton.tsx +351 -0
- package/src/ui/FloatingChatView.tsx +531 -0
- package/src/ui/InputArea.tsx +1107 -0
- package/src/ui/InputToolbar.tsx +371 -0
- package/src/ui/MessageBubble.tsx +442 -0
- package/src/ui/MessageList.tsx +265 -0
- package/src/ui/PermissionBanner.tsx +61 -0
- package/src/ui/SessionHistoryModal.tsx +821 -0
- package/src/ui/SettingsTab.ts +1337 -0
- package/src/ui/SuggestionPopup.tsx +138 -0
- package/src/ui/TerminalBlock.tsx +107 -0
- package/src/ui/ToolCallBlock.tsx +456 -0
- package/src/ui/shared/AttachmentStrip.tsx +57 -0
- package/src/ui/shared/IconButton.tsx +55 -0
- package/src/ui/shared/MarkdownRenderer.tsx +103 -0
- package/src/ui/view-host.ts +56 -0
- package/src/utils/error-utils.ts +274 -0
- package/src/utils/logger.ts +44 -0
- package/src/utils/mention-parser.ts +129 -0
- package/src/utils/paths.ts +246 -0
- package/src/utils/platform.ts +425 -0
- package/styles.css +2322 -0
- package/tsconfig.json +18 -0
- package/version-bump.mjs +18 -0
- package/versions.json +3 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
const { useRef, useEffect, useCallback } = React;
|
|
3
|
+
import { setIcon, DropdownComponent } from "obsidian";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
flattenConfigSelectOptions,
|
|
7
|
+
type SessionModeState,
|
|
8
|
+
type SessionModelState,
|
|
9
|
+
type SessionUsage,
|
|
10
|
+
type SessionConfigOption,
|
|
11
|
+
type SessionConfigSelectGroup,
|
|
12
|
+
} from "../types/session";
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Obsidian Dropdown Hook
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Hook for managing an Obsidian DropdownComponent lifecycle.
|
|
20
|
+
* Handles creation, option population, value sync, and cleanup.
|
|
21
|
+
*/
|
|
22
|
+
function useObsidianDropdown(
|
|
23
|
+
containerRef: React.RefObject<HTMLDivElement | null>,
|
|
24
|
+
options: Array<{ value: string; label: string }> | undefined,
|
|
25
|
+
currentValue: string | undefined,
|
|
26
|
+
onChangeRef: React.RefObject<((value: string) => void) | undefined>,
|
|
27
|
+
): void {
|
|
28
|
+
const instanceRef = useRef<DropdownComponent | null>(null);
|
|
29
|
+
|
|
30
|
+
// Create/destroy dropdown when options change
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const containerEl = containerRef.current;
|
|
33
|
+
if (!containerEl) return;
|
|
34
|
+
|
|
35
|
+
if (!options || options.length <= 1) {
|
|
36
|
+
if (instanceRef.current) {
|
|
37
|
+
containerEl.empty();
|
|
38
|
+
instanceRef.current = null;
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!instanceRef.current) {
|
|
44
|
+
const dropdown = new DropdownComponent(containerEl);
|
|
45
|
+
instanceRef.current = dropdown;
|
|
46
|
+
|
|
47
|
+
for (const opt of options) {
|
|
48
|
+
dropdown.addOption(opt.value, opt.label);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (currentValue) {
|
|
52
|
+
dropdown.setValue(currentValue);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
dropdown.onChange((value) => {
|
|
56
|
+
onChangeRef.current?.(value);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
if (instanceRef.current) {
|
|
62
|
+
containerEl.empty();
|
|
63
|
+
instanceRef.current = null;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}, [options, containerRef, onChangeRef, currentValue]);
|
|
67
|
+
|
|
68
|
+
// Sync value when it changes externally
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (instanceRef.current && currentValue) {
|
|
71
|
+
instanceRef.current.setValue(currentValue);
|
|
72
|
+
}
|
|
73
|
+
}, [currentValue]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// Utility Functions
|
|
78
|
+
// ============================================================================
|
|
79
|
+
|
|
80
|
+
/** Format token count for display (e.g., 21367 → "21.4K", 200000 → "200K") */
|
|
81
|
+
function formatTokenCount(tokens: number): string {
|
|
82
|
+
if (tokens < 1000) return String(tokens);
|
|
83
|
+
const k = tokens / 1000;
|
|
84
|
+
return k >= 100 ? `${Math.round(k)}K` : `${k.toFixed(1)}K`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Get CSS class for usage percentage color thresholds */
|
|
88
|
+
function getUsageColorClass(percentage: number): string {
|
|
89
|
+
if (percentage >= 90) return "agent-client-usage-danger";
|
|
90
|
+
if (percentage >= 80) return "agent-client-usage-warning";
|
|
91
|
+
if (percentage >= 70) return "agent-client-usage-caution";
|
|
92
|
+
return "agent-client-usage-normal";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// InputToolbar
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
export interface InputToolbarProps {
|
|
100
|
+
isSending: boolean;
|
|
101
|
+
isButtonDisabled: boolean;
|
|
102
|
+
hasContent: boolean;
|
|
103
|
+
onSendOrStop: () => void;
|
|
104
|
+
modes?: SessionModeState;
|
|
105
|
+
onModeChange?: (modeId: string) => void;
|
|
106
|
+
models?: SessionModelState;
|
|
107
|
+
onModelChange?: (modelId: string) => void;
|
|
108
|
+
configOptions?: SessionConfigOption[];
|
|
109
|
+
onConfigOptionChange?: (configId: string, value: string) => void;
|
|
110
|
+
usage?: SessionUsage;
|
|
111
|
+
isSessionReady: boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function InputToolbar({
|
|
115
|
+
isSending,
|
|
116
|
+
isButtonDisabled,
|
|
117
|
+
hasContent,
|
|
118
|
+
onSendOrStop,
|
|
119
|
+
modes,
|
|
120
|
+
onModeChange,
|
|
121
|
+
models,
|
|
122
|
+
onModelChange,
|
|
123
|
+
configOptions,
|
|
124
|
+
onConfigOptionChange,
|
|
125
|
+
usage,
|
|
126
|
+
isSessionReady,
|
|
127
|
+
}: InputToolbarProps) {
|
|
128
|
+
// Refs
|
|
129
|
+
const sendButtonRef = useRef<HTMLButtonElement>(null);
|
|
130
|
+
const modeDropdownRef = useRef<HTMLDivElement>(null);
|
|
131
|
+
const modelDropdownRef = useRef<HTMLDivElement>(null);
|
|
132
|
+
const configOptionsRef = useRef<HTMLDivElement>(null);
|
|
133
|
+
const configDropdownInstances = useRef<Map<string, DropdownComponent>>(
|
|
134
|
+
new Map(),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Stable callback refs
|
|
138
|
+
const onModeChangeRef = useRef(onModeChange);
|
|
139
|
+
onModeChangeRef.current = onModeChange;
|
|
140
|
+
|
|
141
|
+
const onModelChangeRef = useRef(onModelChange);
|
|
142
|
+
onModelChangeRef.current = onModelChange;
|
|
143
|
+
|
|
144
|
+
const onConfigOptionChangeRef = useRef(onConfigOptionChange);
|
|
145
|
+
onConfigOptionChangeRef.current = onConfigOptionChange;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Update send button icon color based on state.
|
|
149
|
+
*/
|
|
150
|
+
const updateIconColor = useCallback(
|
|
151
|
+
(svg: SVGElement) => {
|
|
152
|
+
svg.classList.remove(
|
|
153
|
+
"agent-client-icon-sending",
|
|
154
|
+
"agent-client-icon-active",
|
|
155
|
+
"agent-client-icon-inactive",
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (isSending) {
|
|
159
|
+
svg.classList.add("agent-client-icon-sending");
|
|
160
|
+
} else {
|
|
161
|
+
svg.classList.add(
|
|
162
|
+
hasContent
|
|
163
|
+
? "agent-client-icon-active"
|
|
164
|
+
: "agent-client-icon-inactive",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
[isSending, hasContent],
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Update send button icon based on sending state
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (sendButtonRef.current) {
|
|
174
|
+
const iconName = isSending ? "square" : "send-horizontal";
|
|
175
|
+
setIcon(sendButtonRef.current, iconName);
|
|
176
|
+
const svg = sendButtonRef.current.querySelector("svg");
|
|
177
|
+
if (svg) {
|
|
178
|
+
updateIconColor(svg);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}, [isSending, updateIconColor]);
|
|
182
|
+
|
|
183
|
+
// Update icon color when hasContent changes
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
if (sendButtonRef.current) {
|
|
186
|
+
const svg = sendButtonRef.current.querySelector("svg");
|
|
187
|
+
if (svg) {
|
|
188
|
+
updateIconColor(svg);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}, [updateIconColor]);
|
|
192
|
+
|
|
193
|
+
// Mode dropdown
|
|
194
|
+
const modeOptions = modes?.availableModes?.map((m) => ({
|
|
195
|
+
value: m.id,
|
|
196
|
+
label: m.name,
|
|
197
|
+
}));
|
|
198
|
+
useObsidianDropdown(
|
|
199
|
+
modeDropdownRef,
|
|
200
|
+
modeOptions,
|
|
201
|
+
modes?.currentModeId,
|
|
202
|
+
onModeChangeRef,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Model dropdown
|
|
206
|
+
const modelOptions = models?.availableModels?.map((m) => ({
|
|
207
|
+
value: m.modelId,
|
|
208
|
+
label: m.name,
|
|
209
|
+
}));
|
|
210
|
+
useObsidianDropdown(
|
|
211
|
+
modelDropdownRef,
|
|
212
|
+
modelOptions,
|
|
213
|
+
models?.currentModelId,
|
|
214
|
+
onModelChangeRef,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// Initialize configOptions dropdowns (dynamic, replaces mode/model when present)
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
const containerEl = configOptionsRef.current;
|
|
220
|
+
if (!containerEl) return;
|
|
221
|
+
|
|
222
|
+
// Clean up existing dropdowns
|
|
223
|
+
containerEl.empty();
|
|
224
|
+
configDropdownInstances.current.clear();
|
|
225
|
+
|
|
226
|
+
if (!configOptions || configOptions.length === 0) return;
|
|
227
|
+
|
|
228
|
+
for (const option of configOptions) {
|
|
229
|
+
// Flatten options (handle both flat and grouped)
|
|
230
|
+
const flatOptions = flattenConfigSelectOptions(option.options);
|
|
231
|
+
|
|
232
|
+
// Only show if there are multiple values
|
|
233
|
+
if (flatOptions.length <= 1) continue;
|
|
234
|
+
|
|
235
|
+
// Create wrapper div with appropriate class based on category
|
|
236
|
+
const categoryClass = option.category
|
|
237
|
+
? `agent-client-config-selector-${option.category}`
|
|
238
|
+
: "agent-client-config-selector";
|
|
239
|
+
const wrapperEl = containerEl.createDiv({
|
|
240
|
+
cls: `agent-client-config-selector ${categoryClass}`,
|
|
241
|
+
attr: { title: option.description ?? option.name },
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const dropdownContainer = wrapperEl.createDiv();
|
|
245
|
+
const dropdown = new DropdownComponent(dropdownContainer);
|
|
246
|
+
|
|
247
|
+
// Add options (with group prefix for grouped options)
|
|
248
|
+
if (option.options.length > 0 && "group" in option.options[0]) {
|
|
249
|
+
for (const group of option.options as SessionConfigSelectGroup[]) {
|
|
250
|
+
for (const opt of group.options) {
|
|
251
|
+
dropdown.addOption(
|
|
252
|
+
opt.value,
|
|
253
|
+
`${group.name} / ${opt.name}`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
for (const opt of flatOptions) {
|
|
259
|
+
dropdown.addOption(opt.value, opt.name);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Set current value
|
|
264
|
+
dropdown.setValue(option.currentValue);
|
|
265
|
+
|
|
266
|
+
// Handle change
|
|
267
|
+
const configId = option.id;
|
|
268
|
+
dropdown.onChange((value) => {
|
|
269
|
+
if (onConfigOptionChangeRef.current) {
|
|
270
|
+
onConfigOptionChangeRef.current(configId, value);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Add chevron icon
|
|
275
|
+
const iconEl = wrapperEl.createSpan({
|
|
276
|
+
cls: "agent-client-config-selector-icon",
|
|
277
|
+
});
|
|
278
|
+
setIcon(iconEl, "chevron-down");
|
|
279
|
+
|
|
280
|
+
configDropdownInstances.current.set(option.id, dropdown);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return () => {
|
|
284
|
+
containerEl.empty();
|
|
285
|
+
configDropdownInstances.current.clear();
|
|
286
|
+
};
|
|
287
|
+
}, [configOptions]);
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<div className="agent-client-chat-input-actions">
|
|
291
|
+
{/* Context Usage Indicator (left-aligned via margin-right: auto) */}
|
|
292
|
+
{usage && (
|
|
293
|
+
<span
|
|
294
|
+
className={`agent-client-usage-indicator ${getUsageColorClass(Math.round((usage.used / usage.size) * 100))}`}
|
|
295
|
+
aria-label={
|
|
296
|
+
usage.cost
|
|
297
|
+
? `${formatTokenCount(usage.used)} / ${formatTokenCount(usage.size)} tokens\n$${usage.cost.amount.toFixed(2)}`
|
|
298
|
+
: `${formatTokenCount(usage.used)} / ${formatTokenCount(usage.size)} tokens`
|
|
299
|
+
}
|
|
300
|
+
>
|
|
301
|
+
{Math.round((usage.used / usage.size) * 100)}%
|
|
302
|
+
</span>
|
|
303
|
+
)}
|
|
304
|
+
|
|
305
|
+
{/* Config Options (supersedes legacy mode/model selectors) */}
|
|
306
|
+
{configOptions && configOptions.length > 0 ? (
|
|
307
|
+
<div
|
|
308
|
+
ref={configOptionsRef}
|
|
309
|
+
className="agent-client-config-options-container"
|
|
310
|
+
/>
|
|
311
|
+
) : (
|
|
312
|
+
<>
|
|
313
|
+
{/* Legacy Mode Selector */}
|
|
314
|
+
{modes && modes.availableModes.length > 1 && (
|
|
315
|
+
<div
|
|
316
|
+
className="agent-client-mode-selector"
|
|
317
|
+
title={
|
|
318
|
+
modes.availableModes.find(
|
|
319
|
+
(m) => m.id === modes.currentModeId,
|
|
320
|
+
)?.description ?? "Select mode"
|
|
321
|
+
}
|
|
322
|
+
>
|
|
323
|
+
<div ref={modeDropdownRef} />
|
|
324
|
+
<span
|
|
325
|
+
className="agent-client-mode-selector-icon"
|
|
326
|
+
ref={(el) => {
|
|
327
|
+
if (el) setIcon(el, "chevron-down");
|
|
328
|
+
}}
|
|
329
|
+
/>
|
|
330
|
+
</div>
|
|
331
|
+
)}
|
|
332
|
+
|
|
333
|
+
{/* Legacy Model Selector */}
|
|
334
|
+
{models && models.availableModels.length > 1 && (
|
|
335
|
+
<div
|
|
336
|
+
className="agent-client-model-selector"
|
|
337
|
+
title={
|
|
338
|
+
models.availableModels.find(
|
|
339
|
+
(m) => m.modelId === models.currentModelId,
|
|
340
|
+
)?.description ?? "Select model"
|
|
341
|
+
}
|
|
342
|
+
>
|
|
343
|
+
<div ref={modelDropdownRef} />
|
|
344
|
+
<span
|
|
345
|
+
className="agent-client-model-selector-icon"
|
|
346
|
+
ref={(el) => {
|
|
347
|
+
if (el) setIcon(el, "chevron-down");
|
|
348
|
+
}}
|
|
349
|
+
/>
|
|
350
|
+
</div>
|
|
351
|
+
)}
|
|
352
|
+
</>
|
|
353
|
+
)}
|
|
354
|
+
|
|
355
|
+
{/* Send/Stop Button */}
|
|
356
|
+
<button
|
|
357
|
+
ref={sendButtonRef}
|
|
358
|
+
onClick={onSendOrStop}
|
|
359
|
+
disabled={isButtonDisabled}
|
|
360
|
+
className={`agent-client-chat-send-button ${isSending ? "sending" : ""} ${isButtonDisabled ? "agent-client-disabled" : ""}`}
|
|
361
|
+
title={
|
|
362
|
+
!isSessionReady
|
|
363
|
+
? "Connecting..."
|
|
364
|
+
: isSending
|
|
365
|
+
? "Stop generation"
|
|
366
|
+
: "Send message"
|
|
367
|
+
}
|
|
368
|
+
></button>
|
|
369
|
+
</div>
|
|
370
|
+
);
|
|
371
|
+
}
|