@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.
Files changed (233) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunks/memoryStore-CC91S5LP.js → ai/adapter/memoryStore.js} +2 -2
  3. package/dist/ai/adapter/mock/index.js +2 -80
  4. package/dist/ai/adapter/mock/mockAdapter.js +81 -0
  5. package/dist/ai/adapter/openai/createOpenAIStream.js +33 -0
  6. package/dist/ai/adapter/openai/errors.js +37 -0
  7. package/dist/ai/adapter/openai/index.js +5 -333
  8. package/dist/ai/adapter/openai/openaiAdapter.js +54 -0
  9. package/dist/ai/adapter/openai/responses.js +108 -0
  10. package/dist/ai/adapter/openai/sse.js +112 -0
  11. package/dist/{chunks/types-Yy0NrerF.js → ai/adapter/types.js} +1 -1
  12. package/dist/ai/agent/classifier.js +12 -0
  13. package/dist/ai/agent/diff.js +223 -0
  14. package/dist/ai/agent/diffHighlight.js +101 -0
  15. package/dist/ai/agent/permissions.js +29 -0
  16. package/dist/ai/agent/toolfmt.js +383 -0
  17. package/dist/ai/agent/transcript.js +127 -0
  18. package/dist/ai/components/AllocationSelector.js +56 -0
  19. package/dist/ai/components/AttachmentManager.js +147 -0
  20. package/dist/ai/components/AttachmentUpload.js +179 -0
  21. package/dist/ai/components/BlockedGroupBanner.js +29 -0
  22. package/dist/ai/components/BranchNavigator.js +57 -0
  23. package/dist/ai/components/ChatEmptyState.js +171 -0
  24. package/dist/ai/components/ChatInput.js +265 -0
  25. package/dist/ai/components/ChatLayout.js +15 -0
  26. package/dist/ai/components/ChatMessage.js +341 -0
  27. package/dist/ai/components/ChatMessageList.js +301 -0
  28. package/dist/ai/components/ChatSidebar.js +345 -0
  29. package/dist/ai/components/ChatThread.js +198 -0
  30. package/dist/ai/components/ComposerChrome.js +50 -0
  31. package/dist/ai/components/ComposerFrame.js +33 -0
  32. package/dist/ai/components/DragOverlay.js +20 -0
  33. package/dist/ai/components/ModelSelector.js +343 -0
  34. package/dist/ai/components/ShareDialog.js +260 -0
  35. package/dist/ai/components/SlashMenu.js +91 -0
  36. package/dist/ai/components/agent/ActivityLine.js +151 -0
  37. package/dist/ai/components/agent/AgentMessageParts.js +721 -0
  38. package/dist/ai/components/agent/PermissionPicker.js +38 -0
  39. package/dist/ai/components/attachmentKeys.js +10 -0
  40. package/dist/ai/core/ChatProvider.js +848 -0
  41. package/dist/ai/core/KeyboardShortcutsProvider.js +9 -0
  42. package/dist/ai/core/chatReducer.js +223 -0
  43. package/dist/ai/core/config.js +46 -0
  44. package/dist/ai/core/events.js +4 -0
  45. package/dist/ai/core/exportConversation.js +42 -0
  46. package/dist/ai/core/greeting.js +38 -0
  47. package/dist/ai/core/parts.js +84 -0
  48. package/dist/ai/core/useDragDrop.js +36 -0
  49. package/dist/ai/core/useKeyboardShortcuts.js +77 -0
  50. package/dist/ai/core/useSSEStream.js +98 -0
  51. package/dist/ai/index.js +43 -6644
  52. package/dist/ai/strings.js +175 -0
  53. package/dist/ai/ui/Avatar.js +35 -0
  54. package/dist/ai/ui/Dropdown.js +110 -0
  55. package/dist/ai/ui/Markdown.js +79 -0
  56. package/dist/ai/ui/Notice.js +77 -0
  57. package/dist/ai/ui/codeBreaks.js +36 -0
  58. package/dist/ai/ui/prose.js +13 -0
  59. package/dist/ai/ui/sidebar.js +148 -0
  60. package/dist/ai/utils.js +23 -0
  61. package/dist/assets/editor.worker-DfmPPyOL.js +60 -0
  62. package/dist/assets/json.worker-Dqgjpq9F.js +92 -0
  63. package/dist/assets/yaml.worker-DLx0whrM.js +1441 -0
  64. package/dist/components/AddMenu.js +46 -0
  65. package/dist/components/Avatar.js +46 -0
  66. package/dist/{chunks/BareModal-BtRiQezg.js → components/BareModal.js} +1 -1
  67. package/dist/components/Breadcrumbs.js +83 -0
  68. package/dist/components/Button.js +83 -0
  69. package/dist/components/Callout.js +23 -0
  70. package/dist/components/Card.js +31 -0
  71. package/dist/components/CollapsiblePanel.js +77 -0
  72. package/dist/{chunks/ConfirmModal-aOR4O1QE.js → components/ConfirmModal.js} +4 -9
  73. package/dist/components/CopyToClipboard.js +81 -0
  74. package/dist/{chunks/Markdown-CM-FenNE.js → components/CreateModal.js} +9 -116
  75. package/dist/components/DescriptionList.js +42 -0
  76. package/dist/components/Dropdown.js +349 -0
  77. package/dist/components/EmptyState.js +27 -0
  78. package/dist/components/Filter.js +32 -0
  79. package/dist/components/FilterPill.js +13 -0
  80. package/dist/components/IconButton.js +34 -0
  81. package/dist/{chunks/Indicator-hBSIxOPK.js → components/Indicator.js} +1 -1
  82. package/dist/components/Input.js +88 -0
  83. package/dist/components/LetterBadge.js +13 -0
  84. package/dist/components/Listbox.js +123 -0
  85. package/dist/{chunks/Loader-BXoXTZGc.js → components/Loader.js} +1 -1
  86. package/dist/components/Markdown.js +15 -0
  87. package/dist/components/MultiSelect.js +171 -0
  88. package/dist/components/Overview.js +39 -0
  89. package/dist/components/PageHeader.js +194 -0
  90. package/dist/components/Pagination.js +35 -0
  91. package/dist/{chunks/Provider-CDYurraE.js → components/Provider.js} +13 -5
  92. package/dist/{chunks/RequiredMark-DfLdYbYZ.js → components/RequiredMark.js} +1 -1
  93. package/dist/components/SectionHeader.js +11 -0
  94. package/dist/components/SettingModeOption.js +30 -0
  95. package/dist/components/SettingRow.js +35 -0
  96. package/dist/components/SettingsCard.js +31 -0
  97. package/dist/components/SettingsGroup.js +17 -0
  98. package/dist/components/Sort.js +42 -0
  99. package/dist/components/StatusBadge.js +63 -0
  100. package/dist/components/SwitchToggle.js +59 -0
  101. package/dist/{chunks/SwitchToggleSmall-FcsaYSTs.js → components/SwitchToggleSmall.js} +1 -1
  102. package/dist/{chunks/Table-DU2MW8xz.js → components/Table.js} +3 -3
  103. package/dist/components/Toggle.js +20 -0
  104. package/dist/{chunks/Tooltip-K5HJr4_p.js → components/Tooltip.js} +2 -27
  105. package/dist/{chunks/StatusBadge-C1Blx_cW.js → components/dropdownUtils.js} +2 -63
  106. package/dist/{chunks/focus-D_iZJTW1.js → components/focus.js} +1 -1
  107. package/dist/components/ghostButton.js +7 -0
  108. package/dist/components/inputClasses.js +4 -0
  109. package/dist/components/keys.js +27 -0
  110. package/dist/components/relativeTime.js +32 -0
  111. package/dist/{chunks/useCssIsDark-DHr05dG2.js → components/useCssIsDark.js} +1 -1
  112. package/dist/{chunks/duration-BOv8Phl8.js → duration.js} +1 -1
  113. package/dist/editor/Editor.js +13 -0
  114. package/dist/editor/EditorField.js +53 -0
  115. package/dist/editor/FormCodePanel.js +173 -0
  116. package/dist/{chunks/Monaco-DJ8MW-Dq.js → editor/Monaco.js} +5 -26
  117. package/dist/editor/index.js +6 -1
  118. package/dist/editor/themes.js +91 -0
  119. package/dist/editor/workers.d.ts +5 -0
  120. package/dist/editor/workers.js +35 -0
  121. package/dist/editor/yaml.d.ts +9 -1
  122. package/dist/editor/yaml.js +15 -0
  123. package/dist/editor/yaml.worker.d.ts +1 -0
  124. package/dist/editor/yamlLanguage.d.ts +6 -0
  125. package/dist/editor/yamlLanguage.js +312 -0
  126. package/dist/file-explorer/FileExplorer.js +1405 -0
  127. package/dist/file-explorer/FileExplorerContext.js +370 -0
  128. package/dist/{chunks/CodePreviewEditor-YqQBz1YL.js → file-explorer/FilePreview/CodePreviewEditor.js} +3 -2
  129. package/dist/file-explorer/FilePreview/Inline.js +45 -0
  130. package/dist/file-explorer/FilePreview/StreamedBadge.js +16 -0
  131. package/dist/file-explorer/FilePreview/Viewer.js +526 -0
  132. package/dist/file-explorer/FilePreview/index.js +266 -0
  133. package/dist/file-explorer/FilePreview/parse.js +63 -0
  134. package/dist/file-explorer/FilePreview/previewCache.js +98 -0
  135. package/dist/file-explorer/FilePreview/previewType.js +151 -0
  136. package/dist/file-explorer/FilePreview/readCappedBytes.js +196 -0
  137. package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +95 -0
  138. package/dist/file-explorer/FilePreview/useObjectText.js +109 -0
  139. package/dist/file-explorer/FilePreview/usePresignedUrl.js +73 -0
  140. package/dist/file-explorer/FileRow.js +165 -0
  141. package/dist/file-explorer/FileRowsWindow.js +92 -0
  142. package/dist/file-explorer/FileTable.js +172 -0
  143. package/dist/file-explorer/FileTableHeaders.js +49 -0
  144. package/dist/file-explorer/LoadMoreRow.js +46 -0
  145. package/dist/file-explorer/TreeView.js +376 -0
  146. package/dist/file-explorer/helpers.js +537 -0
  147. package/dist/file-explorer/index.js +7 -5671
  148. package/dist/file-explorer/lib/batch.js +39 -0
  149. package/dist/file-explorer/lib/errors.js +9 -0
  150. package/dist/file-explorer/lib/paging.js +124 -0
  151. package/dist/file-explorer/lib/utils.js +322 -0
  152. package/dist/file-explorer/toasts.js +400 -0
  153. package/dist/file-explorer/treeRows.js +73 -0
  154. package/dist/file-explorer/useNearEnd.js +27 -0
  155. package/dist/form/FieldWrapper.js +54 -0
  156. package/dist/form/Fields/BooleanField.js +51 -0
  157. package/dist/form/Fields/CheckboxGroupField.js +72 -0
  158. package/dist/form/Fields/ColorField.js +33 -0
  159. package/dist/form/Fields/DropdownField.js +49 -0
  160. package/dist/form/Fields/DurationField.js +130 -0
  161. package/dist/form/Fields/LabelField.js +19 -0
  162. package/dist/form/Fields/MultiDropdownField.js +40 -0
  163. package/dist/form/Fields/NumberField.js +51 -0
  164. package/dist/form/Fields/ObjectField.js +54 -0
  165. package/dist/form/Fields/RadioField.js +49 -0
  166. package/dist/form/Fields/RangeField.js +58 -0
  167. package/dist/form/Fields/ResourceOptionIcon.js +21 -0
  168. package/dist/form/Fields/StepField.js +6 -0
  169. package/dist/form/Fields/StringField.js +37 -0
  170. package/dist/form/Fields/TextAreaField.js +31 -0
  171. package/dist/form/Fields/durationValidity.js +11 -0
  172. package/dist/form/Form.js +643 -0
  173. package/dist/form/SpecialTypes/DropdownWrapper.js +30 -0
  174. package/dist/form/SpecialTypes/FormikCustomDropdown.js +122 -0
  175. package/dist/form/SpecialTypes/FormikCustomInput.js +111 -0
  176. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.js +31 -0
  177. package/dist/form/SpecialTypes/FormikNumericInput.js +60 -0
  178. package/dist/form/Wizard/WizardContainer.js +138 -0
  179. package/dist/form/Wizard/WizardNavigation.js +92 -0
  180. package/dist/form/Wizard/WizardStepContent.js +35 -0
  181. package/dist/form/Wizard/WizardStepIndicator.js +108 -0
  182. package/dist/form/Wizard/useWizardState.js +118 -0
  183. package/dist/form/Wizard/utils.js +34 -0
  184. package/dist/form/fieldContext.js +26 -0
  185. package/dist/form/fieldRegistry.js +50 -0
  186. package/dist/form/index.js +17 -2321
  187. package/dist/{chunks/FieldWrapper-Cszwg-4B.js → form/lib.js} +2 -97
  188. package/dist/form/useParsedOpts.js +62 -0
  189. package/dist/form/usePrevious.js +11 -0
  190. package/dist/form/utils/checkShowIfObject.js +14 -0
  191. package/dist/form/utils/duration.js +69 -0
  192. package/dist/form/utils/getParentValue.js +23 -0
  193. package/dist/form/utils/getPlaceholder.js +9 -0
  194. package/dist/form/utils/getValueUsingPath.js +23 -0
  195. package/dist/form/utils/secondaryField.js +22 -0
  196. package/dist/graph/AnnotationBanner.js +141 -0
  197. package/dist/graph/Collapse.js +41 -0
  198. package/dist/graph/DependencyGraph.js +1398 -0
  199. package/dist/graph/JobSummary.js +104 -0
  200. package/dist/graph/MatrixGroup.js +107 -0
  201. package/dist/graph/Reveal.js +57 -0
  202. package/dist/graph/TreeView.js +711 -0
  203. package/dist/graph/index.js +1 -2519
  204. package/dist/graph/types.js +6 -0
  205. package/dist/icons.d.ts +4 -4
  206. package/dist/icons.js +5 -5
  207. package/dist/index.js +46 -599
  208. package/dist/list/ActionToolbar.js +184 -0
  209. package/dist/list/HoverCard.js +73 -0
  210. package/dist/list/ListPage.js +145 -0
  211. package/dist/list/ListPager.js +59 -0
  212. package/dist/list/ListTable.js +163 -0
  213. package/dist/list/ListViewControls.js +372 -0
  214. package/dist/list/RowContextMenu.js +351 -0
  215. package/dist/list/UserHoverCard.js +117 -0
  216. package/dist/list/index.js +11 -2
  217. package/dist/list/listSearch.js +137 -0
  218. package/dist/list/listView.js +210 -0
  219. package/dist/list/textEntry.js +10 -0
  220. package/dist/{chunks/logviewer-BTKuQWzw.js → logviewer/LogViewer.js} +4 -82
  221. package/dist/logviewer/index.js +2 -1
  222. package/dist/logviewer/segments.js +80 -0
  223. package/dist/safeUrl.d.ts +6 -0
  224. package/dist/safeUrl.js +22 -0
  225. package/package.json +7 -9
  226. package/dist/assets/editor.worker-BmHUIVG4.js +0 -14807
  227. package/dist/assets/json.worker-D8bJ92nw.js +0 -22207
  228. package/dist/assets/yaml.worker-C4gyGAko.js +0 -50201
  229. package/dist/chunks/PageHeader-0kG_iypP.js +0 -692
  230. package/dist/chunks/SwitchToggle-DShNdcao.js +0 -758
  231. package/dist/chunks/editor-Dk1rbV75.js +0 -361
  232. package/dist/chunks/list-CJm875m7.js +0 -1418
  233. /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 };