@parallelworks/ui 0.19.0 → 0.20.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/README.md +1 -1
- package/dist/{chunks/memoryStore-CC91S5LP.js → ai/adapter/memoryStore.js} +2 -2
- package/dist/ai/adapter/mock/index.js +2 -80
- package/dist/ai/adapter/mock/mockAdapter.js +81 -0
- package/dist/ai/adapter/openai/createOpenAIStream.js +33 -0
- package/dist/ai/adapter/openai/errors.js +37 -0
- package/dist/ai/adapter/openai/index.js +5 -333
- package/dist/ai/adapter/openai/openaiAdapter.js +54 -0
- package/dist/ai/adapter/openai/responses.js +108 -0
- package/dist/ai/adapter/openai/sse.js +112 -0
- package/dist/{chunks/types-Yy0NrerF.js → ai/adapter/types.js} +1 -1
- package/dist/ai/agent/classifier.js +12 -0
- package/dist/ai/agent/diff.js +223 -0
- package/dist/ai/agent/diffHighlight.js +101 -0
- package/dist/ai/agent/permissions.js +29 -0
- package/dist/ai/agent/toolfmt.js +383 -0
- package/dist/ai/agent/transcript.js +127 -0
- package/dist/ai/components/AllocationSelector.js +56 -0
- package/dist/ai/components/AttachmentManager.js +147 -0
- package/dist/ai/components/AttachmentUpload.js +179 -0
- package/dist/ai/components/BlockedGroupBanner.js +29 -0
- package/dist/ai/components/BranchNavigator.js +57 -0
- package/dist/ai/components/ChatEmptyState.js +171 -0
- package/dist/ai/components/ChatInput.js +265 -0
- package/dist/ai/components/ChatLayout.js +15 -0
- package/dist/ai/components/ChatMessage.js +341 -0
- package/dist/ai/components/ChatMessageList.js +301 -0
- package/dist/ai/components/ChatSidebar.js +345 -0
- package/dist/ai/components/ChatThread.js +198 -0
- package/dist/ai/components/ComposerChrome.js +50 -0
- package/dist/ai/components/ComposerFrame.js +33 -0
- package/dist/ai/components/DragOverlay.js +20 -0
- package/dist/ai/components/ModelSelector.js +343 -0
- package/dist/ai/components/ShareDialog.js +260 -0
- package/dist/ai/components/SlashMenu.js +91 -0
- package/dist/ai/components/agent/ActivityLine.js +151 -0
- package/dist/ai/components/agent/AgentMessageParts.js +721 -0
- package/dist/ai/components/agent/PermissionPicker.js +38 -0
- package/dist/ai/components/attachmentKeys.js +10 -0
- package/dist/ai/core/ChatProvider.js +848 -0
- package/dist/ai/core/KeyboardShortcutsProvider.js +9 -0
- package/dist/ai/core/chatReducer.js +223 -0
- package/dist/ai/core/config.js +46 -0
- package/dist/ai/core/events.js +4 -0
- package/dist/ai/core/exportConversation.js +42 -0
- package/dist/ai/core/greeting.js +38 -0
- package/dist/ai/core/parts.js +84 -0
- package/dist/ai/core/useDragDrop.js +36 -0
- package/dist/ai/core/useKeyboardShortcuts.js +77 -0
- package/dist/ai/core/useSSEStream.js +98 -0
- package/dist/ai/index.js +43 -6644
- package/dist/ai/strings.js +175 -0
- package/dist/ai/ui/Avatar.js +35 -0
- package/dist/ai/ui/Dropdown.js +110 -0
- package/dist/ai/ui/Markdown.js +79 -0
- package/dist/ai/ui/Notice.js +77 -0
- package/dist/ai/ui/codeBreaks.js +36 -0
- package/dist/ai/ui/prose.js +13 -0
- package/dist/ai/ui/sidebar.js +148 -0
- package/dist/ai/utils.js +23 -0
- package/dist/assets/editor.worker-DfmPPyOL.js +60 -0
- package/dist/assets/json.worker-Dqgjpq9F.js +92 -0
- package/dist/assets/yaml.worker-DLx0whrM.js +1441 -0
- package/dist/components/AddMenu.js +46 -0
- package/dist/components/Avatar.js +46 -0
- package/dist/{chunks/BareModal-BtRiQezg.js → components/BareModal.js} +1 -1
- package/dist/components/Breadcrumbs.js +83 -0
- package/dist/components/Button.js +83 -0
- package/dist/components/Callout.js +23 -0
- package/dist/components/Card.js +31 -0
- package/dist/components/CollapsiblePanel.js +77 -0
- package/dist/{chunks/ConfirmModal-aOR4O1QE.js → components/ConfirmModal.js} +4 -9
- package/dist/components/CopyToClipboard.js +81 -0
- package/dist/{chunks/Markdown-CM-FenNE.js → components/CreateModal.js} +9 -116
- package/dist/components/DescriptionList.js +42 -0
- package/dist/components/Dropdown.js +349 -0
- package/dist/components/EmptyState.js +27 -0
- package/dist/components/Filter.js +32 -0
- package/dist/components/FilterPill.js +13 -0
- package/dist/components/IconButton.js +34 -0
- package/dist/{chunks/Indicator-hBSIxOPK.js → components/Indicator.js} +1 -1
- package/dist/components/Input.js +88 -0
- package/dist/components/LetterBadge.js +13 -0
- package/dist/components/Listbox.js +123 -0
- package/dist/{chunks/Loader-BXoXTZGc.js → components/Loader.js} +1 -1
- package/dist/components/Markdown.js +15 -0
- package/dist/components/MultiSelect.js +171 -0
- package/dist/components/Overview.js +39 -0
- package/dist/components/PageHeader.js +194 -0
- package/dist/components/Pagination.js +35 -0
- package/dist/{chunks/Provider-CDYurraE.js → components/Provider.js} +13 -5
- package/dist/{chunks/RequiredMark-DfLdYbYZ.js → components/RequiredMark.js} +1 -1
- package/dist/components/SectionHeader.js +11 -0
- package/dist/components/SettingModeOption.js +30 -0
- package/dist/components/SettingRow.js +35 -0
- package/dist/components/SettingsCard.js +31 -0
- package/dist/components/SettingsGroup.js +17 -0
- package/dist/components/Sort.js +42 -0
- package/dist/components/StatusBadge.js +63 -0
- package/dist/components/SwitchToggle.js +59 -0
- package/dist/{chunks/SwitchToggleSmall-FcsaYSTs.js → components/SwitchToggleSmall.js} +1 -1
- package/dist/{chunks/Table-DU2MW8xz.js → components/Table.js} +3 -3
- package/dist/components/Toggle.js +20 -0
- package/dist/{chunks/Tooltip-K5HJr4_p.js → components/Tooltip.js} +2 -27
- package/dist/{chunks/StatusBadge-C1Blx_cW.js → components/dropdownUtils.js} +2 -63
- package/dist/{chunks/focus-D_iZJTW1.js → components/focus.js} +1 -1
- package/dist/components/ghostButton.js +7 -0
- package/dist/components/inputClasses.js +4 -0
- package/dist/components/keys.js +27 -0
- package/dist/components/relativeTime.js +32 -0
- package/dist/{chunks/useCssIsDark-DHr05dG2.js → components/useCssIsDark.js} +1 -1
- package/dist/{chunks/duration-BOv8Phl8.js → duration.js} +1 -1
- package/dist/editor/Editor.js +13 -0
- package/dist/editor/EditorField.js +53 -0
- package/dist/editor/FormCodePanel.js +173 -0
- package/dist/{chunks/Monaco-DJ8MW-Dq.js → editor/Monaco.js} +5 -26
- package/dist/editor/index.js +6 -1
- package/dist/editor/themes.js +91 -0
- package/dist/editor/workers.d.ts +5 -0
- package/dist/editor/workers.js +35 -0
- package/dist/editor/yaml.d.ts +9 -1
- package/dist/editor/yaml.js +15 -0
- package/dist/editor/yaml.worker.d.ts +1 -0
- package/dist/editor/yamlLanguage.d.ts +6 -0
- package/dist/editor/yamlLanguage.js +312 -0
- package/dist/file-explorer/FileExplorer.js +1405 -0
- package/dist/file-explorer/FileExplorerContext.js +370 -0
- package/dist/{chunks/CodePreviewEditor-YqQBz1YL.js → file-explorer/FilePreview/CodePreviewEditor.js} +3 -2
- package/dist/file-explorer/FilePreview/Inline.js +45 -0
- package/dist/file-explorer/FilePreview/StreamedBadge.js +16 -0
- package/dist/file-explorer/FilePreview/Viewer.js +526 -0
- package/dist/file-explorer/FilePreview/index.js +266 -0
- package/dist/file-explorer/FilePreview/parse.js +63 -0
- package/dist/file-explorer/FilePreview/previewCache.js +98 -0
- package/dist/file-explorer/FilePreview/previewType.js +151 -0
- package/dist/file-explorer/FilePreview/readCappedBytes.js +196 -0
- package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +95 -0
- package/dist/file-explorer/FilePreview/useObjectText.js +109 -0
- package/dist/file-explorer/FilePreview/usePresignedUrl.js +73 -0
- package/dist/file-explorer/FileRow.js +165 -0
- package/dist/file-explorer/FileRowsWindow.js +92 -0
- package/dist/file-explorer/FileTable.js +172 -0
- package/dist/file-explorer/FileTableHeaders.js +49 -0
- package/dist/file-explorer/LoadMoreRow.js +46 -0
- package/dist/file-explorer/TreeView.js +376 -0
- package/dist/file-explorer/helpers.js +537 -0
- package/dist/file-explorer/index.js +7 -5671
- package/dist/file-explorer/lib/batch.js +39 -0
- package/dist/file-explorer/lib/errors.js +9 -0
- package/dist/file-explorer/lib/paging.js +124 -0
- package/dist/file-explorer/lib/utils.js +322 -0
- package/dist/file-explorer/toasts.js +400 -0
- package/dist/file-explorer/treeRows.js +73 -0
- package/dist/file-explorer/useNearEnd.js +27 -0
- package/dist/form/FieldWrapper.js +54 -0
- package/dist/form/Fields/BooleanField.js +51 -0
- package/dist/form/Fields/CheckboxGroupField.js +72 -0
- package/dist/form/Fields/ColorField.js +33 -0
- package/dist/form/Fields/DropdownField.js +49 -0
- package/dist/form/Fields/DurationField.js +130 -0
- package/dist/form/Fields/LabelField.js +19 -0
- package/dist/form/Fields/MultiDropdownField.js +40 -0
- package/dist/form/Fields/NumberField.js +51 -0
- package/dist/form/Fields/ObjectField.js +54 -0
- package/dist/form/Fields/RadioField.js +49 -0
- package/dist/form/Fields/RangeField.js +58 -0
- package/dist/form/Fields/ResourceOptionIcon.js +21 -0
- package/dist/form/Fields/StepField.js +6 -0
- package/dist/form/Fields/StringField.js +37 -0
- package/dist/form/Fields/TextAreaField.js +31 -0
- package/dist/form/Fields/durationValidity.js +11 -0
- package/dist/form/Form.js +643 -0
- package/dist/form/SpecialTypes/DropdownWrapper.js +30 -0
- package/dist/form/SpecialTypes/FormikCustomDropdown.js +122 -0
- package/dist/form/SpecialTypes/FormikCustomInput.js +111 -0
- package/dist/form/SpecialTypes/FormikMultiSelectDropdown.js +31 -0
- package/dist/form/SpecialTypes/FormikNumericInput.js +60 -0
- package/dist/form/Wizard/WizardContainer.js +138 -0
- package/dist/form/Wizard/WizardNavigation.js +92 -0
- package/dist/form/Wizard/WizardStepContent.js +35 -0
- package/dist/form/Wizard/WizardStepIndicator.js +108 -0
- package/dist/form/Wizard/useWizardState.js +118 -0
- package/dist/form/Wizard/utils.js +34 -0
- package/dist/form/fieldContext.js +26 -0
- package/dist/form/fieldRegistry.js +50 -0
- package/dist/form/index.js +17 -2321
- package/dist/{chunks/FieldWrapper-Cszwg-4B.js → form/lib.js} +2 -97
- package/dist/form/useParsedOpts.js +62 -0
- package/dist/form/usePrevious.js +11 -0
- package/dist/form/utils/checkShowIfObject.js +14 -0
- package/dist/form/utils/duration.js +69 -0
- package/dist/form/utils/getParentValue.js +23 -0
- package/dist/form/utils/getPlaceholder.js +9 -0
- package/dist/form/utils/getValueUsingPath.js +23 -0
- package/dist/form/utils/secondaryField.js +22 -0
- package/dist/graph/AnnotationBanner.js +141 -0
- package/dist/graph/Collapse.js +41 -0
- package/dist/graph/DependencyGraph.js +1398 -0
- package/dist/graph/JobSummary.js +104 -0
- package/dist/graph/MatrixGroup.js +107 -0
- package/dist/graph/Reveal.js +57 -0
- package/dist/graph/TreeView.js +711 -0
- package/dist/graph/index.js +1 -2519
- package/dist/graph/types.js +6 -0
- package/dist/icons.d.ts +4 -4
- package/dist/icons.js +5 -5
- package/dist/index.js +46 -599
- package/dist/list/ActionToolbar.js +184 -0
- package/dist/list/HoverCard.js +73 -0
- package/dist/list/ListPage.js +145 -0
- package/dist/list/ListPager.js +59 -0
- package/dist/list/ListTable.js +163 -0
- package/dist/list/ListViewControls.js +372 -0
- package/dist/list/RowContextMenu.js +351 -0
- package/dist/list/UserHoverCard.js +117 -0
- package/dist/list/index.js +11 -2
- package/dist/list/listSearch.js +137 -0
- package/dist/list/listView.js +210 -0
- package/dist/list/textEntry.js +10 -0
- package/dist/{chunks/logviewer-BTKuQWzw.js → logviewer/LogViewer.js} +4 -82
- package/dist/logviewer/index.js +2 -1
- package/dist/logviewer/segments.js +80 -0
- package/dist/safeUrl.d.ts +6 -0
- package/dist/safeUrl.js +22 -0
- package/package.json +7 -9
- package/dist/assets/editor.worker-BmHUIVG4.js +0 -14807
- package/dist/assets/json.worker-D8bJ92nw.js +0 -22207
- package/dist/assets/yaml.worker-C4gyGAko.js +0 -50201
- package/dist/chunks/PageHeader-0kG_iypP.js +0 -692
- package/dist/chunks/SwitchToggle-DShNdcao.js +0 -758
- package/dist/chunks/editor-Dk1rbV75.js +0 -361
- package/dist/chunks/list-CJm875m7.js +0 -1418
- /package/dist/{chunks/MarkdownImpl-CyeTVvSt.js → components/MarkdownImpl.js} +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { CloseIcon, ThinkingIcon } from "../../icons.js";
|
|
2
|
+
import { useChatConfig } from "../core/config.js";
|
|
3
|
+
import { useChat } from "../core/ChatProvider.js";
|
|
4
|
+
import Markdown from "../ui/Markdown.js";
|
|
5
|
+
import BlockedGroupBanner from "./BlockedGroupBanner.js";
|
|
6
|
+
import useDragDrop from "../core/useDragDrop.js";
|
|
7
|
+
import { ComposerControls, ConnectToolsLink } from "./ComposerChrome.js";
|
|
8
|
+
import DragOverlay from "./DragOverlay.js";
|
|
9
|
+
import ChatInput from "./ChatInput.js";
|
|
10
|
+
import { ShareButton } from "./ShareDialog.js";
|
|
11
|
+
import ChatMessageList from "./ChatMessageList.js";
|
|
12
|
+
import cx from "classnames";
|
|
13
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
14
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
//#region src/ai/components/ChatThread.tsx
|
|
16
|
+
function ChatThread({ conversationId }) {
|
|
17
|
+
const chatInputRef = useRef(null);
|
|
18
|
+
const [editingState, setEditingState] = useState(null);
|
|
19
|
+
const [reasoningDrawerOpen, setReasoningDrawerOpen] = useState(false);
|
|
20
|
+
const [selectedReasoning, setSelectedReasoning] = useState(null);
|
|
21
|
+
const handleOpenReasoning = useCallback((reasoning, duration) => {
|
|
22
|
+
if (reasoningDrawerOpen && selectedReasoning?.content === reasoning) setReasoningDrawerOpen(false);
|
|
23
|
+
else {
|
|
24
|
+
setSelectedReasoning({
|
|
25
|
+
content: reasoning,
|
|
26
|
+
duration
|
|
27
|
+
});
|
|
28
|
+
setReasoningDrawerOpen(true);
|
|
29
|
+
}
|
|
30
|
+
}, [reasoningDrawerOpen, selectedReasoning?.content]);
|
|
31
|
+
const { adapter, currentUser, currentConversation, loadConversation, loadProviders, sendMessage, retryMessage, isLoadingConversation, isStreaming, streamingContent, navigateToBranch, queuedMessages, removeQueuedMessage } = useChat();
|
|
32
|
+
const { slots, strings } = useChatConfig();
|
|
33
|
+
const requestedConversationIdRef = useRef(null);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
loadProviders();
|
|
36
|
+
if (requestedConversationIdRef.current !== conversationId) {
|
|
37
|
+
requestedConversationIdRef.current = conversationId;
|
|
38
|
+
loadConversation(conversationId);
|
|
39
|
+
}
|
|
40
|
+
}, [
|
|
41
|
+
conversationId,
|
|
42
|
+
loadConversation,
|
|
43
|
+
loadProviders
|
|
44
|
+
]);
|
|
45
|
+
const handleSendMessage = useCallback((content, attachmentIds, attachmentMeta) => {
|
|
46
|
+
sendMessage(content, attachmentIds, void 0, attachmentMeta);
|
|
47
|
+
}, [sendMessage]);
|
|
48
|
+
const handleEditMessage = useCallback((messageId) => {
|
|
49
|
+
const message = (currentConversation?.messages || []).find((m) => m.id === messageId);
|
|
50
|
+
if (message && message.role === "user") setEditingState({
|
|
51
|
+
messageId: message.id,
|
|
52
|
+
parentId: message.parentId || null
|
|
53
|
+
});
|
|
54
|
+
}, [currentConversation?.messages]);
|
|
55
|
+
const handleSaveEdit = useCallback((_messageId, content) => {
|
|
56
|
+
if (!editingState) return;
|
|
57
|
+
sendMessage(content, void 0, editingState.parentId || void 0);
|
|
58
|
+
setEditingState(null);
|
|
59
|
+
}, [sendMessage, editingState]);
|
|
60
|
+
const handleCancelEdit = useCallback(() => {
|
|
61
|
+
setEditingState(null);
|
|
62
|
+
}, []);
|
|
63
|
+
const handleEditLastMessage = useCallback(() => {
|
|
64
|
+
const conversationMessages = currentConversation?.messages || [];
|
|
65
|
+
for (let i = conversationMessages.length - 1; i >= 0; i--) if (conversationMessages[i].role === "user") {
|
|
66
|
+
handleEditMessage(conversationMessages[i].id);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}, [currentConversation?.messages, handleEditMessage]);
|
|
70
|
+
const handleNavigateBranch = useCallback((messageId) => {
|
|
71
|
+
navigateToBranch?.(messageId);
|
|
72
|
+
}, [navigateToBranch]);
|
|
73
|
+
const handleRegenerateMessage = useCallback((messageId) => {
|
|
74
|
+
const conversationMessages = currentConversation?.messages || [];
|
|
75
|
+
const assistantMessage = conversationMessages.find((m) => m.id === messageId);
|
|
76
|
+
if (!assistantMessage || assistantMessage.role !== "assistant") return;
|
|
77
|
+
const userMessage = conversationMessages.find((m) => m.id === assistantMessage.parentId);
|
|
78
|
+
if (!userMessage || userMessage.role !== "user") return;
|
|
79
|
+
sendMessage(userMessage.content || "", userMessage.attachments?.map((a) => a.id), userMessage.parentId || void 0);
|
|
80
|
+
}, [currentConversation?.messages, sendMessage]);
|
|
81
|
+
const handleRetryMessage = useCallback((messageId) => {
|
|
82
|
+
retryMessage(messageId);
|
|
83
|
+
}, [retryMessage]);
|
|
84
|
+
const handleFilesDropped = useCallback((files) => {
|
|
85
|
+
chatInputRef.current?.addFiles(files);
|
|
86
|
+
}, []);
|
|
87
|
+
const { isDragging, dragHandlers } = useDragDrop({ onFilesDropped: handleFilesDropped });
|
|
88
|
+
const attachmentsAvailable = !!adapter.attachments;
|
|
89
|
+
const streamingMessage = useMemo(() => isStreaming && streamingContent ? {
|
|
90
|
+
id: "streaming",
|
|
91
|
+
conversationId,
|
|
92
|
+
role: "assistant",
|
|
93
|
+
content: streamingContent,
|
|
94
|
+
timestamp: ""
|
|
95
|
+
} : null, [
|
|
96
|
+
isStreaming,
|
|
97
|
+
streamingContent,
|
|
98
|
+
conversationId
|
|
99
|
+
]);
|
|
100
|
+
const messages = currentConversation?.messages || [];
|
|
101
|
+
const showAuthor = useMemo(() => {
|
|
102
|
+
return new Set(messages.filter((m) => m.author).map((m) => m.author.username)).size > 1 || currentConversation?.isOwner === false;
|
|
103
|
+
}, [messages, currentConversation?.isOwner]);
|
|
104
|
+
if (isLoadingConversation && !currentConversation) return /* @__PURE__ */ jsxs("div", {
|
|
105
|
+
className: "flex flex-col h-full bg-(--theme-panel-bg)",
|
|
106
|
+
children: [/* @__PURE__ */ jsx("div", { className: "flex-1" }), /* @__PURE__ */ jsx(ChatInput, {
|
|
107
|
+
ref: chatInputRef,
|
|
108
|
+
onSend: handleSendMessage,
|
|
109
|
+
disabled: true,
|
|
110
|
+
conversationId,
|
|
111
|
+
settingsLeft: /* @__PURE__ */ jsx(ConnectToolsLink, {}),
|
|
112
|
+
settingsRight: /* @__PURE__ */ jsxs(Fragment$1, { children: [slots.composerUsage, /* @__PURE__ */ jsx(ComposerControls, {})] })
|
|
113
|
+
})]
|
|
114
|
+
});
|
|
115
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
116
|
+
className: "flex h-full relative bg-(--theme-panel-bg)",
|
|
117
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
118
|
+
className: "flex-1 flex flex-col min-w-0",
|
|
119
|
+
...attachmentsAvailable ? dragHandlers : {},
|
|
120
|
+
children: [
|
|
121
|
+
isDragging && attachmentsAvailable && /* @__PURE__ */ jsx(DragOverlay, { className: "rounded-lg" }),
|
|
122
|
+
/* @__PURE__ */ jsx(BlockedGroupBanner, {}),
|
|
123
|
+
/* @__PURE__ */ jsx(ChatMessageList, {
|
|
124
|
+
messages,
|
|
125
|
+
allMessages: messages,
|
|
126
|
+
streamingMessage,
|
|
127
|
+
queuedMessages,
|
|
128
|
+
onRemoveQueued: removeQueuedMessage,
|
|
129
|
+
editingMessageId: editingState?.messageId,
|
|
130
|
+
showAuthor,
|
|
131
|
+
currentUsername: currentUser.username,
|
|
132
|
+
onNavigateBranch: handleNavigateBranch,
|
|
133
|
+
onEditMessage: currentConversation?.canCollaborate !== false ? handleEditMessage : void 0,
|
|
134
|
+
onRegenerateMessage: currentConversation?.canCollaborate !== false ? handleRegenerateMessage : void 0,
|
|
135
|
+
onRetryMessage: currentConversation?.canCollaborate !== false ? handleRetryMessage : void 0,
|
|
136
|
+
onSaveEdit: handleSaveEdit,
|
|
137
|
+
onCancelEdit: handleCancelEdit,
|
|
138
|
+
onOpenReasoning: handleOpenReasoning,
|
|
139
|
+
inputElement: /* @__PURE__ */ jsx(ChatInput, {
|
|
140
|
+
ref: chatInputRef,
|
|
141
|
+
onSend: handleSendMessage,
|
|
142
|
+
disabled: isLoadingConversation || currentConversation?.canCollaborate === false,
|
|
143
|
+
placeholder: currentConversation?.canCollaborate === false ? strings.thread.viewOnly : void 0,
|
|
144
|
+
conversationId,
|
|
145
|
+
onEditLastMessage: handleEditLastMessage,
|
|
146
|
+
settingsLeft: /* @__PURE__ */ jsxs(Fragment$1, { children: [currentConversation?.isOwner !== false && /* @__PURE__ */ jsx(ShareButton, { conversationId }), /* @__PURE__ */ jsx(ConnectToolsLink, {})] }),
|
|
147
|
+
settingsRight: /* @__PURE__ */ jsxs(Fragment$1, { children: [slots.composerUsage, /* @__PURE__ */ jsx(ComposerControls, {})] })
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
]
|
|
151
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
152
|
+
className: cx("flex-shrink-0 border-l theme-border flex flex-col h-full transition-[width] duration-300 ease-in-out overflow-hidden bg-(--theme-panel-bg)", reasoningDrawerOpen ? "w-[400px]" : "w-0 border-l-0"),
|
|
153
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
154
|
+
className: "w-[400px] h-full flex flex-col",
|
|
155
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
156
|
+
className: "flex items-center justify-between p-3 border-b theme-border",
|
|
157
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
158
|
+
className: "flex items-center gap-2",
|
|
159
|
+
children: [
|
|
160
|
+
/* @__PURE__ */ jsx(ThinkingIcon, { className: "w-5 h-5 theme-muted-text" }),
|
|
161
|
+
/* @__PURE__ */ jsx("span", {
|
|
162
|
+
className: "font-medium",
|
|
163
|
+
children: strings.thinking.activity
|
|
164
|
+
}),
|
|
165
|
+
selectedReasoning?.duration && /* @__PURE__ */ jsxs("span", {
|
|
166
|
+
className: "text-sm theme-muted-text",
|
|
167
|
+
children: [
|
|
168
|
+
"· ",
|
|
169
|
+
Math.round(selectedReasoning.duration / 1e3),
|
|
170
|
+
"s"
|
|
171
|
+
]
|
|
172
|
+
})
|
|
173
|
+
]
|
|
174
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
175
|
+
type: "button",
|
|
176
|
+
onClick: () => setReasoningDrawerOpen(false),
|
|
177
|
+
className: "p-1 rounded hover:theme-hover transition-colors",
|
|
178
|
+
children: /* @__PURE__ */ jsx(CloseIcon, { className: "w-5 h-5 theme-muted-text" })
|
|
179
|
+
})]
|
|
180
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
181
|
+
className: "flex-1 overflow-y-auto p-4",
|
|
182
|
+
children: selectedReasoning?.content && /* @__PURE__ */ jsxs("div", {
|
|
183
|
+
className: "space-y-4",
|
|
184
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
185
|
+
className: "flex items-center gap-2 text-sm font-medium theme-text",
|
|
186
|
+
children: [/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-blue-500" }), /* @__PURE__ */ jsx("span", { children: strings.thinking.label })]
|
|
187
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
188
|
+
className: "text-sm theme-muted-text leading-relaxed pl-4 border-l-2 theme-border",
|
|
189
|
+
children: /* @__PURE__ */ jsx(Markdown, { children: selectedReasoning.content })
|
|
190
|
+
})]
|
|
191
|
+
})
|
|
192
|
+
})]
|
|
193
|
+
})
|
|
194
|
+
})]
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
export { ChatThread as default };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ConnectIcon } from "../../icons.js";
|
|
2
|
+
import { useChatConfig } from "../core/config.js";
|
|
3
|
+
import AllocationSelector from "./AllocationSelector.js";
|
|
4
|
+
import ModelSelector from "./ModelSelector.js";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
//#region src/ai/components/ComposerChrome.tsx
|
|
7
|
+
/** Where the turn runs, sitting above the composer the way the terminal shows
|
|
8
|
+
* it: the machine, the workspace, and whatever else names the place. */
|
|
9
|
+
function ComposerContext({ children }) {
|
|
10
|
+
return /* @__PURE__ */ jsx("div", {
|
|
11
|
+
className: "mb-1.5 flex flex-wrap items-center gap-1.5",
|
|
12
|
+
children
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/** What the turn runs as, sitting under the composer: the surface's own
|
|
16
|
+
* controls on the left, the model on the right, both reading as plain text. */
|
|
17
|
+
function ComposerSettings({ left, children }) {
|
|
18
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
19
|
+
className: "mt-1.5 flex flex-wrap items-center justify-between gap-x-3 gap-y-1",
|
|
20
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
21
|
+
className: "flex items-center gap-2",
|
|
22
|
+
children: left
|
|
23
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
24
|
+
className: "flex items-center gap-2",
|
|
25
|
+
children
|
|
26
|
+
})]
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** The two selectors every surface carries, bare so they read as text beside
|
|
30
|
+
* whatever the surface puts next to them. */
|
|
31
|
+
function ComposerControls({ targetSession }) {
|
|
32
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ModelSelector, {
|
|
33
|
+
variant: "bare",
|
|
34
|
+
targetSession
|
|
35
|
+
}), /* @__PURE__ */ jsx(AllocationSelector, { variant: "bare" })] });
|
|
36
|
+
}
|
|
37
|
+
function ConnectToolsLink() {
|
|
38
|
+
const { extraLinks, LinkComponent, strings } = useChatConfig();
|
|
39
|
+
if (!extraLinks.connectTools) return null;
|
|
40
|
+
return /* @__PURE__ */ jsxs(LinkComponent, {
|
|
41
|
+
target: {
|
|
42
|
+
kind: "external",
|
|
43
|
+
href: extraLinks.connectTools
|
|
44
|
+
},
|
|
45
|
+
className: "flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] theme-muted-text transition-colors hover:theme-muted-panel hover:theme-text",
|
|
46
|
+
children: [/* @__PURE__ */ jsx(ConnectIcon, { className: "h-3 w-3" }), strings.sidebar.connectTools]
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { ComposerContext, ComposerControls, ComposerSettings, ConnectToolsLink };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ComposerContext, ComposerControls, ComposerSettings } from "./ComposerChrome.js";
|
|
2
|
+
import cx from "classnames";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
//#region src/ai/components/ComposerFrame.tsx
|
|
5
|
+
/** The column a composer sits in: notices above it, what the agent is doing
|
|
6
|
+
* right now, a row of chips saying where the message goes, the box itself,
|
|
7
|
+
* and a row of settings under it with the model and allocation on the right. */
|
|
8
|
+
function ComposerFrame({ notices, activity, above, belowLeft, belowRight, hint, className, children }) {
|
|
9
|
+
return /* @__PURE__ */ jsx("div", {
|
|
10
|
+
className: cx("shrink-0 px-6 pb-6 pt-2", className),
|
|
11
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
12
|
+
className: "mx-auto w-full max-w-[50rem] space-y-2",
|
|
13
|
+
children: [notices, /* @__PURE__ */ jsxs("div", { children: [
|
|
14
|
+
activity && /* @__PURE__ */ jsx("div", {
|
|
15
|
+
className: "mb-2",
|
|
16
|
+
children: activity
|
|
17
|
+
}),
|
|
18
|
+
above && /* @__PURE__ */ jsx(ComposerContext, { children: above }),
|
|
19
|
+
children,
|
|
20
|
+
/* @__PURE__ */ jsx(ComposerSettings, {
|
|
21
|
+
left: belowLeft,
|
|
22
|
+
children: belowRight ?? /* @__PURE__ */ jsx(ComposerControls, {})
|
|
23
|
+
}),
|
|
24
|
+
hint && /* @__PURE__ */ jsx("p", {
|
|
25
|
+
className: "mt-1 text-center text-[11px] theme-muted-text",
|
|
26
|
+
children: hint
|
|
27
|
+
})
|
|
28
|
+
] })]
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { ComposerFrame as default };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import cx from "classnames";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region src/ai/components/DragOverlay.tsx
|
|
4
|
+
function DragOverlay({ className }) {
|
|
5
|
+
return /* @__PURE__ */ jsx("div", {
|
|
6
|
+
className: cx("absolute inset-0 z-50 flex items-center justify-center bg-blue-500/10 border-2 border-dashed border-blue-500 backdrop-blur-sm pointer-events-none", className),
|
|
7
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
8
|
+
className: "text-center",
|
|
9
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
10
|
+
className: "text-lg font-medium text-blue-600",
|
|
11
|
+
children: "Drop files here"
|
|
12
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
13
|
+
className: "text-sm text-blue-500/80",
|
|
14
|
+
children: "Release to upload"
|
|
15
|
+
})]
|
|
16
|
+
})
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { DragOverlay as default };
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { CheckIcon, ChevronDownIcon, CloudIcon, CogIcon, RetryIcon, RobotIcon } from "../../icons.js";
|
|
2
|
+
import { useChatConfig } from "../core/config.js";
|
|
3
|
+
import { useChat } from "../core/ChatProvider.js";
|
|
4
|
+
import cx from "classnames";
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { createPortal } from "react-dom";
|
|
8
|
+
//#region src/ai/components/ModelSelector.tsx
|
|
9
|
+
/** The search field plus the list's max-h-80, so the flip decision matches
|
|
10
|
+
* what renders. */
|
|
11
|
+
var MAX_PANEL_HEIGHT_PX = 380;
|
|
12
|
+
function CspIcon({ cspKind, className }) {
|
|
13
|
+
const { slots } = useChatConfig();
|
|
14
|
+
const custom = slots.providerIcon?.(cspKind, className);
|
|
15
|
+
if (custom) return /* @__PURE__ */ jsx(Fragment$1, { children: custom });
|
|
16
|
+
return /* @__PURE__ */ jsx(CloudIcon, { className: cx("text-purple-500", className) });
|
|
17
|
+
}
|
|
18
|
+
function getProviderKeyFromModelId(modelId) {
|
|
19
|
+
const idx = modelId.indexOf("/");
|
|
20
|
+
return idx > 0 ? modelId.substring(0, idx) : modelId;
|
|
21
|
+
}
|
|
22
|
+
function getModelNameFromId(modelId) {
|
|
23
|
+
const idx = modelId.indexOf("/");
|
|
24
|
+
return idx > 0 ? modelId.substring(idx + 1) : modelId;
|
|
25
|
+
}
|
|
26
|
+
function getModelLabel(model) {
|
|
27
|
+
const idName = getModelNameFromId(model.id);
|
|
28
|
+
const name = model.name?.trim();
|
|
29
|
+
if (!name || name === `${model.provider} (${idName})`) return idName;
|
|
30
|
+
return name;
|
|
31
|
+
}
|
|
32
|
+
function groupModelsByProvider(models) {
|
|
33
|
+
const groups = /* @__PURE__ */ new Map();
|
|
34
|
+
for (const model of models) {
|
|
35
|
+
const key = model.id.includes("/") ? getProviderKeyFromModelId(model.id) : model.provider ?? model.owned_by;
|
|
36
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
37
|
+
groups.get(key).push(model);
|
|
38
|
+
}
|
|
39
|
+
return groups;
|
|
40
|
+
}
|
|
41
|
+
function ModelSelector({ targetSession, variant = "default" }) {
|
|
42
|
+
const bare = variant === "bare";
|
|
43
|
+
const { providers, models, unreachableSessions, selectedProvider, setSelectedProvider, isLoadingModels, hasLoadedModels, modelsError, refreshModels, currentUser } = useChat();
|
|
44
|
+
const { extraLinks, LinkComponent } = useChatConfig();
|
|
45
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
46
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
47
|
+
const [dropdownPosition, setDropdownPosition] = useState({
|
|
48
|
+
top: 0,
|
|
49
|
+
bottom: 0,
|
|
50
|
+
left: 0,
|
|
51
|
+
width: 0,
|
|
52
|
+
flipped: false
|
|
53
|
+
});
|
|
54
|
+
const containerRef = useRef(null);
|
|
55
|
+
const dropdownRef = useRef(null);
|
|
56
|
+
const buttonRef = useRef(null);
|
|
57
|
+
const inputRef = useRef(null);
|
|
58
|
+
const [mounted, setMounted] = useState(false);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
setMounted(true);
|
|
61
|
+
}, []);
|
|
62
|
+
const updateDropdownPosition = useCallback(() => {
|
|
63
|
+
if (!buttonRef.current) return;
|
|
64
|
+
const rect = buttonRef.current.getBoundingClientRect();
|
|
65
|
+
const below = window.innerHeight - rect.bottom;
|
|
66
|
+
setDropdownPosition({
|
|
67
|
+
top: rect.bottom + 4,
|
|
68
|
+
bottom: window.innerHeight - rect.top + 4,
|
|
69
|
+
left: rect.left,
|
|
70
|
+
width: Math.max(rect.width, 320),
|
|
71
|
+
flipped: below < MAX_PANEL_HEIGHT_PX && rect.top > below
|
|
72
|
+
});
|
|
73
|
+
}, []);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!isOpen) return;
|
|
76
|
+
function handleClickOutside(event) {
|
|
77
|
+
const target = event.target;
|
|
78
|
+
const clickedInsideButton = containerRef.current?.contains(target);
|
|
79
|
+
const clickedInsideDropdown = dropdownRef.current?.contains(target);
|
|
80
|
+
if (!clickedInsideButton && !clickedInsideDropdown) setIsOpen(false);
|
|
81
|
+
}
|
|
82
|
+
const timeoutId = setTimeout(() => {
|
|
83
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
84
|
+
}, 0);
|
|
85
|
+
return () => {
|
|
86
|
+
clearTimeout(timeoutId);
|
|
87
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
88
|
+
};
|
|
89
|
+
}, [isOpen]);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!isOpen) return;
|
|
92
|
+
updateDropdownPosition();
|
|
93
|
+
window.addEventListener("scroll", updateDropdownPosition, true);
|
|
94
|
+
window.addEventListener("resize", updateDropdownPosition);
|
|
95
|
+
return () => {
|
|
96
|
+
window.removeEventListener("scroll", updateDropdownPosition, true);
|
|
97
|
+
window.removeEventListener("resize", updateDropdownPosition);
|
|
98
|
+
};
|
|
99
|
+
}, [isOpen, updateDropdownPosition]);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (isOpen && inputRef.current) inputRef.current.focus();
|
|
102
|
+
}, [isOpen]);
|
|
103
|
+
const filteredModels = useMemo(() => {
|
|
104
|
+
if (!searchQuery.trim()) return models;
|
|
105
|
+
const query = searchQuery.toLowerCase();
|
|
106
|
+
return models.filter((model) => getModelNameFromId(model.id).toLowerCase().includes(query) || (model.provider || "").toLowerCase().includes(query) || (model.name || "").toLowerCase().includes(query));
|
|
107
|
+
}, [models, searchQuery]);
|
|
108
|
+
const groupedModels = useMemo(() => groupModelsByProvider(filteredModels), [filteredModels]);
|
|
109
|
+
const selectedModelInfo = models.find((m) => m.id === selectedProvider);
|
|
110
|
+
const selectedModelName = selectedModelInfo ? getModelLabel(selectedModelInfo) : null;
|
|
111
|
+
const selectedProviderName = selectedModelInfo?.provider || null;
|
|
112
|
+
const handleSelect = (modelId) => {
|
|
113
|
+
setSelectedProvider(modelId);
|
|
114
|
+
setIsOpen(false);
|
|
115
|
+
setSearchQuery("");
|
|
116
|
+
};
|
|
117
|
+
const targetSessionDisplayName = targetSession?.split(":").pop();
|
|
118
|
+
const targetSessionFound = targetSession && models.length > 0 ? models.some((m) => m.id.startsWith(`session:${targetSession}/`)) : false;
|
|
119
|
+
const targetSessionStatus = targetSession ? isLoadingModels ? "connecting" : hasLoadedModels && !targetSessionFound ? "offline" : null : null;
|
|
120
|
+
const boxClass = bare ? "flex items-center gap-1 rounded-md px-1.5 py-0.5 max-w-[280px]" : "flex items-center gap-2 px-3 h-8 rounded-lg min-w-[180px] max-w-[280px]";
|
|
121
|
+
const textClass = bare ? "text-[11px]" : "text-sm";
|
|
122
|
+
if (isLoadingModels && models.length === 0 && !targetSession) return /* @__PURE__ */ jsx("div", {
|
|
123
|
+
ref: containerRef,
|
|
124
|
+
className: "relative",
|
|
125
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
126
|
+
className: boxClass,
|
|
127
|
+
children: [!bare && /* @__PURE__ */ jsx(RobotIcon, { className: "h-4 w-4 text-purple-500 flex-shrink-0 animate-pulse" }), /* @__PURE__ */ jsx("span", {
|
|
128
|
+
className: cx(textClass, "theme-muted-text"),
|
|
129
|
+
children: "Loading models..."
|
|
130
|
+
})]
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
if (modelsError && models.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
134
|
+
ref: containerRef,
|
|
135
|
+
className: "relative",
|
|
136
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
137
|
+
className: boxClass,
|
|
138
|
+
children: [
|
|
139
|
+
!bare && /* @__PURE__ */ jsx(RobotIcon, { className: "h-4 w-4 text-red-500 flex-shrink-0" }),
|
|
140
|
+
/* @__PURE__ */ jsx("span", {
|
|
141
|
+
className: cx(textClass, "text-red-600"),
|
|
142
|
+
children: "Failed to load models"
|
|
143
|
+
}),
|
|
144
|
+
/* @__PURE__ */ jsxs("button", {
|
|
145
|
+
type: "button",
|
|
146
|
+
onClick: () => refreshModels(),
|
|
147
|
+
className: cx("flex items-center gap-1 text-blue-600 hover:underline ml-1", bare ? "text-[11px]" : "text-xs"),
|
|
148
|
+
children: [/* @__PURE__ */ jsx(RetryIcon, { className: "h-2.5 w-2.5" }), "Retry"]
|
|
149
|
+
})
|
|
150
|
+
]
|
|
151
|
+
})
|
|
152
|
+
});
|
|
153
|
+
if (hasLoadedModels && models.length === 0 && !targetSessionStatus && unreachableSessions.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
154
|
+
ref: containerRef,
|
|
155
|
+
className: "relative",
|
|
156
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
157
|
+
className: boxClass,
|
|
158
|
+
children: [!bare && /* @__PURE__ */ jsx(RobotIcon, { className: "h-4 w-4 theme-muted-text flex-shrink-0" }), /* @__PURE__ */ jsx("span", {
|
|
159
|
+
className: cx(textClass, "theme-muted-text"),
|
|
160
|
+
children: "No models available"
|
|
161
|
+
})]
|
|
162
|
+
})
|
|
163
|
+
});
|
|
164
|
+
const triggerText = selectedModelName ? null : targetSessionStatus === "connecting" ? "Connecting..." : models.length === 0 && isLoadingModels ? "Loading models..." : "Select a model";
|
|
165
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
166
|
+
ref: containerRef,
|
|
167
|
+
className: "relative",
|
|
168
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
169
|
+
ref: buttonRef,
|
|
170
|
+
type: "button",
|
|
171
|
+
onClick: () => setIsOpen(!isOpen),
|
|
172
|
+
disabled: models.length === 0 && !targetSessionStatus && unreachableSessions.length === 0,
|
|
173
|
+
className: cx(boxClass, "transition-all cursor-pointer bg-transparent", "focus:outline-none", "disabled:opacity-50 disabled:cursor-not-allowed", bare ? "hover:theme-muted-panel" : "border theme-border hover:theme-hover"),
|
|
174
|
+
children: [
|
|
175
|
+
!bare && /* @__PURE__ */ jsx(RobotIcon, { className: cx("h-4 w-4 flex-shrink-0", targetSessionStatus === "connecting" ? "text-amber-500 animate-pulse" : "text-purple-500") }),
|
|
176
|
+
/* @__PURE__ */ jsx("div", {
|
|
177
|
+
className: "flex-1 min-w-0 text-left",
|
|
178
|
+
children: selectedModelName ? /* @__PURE__ */ jsxs("div", {
|
|
179
|
+
className: "flex items-baseline gap-1.5 min-w-0",
|
|
180
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
181
|
+
title: selectedModelName,
|
|
182
|
+
className: cx("truncate", bare ? "text-[11px] theme-muted-text" : "text-sm font-medium text-(--theme-panel)"),
|
|
183
|
+
children: selectedModelName
|
|
184
|
+
}), selectedProviderName && /* @__PURE__ */ jsx("span", {
|
|
185
|
+
title: selectedProviderName,
|
|
186
|
+
className: cx("theme-muted-text truncate flex-shrink-0 max-w-[45%]", bare ? "text-[10px]" : "text-xs"),
|
|
187
|
+
children: selectedProviderName
|
|
188
|
+
})]
|
|
189
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
190
|
+
className: cx(bare ? "text-[11px]" : "text-sm", targetSessionStatus === "connecting" ? "text-amber-600" : "theme-muted-text"),
|
|
191
|
+
children: triggerText
|
|
192
|
+
})
|
|
193
|
+
}),
|
|
194
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, { className: cx("h-3 w-3 theme-muted-text transition-transform flex-shrink-0", !bare && "ml-auto", isOpen && "rotate-180") })
|
|
195
|
+
]
|
|
196
|
+
}), isOpen && mounted && createPortal(/* @__PURE__ */ jsxs("div", {
|
|
197
|
+
ref: dropdownRef,
|
|
198
|
+
role: "none",
|
|
199
|
+
onClick: (e) => e.stopPropagation(),
|
|
200
|
+
onKeyDown: (e) => {
|
|
201
|
+
if (e.key === "Escape") {
|
|
202
|
+
e.preventDefault();
|
|
203
|
+
setIsOpen(false);
|
|
204
|
+
buttonRef.current?.focus();
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
style: {
|
|
208
|
+
position: "fixed",
|
|
209
|
+
...dropdownPosition.flipped ? { bottom: dropdownPosition.bottom } : { top: dropdownPosition.top },
|
|
210
|
+
left: dropdownPosition.left,
|
|
211
|
+
width: dropdownPosition.width,
|
|
212
|
+
zIndex: 9999
|
|
213
|
+
},
|
|
214
|
+
className: cx("theme-panel rounded-lg shadow-lg", "border theme-border", "overflow-hidden"),
|
|
215
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
216
|
+
className: "p-2 border-b theme-border",
|
|
217
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
218
|
+
ref: inputRef,
|
|
219
|
+
type: "text",
|
|
220
|
+
value: searchQuery,
|
|
221
|
+
onChange: (e) => setSearchQuery(e.target.value),
|
|
222
|
+
placeholder: "Search models...",
|
|
223
|
+
className: cx("w-full px-3 py-2 text-sm rounded-md", "theme-muted-panel", "border theme-border", "focus:outline-none focus:ring-2 focus:ring-blue-500", "placeholder:theme-muted-text")
|
|
224
|
+
})
|
|
225
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
226
|
+
className: "max-h-80 overflow-y-auto",
|
|
227
|
+
children: [
|
|
228
|
+
targetSessionStatus && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
|
|
229
|
+
className: "px-3 py-1.5 bg-amber-500/5 sticky top-0",
|
|
230
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
231
|
+
className: "flex items-center gap-2",
|
|
232
|
+
children: [
|
|
233
|
+
/* @__PURE__ */ jsx(CloudIcon, { className: "h-3 w-3 text-amber-500" }),
|
|
234
|
+
/* @__PURE__ */ jsx("span", {
|
|
235
|
+
className: "text-xs font-semibold text-amber-600 uppercase tracking-wide flex-1",
|
|
236
|
+
children: targetSessionDisplayName
|
|
237
|
+
}),
|
|
238
|
+
/* @__PURE__ */ jsx("span", {
|
|
239
|
+
className: cx("text-[10px] px-1.5 py-0.5 rounded-full", targetSessionStatus === "connecting" ? "text-amber-600 bg-amber-500/10 animate-pulse" : "text-amber-500 bg-amber-500/10"),
|
|
240
|
+
children: targetSessionStatus === "connecting" ? "Connecting..." : "Not responding"
|
|
241
|
+
})
|
|
242
|
+
]
|
|
243
|
+
})
|
|
244
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
245
|
+
className: "px-4 py-2 flex items-center gap-2",
|
|
246
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
247
|
+
className: "text-sm theme-muted-text italic",
|
|
248
|
+
children: targetSessionStatus === "connecting" ? "Loading session models..." : "Service not responding — ensure it is running on the remote host"
|
|
249
|
+
})
|
|
250
|
+
})] }),
|
|
251
|
+
unreachableSessions.filter((s) => !targetSession || `${s.owner}:${s.name}` !== targetSession).map((s) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
|
|
252
|
+
className: "px-3 py-1.5 bg-amber-500/5 sticky top-0",
|
|
253
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
254
|
+
className: "flex items-center gap-2",
|
|
255
|
+
children: [
|
|
256
|
+
/* @__PURE__ */ jsx(CloudIcon, { className: "h-3 w-3 text-amber-500" }),
|
|
257
|
+
/* @__PURE__ */ jsx("span", {
|
|
258
|
+
className: "text-xs font-semibold text-amber-600 uppercase tracking-wide flex-1",
|
|
259
|
+
children: s.name
|
|
260
|
+
}),
|
|
261
|
+
/* @__PURE__ */ jsx("span", {
|
|
262
|
+
className: "text-[10px] text-amber-500 bg-amber-500/10 px-1.5 py-0.5 rounded-full",
|
|
263
|
+
children: "Not responding"
|
|
264
|
+
})
|
|
265
|
+
]
|
|
266
|
+
})
|
|
267
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
268
|
+
className: "px-4 py-2 flex items-center gap-2",
|
|
269
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
270
|
+
className: "text-sm theme-muted-text italic",
|
|
271
|
+
children: "Service not responding — ensure it is running on the remote host"
|
|
272
|
+
})
|
|
273
|
+
})] }, `${s.owner}:${s.name}`)),
|
|
274
|
+
groupedModels.size === 0 && !targetSessionStatus && unreachableSessions.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
275
|
+
className: "p-4 text-center theme-muted-text text-sm",
|
|
276
|
+
children: "No models found"
|
|
277
|
+
}) : Array.from(groupedModels.entries()).map(([providerKey, providerModels]) => {
|
|
278
|
+
const providerInfo = providers.find((p) => `${p.user}:${p.name}` === providerKey);
|
|
279
|
+
const providerName = providerModels[0]?.provider || providerInfo?.name || "Unknown Provider";
|
|
280
|
+
const platform = providerInfo?.platformLabel;
|
|
281
|
+
const platformSuffix = providerInfo && platform && platform.toLowerCase() !== providerName.toLowerCase() ? platform : null;
|
|
282
|
+
const isOwner = providerInfo?.user === currentUser.username;
|
|
283
|
+
const settingsHref = providerInfo && extraLinks.providerSettings ? extraLinks.providerSettings(providerInfo.user, providerInfo.name) : null;
|
|
284
|
+
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
|
|
285
|
+
className: "px-3 py-1.5 theme-muted-panel sticky top-0",
|
|
286
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
287
|
+
className: "flex items-center gap-2",
|
|
288
|
+
children: [
|
|
289
|
+
/* @__PURE__ */ jsx(CspIcon, {
|
|
290
|
+
cspKind: providerInfo?.cspKind ?? "other",
|
|
291
|
+
className: "h-3 w-3"
|
|
292
|
+
}),
|
|
293
|
+
/* @__PURE__ */ jsx("span", {
|
|
294
|
+
className: "text-xs font-semibold theme-muted-text uppercase tracking-wide",
|
|
295
|
+
children: providerName
|
|
296
|
+
}),
|
|
297
|
+
/* @__PURE__ */ jsx("span", {
|
|
298
|
+
className: "min-w-0 flex-1 truncate text-xs theme-muted-text",
|
|
299
|
+
children: platformSuffix ? `· ${platformSuffix}` : ""
|
|
300
|
+
}),
|
|
301
|
+
/* @__PURE__ */ jsxs("span", {
|
|
302
|
+
className: "text-xs theme-muted-text",
|
|
303
|
+
children: [
|
|
304
|
+
"(",
|
|
305
|
+
providerModels.length,
|
|
306
|
+
")"
|
|
307
|
+
]
|
|
308
|
+
}),
|
|
309
|
+
isOwner && settingsHref && /* @__PURE__ */ jsx(LinkComponent, {
|
|
310
|
+
target: {
|
|
311
|
+
kind: "external",
|
|
312
|
+
href: settingsHref
|
|
313
|
+
},
|
|
314
|
+
onClick: (e) => e.stopPropagation(),
|
|
315
|
+
className: "p-0.5 rounded hover:theme-hover theme-muted-text hover:text-blue-600 transition-colors",
|
|
316
|
+
title: "Manage provider",
|
|
317
|
+
children: /* @__PURE__ */ jsx(CogIcon, { className: "h-3 w-3" })
|
|
318
|
+
})
|
|
319
|
+
]
|
|
320
|
+
})
|
|
321
|
+
}), providerModels.map((model) => {
|
|
322
|
+
const modelName = getModelLabel(model);
|
|
323
|
+
const isSelected = model.id === selectedProvider;
|
|
324
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
325
|
+
type: "button",
|
|
326
|
+
onClick: () => handleSelect(model.id),
|
|
327
|
+
className: cx("w-full flex items-center gap-3 px-4 py-2 text-left cursor-pointer", "hover:bg-blue-500/5 transition-colors", isSelected && "bg-blue-500/10"),
|
|
328
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
329
|
+
title: modelName,
|
|
330
|
+
className: cx("flex-1 min-w-0 text-sm truncate", isSelected ? "text-blue-600 font-medium" : ""),
|
|
331
|
+
style: !isSelected ? { color: "var(--theme-panel, #1f2937)" } : void 0,
|
|
332
|
+
children: modelName
|
|
333
|
+
}), isSelected && /* @__PURE__ */ jsx(CheckIcon, { className: "h-3.5 w-3.5 text-blue-600 flex-shrink-0" })]
|
|
334
|
+
}, model.id);
|
|
335
|
+
})] }, providerKey);
|
|
336
|
+
})
|
|
337
|
+
]
|
|
338
|
+
})]
|
|
339
|
+
}), document.body)]
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
//#endregion
|
|
343
|
+
export { ModelSelector as default, getProviderKeyFromModelId };
|