@parallelworks/ui 0.40.1 → 0.42.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 (289) hide show
  1. package/README.md +25 -0
  2. package/dist/ai/adapter/memoryStore.js +7 -0
  3. package/dist/ai/adapter/mock/mockAdapter.js +52 -17
  4. package/dist/ai/adapter/openai/createOpenAIStream.js +1 -1
  5. package/dist/ai/adapter/openai/index.d.ts +1 -1
  6. package/dist/ai/adapter/openai/sse.d.ts +8 -2
  7. package/dist/ai/adapter/openai/sse.js +40 -4
  8. package/dist/ai/adapter/openai/wire.d.ts +6 -0
  9. package/dist/ai/components/AllocationSelector.d.ts +3 -4
  10. package/dist/ai/components/AllocationSelector.js +37 -33
  11. package/dist/ai/components/AttachmentManager.js +7 -7
  12. package/dist/ai/components/AttachmentUpload.js +5 -5
  13. package/dist/ai/components/BranchNavigator.js +1 -1
  14. package/dist/ai/components/ChatEmptyState.js +2 -2
  15. package/dist/ai/components/ChatInput.js +10 -8
  16. package/dist/ai/components/ChatLayout.js +4 -5
  17. package/dist/ai/components/ChatMessage.js +12 -12
  18. package/dist/ai/components/ChatMessageList.js +9 -9
  19. package/dist/ai/components/ChatSidebar.js +8 -9
  20. package/dist/ai/components/ChatThread.js +4 -4
  21. package/dist/ai/components/ComposerChrome.d.ts +2 -2
  22. package/dist/ai/components/ComposerChrome.js +3 -6
  23. package/dist/ai/components/DragOverlay.js +2 -2
  24. package/dist/ai/components/ModelSelector.d.ts +5 -3
  25. package/dist/ai/components/ModelSelector.js +102 -339
  26. package/dist/ai/components/PastedText.js +11 -11
  27. package/dist/ai/components/ProviderIssueBanner.d.ts +2 -0
  28. package/dist/ai/components/ProviderIssueBanner.js +34 -16
  29. package/dist/ai/components/Reasoning.js +4 -4
  30. package/dist/ai/components/ShareDialog.js +43 -35
  31. package/dist/ai/components/SlashMenu.d.ts +12 -2
  32. package/dist/ai/components/SlashMenu.js +29 -13
  33. package/dist/ai/components/agent/ActivityLine.js +3 -3
  34. package/dist/ai/components/agent/AgentMessageParts.js +20 -20
  35. package/dist/ai/components/agent/PermissionPicker.js +13 -11
  36. package/dist/ai/components/conversationActions.d.ts +4 -6
  37. package/dist/ai/components/conversationActions.js +9 -13
  38. package/dist/ai/core/ChatProvider.js +4 -4
  39. package/dist/ai/core/config.js +1 -1
  40. package/dist/ai/core/parts.d.ts +5 -0
  41. package/dist/ai/core/parts.js +28 -1
  42. package/dist/ai/core/providerIssues.d.ts +7 -0
  43. package/dist/ai/core/providerIssues.js +9 -1
  44. package/dist/ai/core/useKeyboardShortcuts.d.ts +4 -0
  45. package/dist/ai/core/useKeyboardShortcuts.js +8 -2
  46. package/dist/ai/index.d.ts +0 -1
  47. package/dist/ai/index.js +1 -2
  48. package/dist/ai/strings.d.ts +19 -0
  49. package/dist/ai/strings.js +18 -1
  50. package/dist/ai/ui/Avatar.js +5 -5
  51. package/dist/ai/ui/ConversationSidebar.d.ts +0 -1
  52. package/dist/ai/ui/ConversationSidebar.js +6 -7
  53. package/dist/ai/ui/Markdown.js +3 -3
  54. package/dist/ai/ui/Notice.js +6 -6
  55. package/dist/ai/ui/chip.d.ts +4 -0
  56. package/dist/ai/ui/chip.js +8 -0
  57. package/dist/ai/ui/sidebar.d.ts +6 -5
  58. package/dist/ai/ui/sidebar.js +39 -43
  59. package/dist/components/ActionMenu.d.ts +13 -0
  60. package/dist/components/ActionMenu.js +30 -0
  61. package/dist/components/Avatar.js +6 -6
  62. package/dist/components/Breadcrumbs.js +1 -1
  63. package/dist/components/Button.js +10 -10
  64. package/dist/components/Callout.js +2 -2
  65. package/dist/components/Card.js +3 -3
  66. package/dist/components/Combobox.d.ts +66 -0
  67. package/dist/components/Combobox.js +393 -0
  68. package/dist/components/ConfirmModal.js +11 -11
  69. package/dist/components/ContextMenu.d.ts +23 -0
  70. package/dist/components/ContextMenu.js +89 -0
  71. package/dist/components/CopyToClipboard.js +2 -2
  72. package/dist/components/CreateModal.d.ts +17 -36
  73. package/dist/components/CreateModal.js +97 -259
  74. package/dist/components/DescriptionList.js +8 -8
  75. package/dist/components/Filter.js +2 -2
  76. package/dist/components/FilterPill.js +2 -2
  77. package/dist/components/HoverCard.d.ts +14 -0
  78. package/dist/components/HoverCard.js +33 -0
  79. package/dist/components/IconButton.d.ts +5 -5
  80. package/dist/components/IconButton.js +18 -22
  81. package/dist/components/InfoTip.d.ts +15 -0
  82. package/dist/components/InfoTip.js +60 -0
  83. package/dist/components/Input.js +5 -5
  84. package/dist/components/LetterBadge.js +2 -2
  85. package/dist/components/Loader.js +3 -3
  86. package/dist/components/Markdown.js +1 -1
  87. package/dist/components/MarkdownImpl.js +2 -2
  88. package/dist/components/Menu.d.ts +33 -0
  89. package/dist/components/Menu.js +86 -0
  90. package/dist/components/MenuEntries.d.ts +56 -0
  91. package/dist/components/MenuEntries.js +123 -0
  92. package/dist/components/Modal.d.ts +29 -0
  93. package/dist/components/Modal.js +47 -0
  94. package/dist/components/Overview.js +11 -10
  95. package/dist/components/PageHeader.d.ts +10 -16
  96. package/dist/components/PageHeader.js +69 -95
  97. package/dist/components/Pagination.js +5 -5
  98. package/dist/components/Provider.d.ts +5 -11
  99. package/dist/components/Provider.js +11 -14
  100. package/dist/components/Radio.d.ts +8 -0
  101. package/dist/components/Radio.js +16 -0
  102. package/dist/components/SectionHeader.js +2 -2
  103. package/dist/components/SegmentedControl.d.ts +6 -5
  104. package/dist/components/SegmentedControl.js +34 -30
  105. package/dist/components/Select.d.ts +27 -0
  106. package/dist/components/Select.js +78 -0
  107. package/dist/components/SettingModeOption.d.ts +5 -8
  108. package/dist/components/SettingModeOption.js +10 -12
  109. package/dist/components/SettingRow.js +4 -4
  110. package/dist/components/SettingsCard.js +4 -4
  111. package/dist/components/SettingsGroup.js +2 -2
  112. package/dist/components/Sort.js +1 -1
  113. package/dist/components/StatusBadge.js +7 -7
  114. package/dist/components/StatusIcons.js +4 -4
  115. package/dist/components/Switch.d.ts +22 -0
  116. package/dist/components/Switch.js +28 -0
  117. package/dist/components/Table.js +11 -11
  118. package/dist/components/Tooltip.d.ts +24 -20
  119. package/dist/components/Tooltip.js +58 -76
  120. package/dist/components/floating.d.ts +14 -0
  121. package/dist/components/floating.js +21 -0
  122. package/dist/components/search.d.ts +2 -0
  123. package/dist/components/search.js +14 -0
  124. package/dist/editor/Editor.js +1 -1
  125. package/dist/editor/EditorField.js +16 -21
  126. package/dist/editor/FormCodePanel.js +19 -19
  127. package/dist/editor/Monaco.js +3 -3
  128. package/dist/file-explorer/FileExplorer.js +77 -81
  129. package/dist/file-explorer/FileExplorerContext.js +2 -2
  130. package/dist/file-explorer/FilePreview/CodePreviewEditor.js +1 -1
  131. package/dist/file-explorer/FilePreview/Inline.js +1 -1
  132. package/dist/file-explorer/FilePreview/StreamedBadge.js +5 -6
  133. package/dist/file-explorer/FilePreview/Viewer.js +8 -8
  134. package/dist/file-explorer/FilePreview/index.d.ts +1 -1
  135. package/dist/file-explorer/FilePreview/index.js +49 -83
  136. package/dist/file-explorer/FileRow.d.ts +4 -3
  137. package/dist/file-explorer/FileRow.js +32 -38
  138. package/dist/file-explorer/FileRowsWindow.d.ts +3 -2
  139. package/dist/file-explorer/FileRowsWindow.js +4 -4
  140. package/dist/file-explorer/FileTable.d.ts +3 -2
  141. package/dist/file-explorer/FileTable.js +3 -3
  142. package/dist/file-explorer/LoadMoreRow.js +1 -1
  143. package/dist/file-explorer/TreeView.js +11 -16
  144. package/dist/file-explorer/helpers.js +11 -12
  145. package/dist/file-explorer/toasts.js +11 -11
  146. package/dist/form/FieldGroup.d.ts +11 -0
  147. package/dist/form/FieldGroup.js +36 -0
  148. package/dist/form/FieldWrapper.js +4 -4
  149. package/dist/form/Fields/BooleanField.js +23 -33
  150. package/dist/form/Fields/CheckboxGroupField.d.ts +1 -1
  151. package/dist/form/Fields/CheckboxGroupField.js +9 -13
  152. package/dist/form/Fields/ColorField.d.ts +1 -2
  153. package/dist/form/Fields/ColorField.js +6 -11
  154. package/dist/form/Fields/DropdownField.d.ts +3 -13
  155. package/dist/form/Fields/DropdownField.js +40 -27
  156. package/dist/form/Fields/DurationField.d.ts +1 -2
  157. package/dist/form/Fields/DurationField.js +25 -32
  158. package/dist/form/Fields/LabelField.js +2 -2
  159. package/dist/form/Fields/MultiDropdownField.d.ts +6 -12
  160. package/dist/form/Fields/MultiDropdownField.js +31 -22
  161. package/dist/form/Fields/NumberField.d.ts +1 -1
  162. package/dist/form/Fields/NumberField.js +65 -35
  163. package/dist/form/Fields/ObjectField.d.ts +1 -1
  164. package/dist/form/Fields/ObjectField.js +12 -43
  165. package/dist/form/Fields/RadioField.d.ts +1 -1
  166. package/dist/form/Fields/RadioField.js +30 -41
  167. package/dist/form/Fields/RangeField.d.ts +1 -2
  168. package/dist/form/Fields/RangeField.js +23 -16
  169. package/dist/form/Fields/StringField.d.ts +1 -2
  170. package/dist/form/Fields/StringField.js +58 -26
  171. package/dist/form/Fields/TextAreaField.js +12 -10
  172. package/dist/form/Form.d.ts +30 -48
  173. package/dist/form/Form.js +306 -412
  174. package/dist/form/FormLayout.js +9 -9
  175. package/dist/form/Wizard/WizardContainer.d.ts +1 -4
  176. package/dist/form/Wizard/WizardContainer.js +40 -56
  177. package/dist/form/Wizard/WizardNavigation.js +1 -1
  178. package/dist/form/Wizard/WizardStepContent.d.ts +1 -5
  179. package/dist/form/Wizard/WizardStepContent.js +11 -16
  180. package/dist/form/Wizard/WizardStepIndicator.js +6 -6
  181. package/dist/form/Wizard/types.d.ts +0 -14
  182. package/dist/form/fieldRegistry.d.ts +1 -1
  183. package/dist/form/fieldRegistry.js +6 -6
  184. package/dist/form/formEditing.js +1 -1
  185. package/dist/form/index.d.ts +5 -4
  186. package/dist/form/index.js +6 -7
  187. package/dist/form/types/fieldComponentTypes.d.ts +8 -10
  188. package/dist/form/useChoiceField.d.ts +20 -0
  189. package/dist/form/useChoiceField.js +49 -0
  190. package/dist/form/useFieldValue.d.ts +7 -0
  191. package/dist/form/useFieldValue.js +20 -0
  192. package/dist/form/useParsedOpts.d.ts +3 -1
  193. package/dist/form/useParsedOpts.js +12 -17
  194. package/dist/form/utils/dependentOptions.d.ts +8 -4
  195. package/dist/form/utils/dependentOptions.js +25 -17
  196. package/dist/form/utils/fieldItems.d.ts +10 -0
  197. package/dist/form/utils/fieldItems.js +11 -0
  198. package/dist/form/utils/fieldPath.d.ts +10 -0
  199. package/dist/form/utils/fieldPath.js +20 -0
  200. package/dist/form/utils/shareEqual.d.ts +6 -0
  201. package/dist/form/utils/shareEqual.js +38 -0
  202. package/dist/graph/AnnotationBanner.js +8 -8
  203. package/dist/graph/Collapse.d.ts +1 -0
  204. package/dist/graph/Collapse.js +6 -27
  205. package/dist/graph/Connectors.js +5 -5
  206. package/dist/graph/DependencyGraph.js +20 -19
  207. package/dist/graph/GraphEditor.js +30 -41
  208. package/dist/graph/GraphEditorDialogs.js +17 -17
  209. package/dist/graph/GraphNode.js +1 -1
  210. package/dist/graph/InputDialog.js +19 -22
  211. package/dist/graph/InputsEditor.js +58 -66
  212. package/dist/graph/JobSummary.js +42 -39
  213. package/dist/graph/MatrixGroup.js +1 -1
  214. package/dist/graph/ProblemsButton.js +18 -19
  215. package/dist/graph/Reveal.js +1 -1
  216. package/dist/graph/ShortcutsButton.js +15 -18
  217. package/dist/graph/SuggestionInput.d.ts +5 -10
  218. package/dist/graph/SuggestionInput.js +14 -18
  219. package/dist/graph/ToolbarPopover.d.ts +11 -6
  220. package/dist/graph/ToolbarPopover.js +35 -67
  221. package/dist/graph/TreeView.js +24 -37
  222. package/dist/graph/WorkflowSettingsDialog.js +4 -4
  223. package/dist/graph/editorChrome.js +13 -12
  224. package/dist/graph/editorFields.js +29 -29
  225. package/dist/graph/editorPrimitives.d.ts +1 -1
  226. package/dist/graph/editorPrimitives.js +10 -7
  227. package/dist/graph/editorRows.js +103 -97
  228. package/dist/graph/fieldProblems.js +2 -2
  229. package/dist/graph/graphEditorState.d.ts +4 -3
  230. package/dist/graph/inputRefs.js +9 -9
  231. package/dist/graph/settingsViews.js +6 -7
  232. package/dist/graph/stepWith.js +6 -6
  233. package/dist/index.d.ts +14 -15
  234. package/dist/index.js +15 -14
  235. package/dist/list/ActionToolbar.d.ts +6 -14
  236. package/dist/list/ActionToolbar.js +48 -96
  237. package/dist/list/ListPage.d.ts +6 -12
  238. package/dist/list/ListPage.js +46 -29
  239. package/dist/list/ListPager.js +4 -4
  240. package/dist/list/ListTable.js +5 -5
  241. package/dist/list/ListViewControls.d.ts +5 -4
  242. package/dist/list/ListViewControls.js +177 -267
  243. package/dist/list/UserHoverCard.d.ts +3 -17
  244. package/dist/list/UserHoverCard.js +21 -45
  245. package/dist/list/index.d.ts +3 -4
  246. package/dist/list/index.js +3 -4
  247. package/dist/list/listSearch.js +12 -11
  248. package/dist/list/rowMenu.d.ts +63 -0
  249. package/dist/list/rowMenu.js +134 -0
  250. package/dist/logviewer/LogViewer.js +26 -26
  251. package/dist/styles.css +1 -1
  252. package/package.json +7 -13
  253. package/dist/ai/ui/Dropdown.d.ts +0 -31
  254. package/dist/ai/ui/Dropdown.js +0 -110
  255. package/dist/components/AddMenu.d.ts +0 -18
  256. package/dist/components/AddMenu.js +0 -46
  257. package/dist/components/BareModal.d.ts +0 -17
  258. package/dist/components/BareModal.js +0 -77
  259. package/dist/components/CollapsiblePanel.d.ts +0 -27
  260. package/dist/components/CollapsiblePanel.js +0 -77
  261. package/dist/components/Dropdown.d.ts +0 -28
  262. package/dist/components/Dropdown.js +0 -355
  263. package/dist/components/Listbox.d.ts +0 -21
  264. package/dist/components/Listbox.js +0 -119
  265. package/dist/components/MultiSelect.d.ts +0 -22
  266. package/dist/components/MultiSelect.js +0 -168
  267. package/dist/components/SwitchToggle.d.ts +0 -15
  268. package/dist/components/SwitchToggle.js +0 -57
  269. package/dist/components/SwitchToggleSmall.d.ts +0 -6
  270. package/dist/components/SwitchToggleSmall.js +0 -14
  271. package/dist/components/Toggle.d.ts +0 -6
  272. package/dist/components/Toggle.js +0 -20
  273. package/dist/components/dropdownUtils.d.ts +0 -37
  274. package/dist/components/dropdownUtils.js +0 -99
  275. package/dist/form/SpecialTypes/DropdownWrapper.d.ts +0 -25
  276. package/dist/form/SpecialTypes/DropdownWrapper.js +0 -28
  277. package/dist/form/SpecialTypes/FormikCustomDropdown.d.ts +0 -51
  278. package/dist/form/SpecialTypes/FormikCustomDropdown.js +0 -119
  279. package/dist/form/SpecialTypes/FormikCustomInput.d.ts +0 -19
  280. package/dist/form/SpecialTypes/FormikCustomInput.js +0 -121
  281. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.d.ts +0 -13
  282. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.js +0 -31
  283. package/dist/form/SpecialTypes/FormikNumericInput.d.ts +0 -9
  284. package/dist/form/SpecialTypes/FormikNumericInput.js +0 -60
  285. package/dist/form/SpecialTypes/index.d.ts +0 -3
  286. package/dist/list/HoverCard.d.ts +0 -24
  287. package/dist/list/HoverCard.js +0 -73
  288. package/dist/list/RowContextMenu.d.ts +0 -127
  289. package/dist/list/RowContextMenu.js +0 -515
@@ -1,17 +1,15 @@
1
- import { FaCheck } from "../../icons/fa/FaCheck.js";
2
1
  import { FaChevronDown } from "../../icons/fa/FaChevronDown.js";
3
2
  import { FaCloud } from "../../icons/fa/FaCloud.js";
4
- import { FaCog } from "../../icons/fa/FaCog.js";
5
3
  import { FaExclamationTriangle } from "../../icons/fa/FaExclamationTriangle.js";
6
- import { FaRobot } from "../../icons/fa/FaRobot.js";
7
4
  import { FaUndoAlt } from "../../icons/fa/FaUndoAlt.js";
5
+ import { Combobox } from "../../components/Combobox.js";
8
6
  import { useChatConfig } from "../core/config.js";
9
- import { providerIssueFor } from "../core/providerIssues.js";
7
+ import { issueDetail, issueLabel, providerIssueFor } from "../core/providerIssues.js";
10
8
  import { useChat } from "../core/ChatProvider.js";
11
- import cx from "classnames";
12
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
13
- import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
14
- import { createPortal } from "react-dom";
9
+ import { chipClassName } from "../ui/chip.js";
10
+ import { clsx } from "clsx";
11
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
+ import { useMemo } from "react";
15
13
  //#region src/ai/components/ModelSelector.tsx
16
14
  var ORG_OWNER = "org";
17
15
  /** Who registered a provider: the provider list's owner when the adapter
@@ -21,14 +19,11 @@ function providerOwner(info, model) {
21
19
  if (info) return info.user === ORG_OWNER ? "" : info.user;
22
20
  return model?.provider_owner ?? "";
23
21
  }
24
- /** The search field plus the list's max-h-80, so the flip decision matches
25
- * what renders. */
26
- var MAX_PANEL_HEIGHT_PX = 380;
27
22
  function CspIcon({ cspKind, className }) {
28
23
  const { slots } = useChatConfig();
29
24
  const custom = slots.providerIcon?.(cspKind, className);
30
- if (custom) return /* @__PURE__ */ jsx(Fragment$1, { children: custom });
31
- return /* @__PURE__ */ jsx(FaCloud, { className: cx("text-purple-500", className) });
25
+ if (custom) return /* @__PURE__ */ jsx(Fragment, { children: custom });
26
+ return /* @__PURE__ */ jsx(FaCloud, { className: clsx("text-purple-500", className) });
32
27
  }
33
28
  function getProviderKeyFromModelId(modelId) {
34
29
  const idx = modelId.indexOf("/");
@@ -54,339 +49,107 @@ function groupModelsByProvider(models) {
54
49
  }
55
50
  return groups;
56
51
  }
57
- function ModelSelector({ targetSession, variant = "default" }) {
58
- const bare = variant === "bare";
52
+ /** Picks the model a conversation runs on, grouped by provider. Sessions that
53
+ * are still connecting or not responding show as headings with their status,
54
+ * so the reader sees why their models are missing. */
55
+ function ModelSelector({ targetSession }) {
59
56
  const { providers, models, unreachableSessions, providerIssues, selectedProvider, setSelectedProvider, isLoadingModels, hasLoadedModels, modelsError, refreshModels, currentUser } = useChat();
60
- const { extraLinks, LinkComponent, strings } = useChatConfig();
61
- const [isOpen, setIsOpen] = useState(false);
62
- const [searchQuery, setSearchQuery] = useState("");
63
- const [dropdownPosition, setDropdownPosition] = useState({
64
- top: 0,
65
- bottom: 0,
66
- left: 0,
67
- width: 0,
68
- flipped: false
69
- });
70
- const containerRef = useRef(null);
71
- const dropdownRef = useRef(null);
72
- const buttonRef = useRef(null);
73
- const inputRef = useRef(null);
74
- const [mounted, setMounted] = useState(false);
75
- useEffect(() => {
76
- setMounted(true);
77
- }, []);
78
- const updateDropdownPosition = useCallback(() => {
79
- if (!buttonRef.current) return;
80
- const rect = buttonRef.current.getBoundingClientRect();
81
- const below = window.innerHeight - rect.bottom;
82
- setDropdownPosition({
83
- top: rect.bottom + 4,
84
- bottom: window.innerHeight - rect.top + 4,
85
- left: rect.left,
86
- width: Math.max(rect.width, 320),
87
- flipped: below < MAX_PANEL_HEIGHT_PX && rect.top > below
57
+ const { strings } = useChatConfig();
58
+ const t = strings.modelPicker;
59
+ const targetSessionFound = !!targetSession && models.some((m) => m.id.startsWith(`session:${targetSession}/`));
60
+ const targetSessionStatus = !targetSession ? null : isLoadingModels ? "connecting" : hasLoadedModels && !targetSessionFound ? "offline" : null;
61
+ const groups = useMemo(() => {
62
+ const status = (label, description) => ({
63
+ label,
64
+ icon: /* @__PURE__ */ jsx(FaCloud, { className: "h-3 w-3 text-amber-500" }),
65
+ description,
66
+ items: []
88
67
  });
89
- }, []);
90
- useEffect(() => {
91
- if (!isOpen) return;
92
- function handleClickOutside(event) {
93
- const target = event.target;
94
- const clickedInsideButton = containerRef.current?.contains(target);
95
- const clickedInsideDropdown = dropdownRef.current?.contains(target);
96
- if (!clickedInsideButton && !clickedInsideDropdown) setIsOpen(false);
97
- }
98
- const timeoutId = setTimeout(() => {
99
- document.addEventListener("mousedown", handleClickOutside);
100
- }, 0);
101
- return () => {
102
- clearTimeout(timeoutId);
103
- document.removeEventListener("mousedown", handleClickOutside);
104
- };
105
- }, [isOpen]);
106
- useEffect(() => {
107
- if (!isOpen) return;
108
- updateDropdownPosition();
109
- window.addEventListener("scroll", updateDropdownPosition, true);
110
- window.addEventListener("resize", updateDropdownPosition);
111
- return () => {
112
- window.removeEventListener("scroll", updateDropdownPosition, true);
113
- window.removeEventListener("resize", updateDropdownPosition);
114
- };
115
- }, [isOpen, updateDropdownPosition]);
116
- useEffect(() => {
117
- if (isOpen && inputRef.current) inputRef.current.focus();
118
- }, [isOpen]);
119
- const filteredModels = useMemo(() => {
120
- if (!searchQuery.trim()) return models;
121
- const query = searchQuery.toLowerCase();
122
- return models.filter((model) => getModelNameFromId(model.id).toLowerCase().includes(query) || (model.provider || "").toLowerCase().includes(query) || (model.name || "").toLowerCase().includes(query));
123
- }, [models, searchQuery]);
124
- const groupedModels = useMemo(() => groupModelsByProvider(filteredModels), [filteredModels]);
125
- const selectedModelInfo = models.find((m) => m.id === selectedProvider);
126
- const selectedModelName = selectedModelInfo ? getModelLabel(selectedModelInfo) : null;
127
- const selectedProviderName = selectedModelInfo?.provider || null;
128
- const issueLabel = (issue) => issue.status === "unauthorized" ? strings.providerIssue.keyRejected : strings.providerIssue.unreachable;
129
- const issueDetail = (issue) => issue.message || (issue.status === "unauthorized" ? strings.providerIssue.keyRejectedHint : strings.providerIssue.unreachableHint);
130
- const issueSummary = (issue) => `${issueLabel(issue)} \u2014 ${issueDetail(issue)}`;
131
- const selectedModelIssue = providerIssueFor(providerIssues, selectedModelInfo);
132
- const handleSelect = (modelId) => {
133
- setSelectedProvider(modelId);
134
- setIsOpen(false);
135
- setSearchQuery("");
136
- };
137
- const targetSessionDisplayName = targetSession?.split(":").pop();
138
- const targetSessionFound = targetSession && models.length > 0 ? models.some((m) => m.id.startsWith(`session:${targetSession}/`)) : false;
139
- const targetSessionStatus = targetSession ? isLoadingModels ? "connecting" : hasLoadedModels && !targetSessionFound ? "offline" : null : null;
140
- const boxClass = bare ? "flex items-center gap-1.5 rounded-full h-8 px-2.5 min-w-0 max-w-[280px]" : "flex items-center gap-2 px-3 h-8 rounded-lg min-w-[180px] max-w-[280px]";
141
- const textClass = bare ? "text-[13px]" : "text-sm";
142
- if (isLoadingModels && models.length === 0 && !targetSession) return /* @__PURE__ */ jsx("div", {
143
- ref: containerRef,
144
- className: "relative min-w-0",
145
- children: /* @__PURE__ */ jsxs("div", {
146
- className: boxClass,
147
- children: [!bare && /* @__PURE__ */ jsx(FaRobot, { className: "h-4 w-4 text-purple-500 flex-shrink-0 animate-pulse" }), /* @__PURE__ */ jsx("span", {
148
- className: cx(textClass, "theme-muted-text"),
149
- children: "Loading models..."
150
- })]
151
- })
152
- });
153
- if (modelsError && models.length === 0) return /* @__PURE__ */ jsx("div", {
154
- ref: containerRef,
155
- className: "relative min-w-0",
156
- children: /* @__PURE__ */ jsxs("div", {
157
- className: boxClass,
158
- children: [
159
- !bare && /* @__PURE__ */ jsx(FaRobot, { className: "h-4 w-4 text-red-500 flex-shrink-0" }),
160
- /* @__PURE__ */ jsx("span", {
161
- className: cx(textClass, "text-red-600"),
162
- children: "Failed to load models"
68
+ const sessions = unreachableSessions.filter((s) => `${s.owner}:${s.name}` !== targetSession).map((s) => status(s.name, t.notResponding));
69
+ if (targetSession && targetSessionStatus) sessions.unshift(status(targetSession.split(":").pop() ?? targetSession, targetSessionStatus === "connecting" ? t.connecting : t.notResponding));
70
+ const byProvider = [...groupModelsByProvider(models)].map(([providerKey, providerModels]) => {
71
+ const info = providers.find((p) => `${p.user}:${p.name}` === providerKey);
72
+ const first = providerModels[0];
73
+ const name = first?.provider || info?.name || providerKey;
74
+ const platform = info?.platformLabel;
75
+ const owner = providerOwner(info, first);
76
+ const issue = providerIssueFor(providerIssues, first);
77
+ const description = [
78
+ platform && platform.toLowerCase() !== name.toLowerCase() ? platform : null,
79
+ owner && owner !== currentUser.username ? t.sharedBy(owner) : null,
80
+ issue ? issueLabel(strings.providerIssue, issue) : null
81
+ ].filter(Boolean).join(" · ");
82
+ return {
83
+ label: name,
84
+ icon: /* @__PURE__ */ jsx(CspIcon, {
85
+ cspKind: info?.cspKind ?? "other",
86
+ className: "h-3 w-3"
163
87
  }),
164
- /* @__PURE__ */ jsxs("button", {
165
- type: "button",
166
- onClick: () => refreshModels(),
167
- className: "flex items-center gap-1 text-xs text-blue-600 hover:underline ml-1",
168
- children: [/* @__PURE__ */ jsx(FaUndoAlt, { className: "h-2.5 w-2.5" }), "Retry"]
169
- })
170
- ]
171
- })
88
+ description: description || void 0,
89
+ items: providerModels.map((model) => ({
90
+ label: getModelLabel(model),
91
+ value: model.id,
92
+ disabled: !!issue
93
+ }))
94
+ };
95
+ });
96
+ return [...sessions, ...byProvider];
97
+ }, [
98
+ models,
99
+ providers,
100
+ providerIssues,
101
+ unreachableSessions,
102
+ targetSession,
103
+ targetSessionStatus,
104
+ currentUser.username,
105
+ strings.providerIssue,
106
+ t
107
+ ]);
108
+ if (isLoadingModels && models.length === 0 && !targetSession) return /* @__PURE__ */ jsx("span", {
109
+ className: clsx(chipClassName, "cursor-default animate-pulse"),
110
+ children: t.loading
172
111
  });
173
- if (hasLoadedModels && models.length === 0 && !targetSessionStatus && unreachableSessions.length === 0) return /* @__PURE__ */ jsx("div", {
174
- ref: containerRef,
175
- className: "relative min-w-0",
176
- children: /* @__PURE__ */ jsxs("div", {
177
- className: boxClass,
178
- children: [!bare && /* @__PURE__ */ jsx(FaRobot, { className: "h-4 w-4 theme-muted-text flex-shrink-0" }), /* @__PURE__ */ jsx("span", {
179
- className: cx(textClass, "theme-muted-text"),
180
- children: "No models available"
181
- })]
182
- })
112
+ if (modelsError && models.length === 0) return /* @__PURE__ */ jsxs("span", {
113
+ className: clsx(chipClassName, "cursor-default text-(--theme-error)"),
114
+ children: [t.failed, /* @__PURE__ */ jsxs("button", {
115
+ type: "button",
116
+ onClick: () => refreshModels(),
117
+ className: "flex items-center gap-1 text-xs text-(--theme-element) hover:underline",
118
+ children: [/* @__PURE__ */ jsx(FaUndoAlt, { className: "h-2.5 w-2.5" }), t.retry]
119
+ })]
120
+ });
121
+ if (groups.length === 0 && hasLoadedModels) return /* @__PURE__ */ jsx("span", {
122
+ className: clsx(chipClassName, "cursor-default"),
123
+ children: t.none
183
124
  });
184
- const triggerText = selectedModelName ? null : targetSessionStatus === "connecting" ? "Connecting..." : models.length === 0 && isLoadingModels ? "Loading models..." : "Select a model";
185
- return /* @__PURE__ */ jsxs("div", {
186
- ref: containerRef,
187
- className: "relative min-w-0",
188
- children: [/* @__PURE__ */ jsxs("button", {
189
- ref: buttonRef,
125
+ const selected = models.find((m) => m.id === selectedProvider);
126
+ const issue = providerIssueFor(providerIssues, selected);
127
+ return /* @__PURE__ */ jsx(Combobox, {
128
+ "aria-label": t.choose,
129
+ placeholder: t.search,
130
+ items: groups,
131
+ value: selectedProvider,
132
+ onValueChange: (id) => id && setSelectedProvider(id),
133
+ disabled: groups.length === 0,
134
+ triggerTooltip: issue ? `${issueLabel(strings.providerIssue, issue)} \u2014 ${issueDetail(strings.providerIssue, issue)}` : void 0,
135
+ trigger: /* @__PURE__ */ jsxs("button", {
190
136
  type: "button",
191
- onClick: () => setIsOpen(!isOpen),
192
- "aria-haspopup": "dialog",
193
- "aria-expanded": isOpen,
194
- disabled: models.length === 0 && !targetSessionStatus && unreachableSessions.length === 0,
195
- className: cx(boxClass, "transition-all cursor-pointer bg-transparent", "focus:outline-none", "disabled:opacity-50 disabled:cursor-not-allowed", bare ? "hover:chat-tint" : "border theme-border hover:theme-hover"),
196
- children: [
197
- !bare && /* @__PURE__ */ jsx(FaRobot, { className: cx("h-4 w-4 flex-shrink-0", targetSessionStatus === "connecting" ? "text-amber-500 animate-pulse" : "text-purple-500") }),
198
- /* @__PURE__ */ jsx("div", {
199
- className: "flex-1 min-w-0 text-left",
200
- children: selectedModelName ? /* @__PURE__ */ jsxs("div", {
201
- className: "flex items-baseline gap-1.5 min-w-0",
202
- children: [
203
- /* @__PURE__ */ jsx("span", {
204
- title: selectedModelName,
205
- className: cx("truncate", bare ? "text-[13px] font-medium theme-text" : "text-sm font-medium text-(--theme-panel)"),
206
- children: selectedModelName
207
- }),
208
- selectedProviderName && /* @__PURE__ */ jsx("span", {
209
- title: selectedProviderName,
210
- className: "theme-muted-text truncate flex-shrink-0 max-w-[45%] text-xs",
211
- children: selectedProviderName
212
- }),
213
- selectedModelIssue && /* @__PURE__ */ jsx(FaExclamationTriangle, {
214
- title: issueSummary(selectedModelIssue),
215
- className: "h-3.5 w-3.5 text-amber-500 flex-shrink-0 self-center"
216
- })
217
- ]
218
- }) : /* @__PURE__ */ jsx("span", {
219
- className: cx(textClass, targetSessionStatus === "connecting" ? "text-amber-600" : "theme-muted-text"),
220
- children: triggerText
221
- })
137
+ className: chipClassName,
138
+ children: [selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
139
+ /* @__PURE__ */ jsx("span", {
140
+ className: "truncate font-medium theme-text",
141
+ children: getModelLabel(selected)
142
+ }),
143
+ selected.provider && /* @__PURE__ */ jsx("span", {
144
+ className: "max-w-[45%] shrink-0 truncate text-xs",
145
+ children: selected.provider
222
146
  }),
223
- /* @__PURE__ */ jsx(FaChevronDown, { className: cx("h-3 w-3 theme-muted-text transition-transform flex-shrink-0", !bare && "ml-auto", isOpen && "rotate-180") })
224
- ]
225
- }), isOpen && mounted && createPortal(/* @__PURE__ */ jsxs("div", {
226
- ref: dropdownRef,
227
- role: "none",
228
- onClick: (e) => e.stopPropagation(),
229
- onKeyDown: (e) => {
230
- if (e.key === "Escape") {
231
- e.preventDefault();
232
- setIsOpen(false);
233
- buttonRef.current?.focus();
234
- }
235
- },
236
- style: {
237
- position: "fixed",
238
- ...dropdownPosition.flipped ? { bottom: dropdownPosition.bottom } : { top: dropdownPosition.top },
239
- left: dropdownPosition.left,
240
- width: dropdownPosition.width,
241
- zIndex: 9999
242
- },
243
- className: cx("theme-panel rounded-lg shadow-lg", "border theme-border", "overflow-hidden"),
244
- children: [/* @__PURE__ */ jsx("div", {
245
- className: "p-2 border-b theme-border",
246
- children: /* @__PURE__ */ jsx("input", {
247
- ref: inputRef,
248
- type: "text",
249
- value: searchQuery,
250
- onChange: (e) => setSearchQuery(e.target.value),
251
- placeholder: "Search models...",
252
- 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")
253
- })
254
- }), /* @__PURE__ */ jsxs("div", {
255
- className: "max-h-80 overflow-y-auto",
256
- children: [
257
- targetSessionStatus && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
258
- className: "px-3 py-1.5 bg-amber-500/5 sticky top-0",
259
- children: /* @__PURE__ */ jsxs("div", {
260
- className: "flex items-center gap-2",
261
- children: [
262
- /* @__PURE__ */ jsx(FaCloud, { className: "h-3 w-3 text-amber-500" }),
263
- /* @__PURE__ */ jsx("span", {
264
- className: "text-xs font-semibold text-amber-600 uppercase tracking-wide flex-1",
265
- children: targetSessionDisplayName
266
- }),
267
- /* @__PURE__ */ jsx("span", {
268
- 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"),
269
- children: targetSessionStatus === "connecting" ? "Connecting..." : "Not responding"
270
- })
271
- ]
272
- })
273
- }), /* @__PURE__ */ jsx("div", {
274
- className: "px-4 py-2 flex items-center gap-2",
275
- children: /* @__PURE__ */ jsx("span", {
276
- className: "text-sm theme-muted-text italic",
277
- children: targetSessionStatus === "connecting" ? "Loading session models..." : "Service not responding — ensure it is running on the remote host"
278
- })
279
- })] }),
280
- unreachableSessions.filter((s) => !targetSession || `${s.owner}:${s.name}` !== targetSession).map((s) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
281
- className: "px-3 py-1.5 bg-amber-500/5 sticky top-0",
282
- children: /* @__PURE__ */ jsxs("div", {
283
- className: "flex items-center gap-2",
284
- children: [
285
- /* @__PURE__ */ jsx(FaCloud, { className: "h-3 w-3 text-amber-500" }),
286
- /* @__PURE__ */ jsx("span", {
287
- className: "text-xs font-semibold text-amber-600 uppercase tracking-wide flex-1",
288
- children: s.name
289
- }),
290
- /* @__PURE__ */ jsx("span", {
291
- className: "text-[10px] text-amber-500 bg-amber-500/10 px-1.5 py-0.5 rounded-full",
292
- children: "Not responding"
293
- })
294
- ]
295
- })
296
- }), /* @__PURE__ */ jsx("div", {
297
- className: "px-4 py-2 flex items-center gap-2",
298
- children: /* @__PURE__ */ jsx("span", {
299
- className: "text-sm theme-muted-text italic",
300
- children: "Service not responding — ensure it is running on the remote host"
301
- })
302
- })] }, `${s.owner}:${s.name}`)),
303
- groupedModels.size === 0 && !targetSessionStatus && unreachableSessions.length === 0 ? /* @__PURE__ */ jsx("div", {
304
- className: "p-4 text-center theme-muted-text text-sm",
305
- children: "No models found"
306
- }) : Array.from(groupedModels.entries()).map(([providerKey, providerModels]) => {
307
- const providerInfo = providers.find((p) => `${p.user}:${p.name}` === providerKey);
308
- const providerName = providerModels[0]?.provider || providerInfo?.name || "Unknown Provider";
309
- const platform = providerInfo?.platformLabel;
310
- const platformSuffix = providerInfo && platform && platform.toLowerCase() !== providerName.toLowerCase() ? platform : null;
311
- const isOwner = providerInfo?.user === currentUser.username;
312
- const owner = providerOwner(providerInfo, providerModels[0]);
313
- const headerSuffix = [platformSuffix, owner && owner !== currentUser.username ? strings.modelPicker.sharedBy(owner) : null].filter(Boolean).join(" · ");
314
- const settingsHref = providerInfo && extraLinks.providerSettings ? extraLinks.providerSettings(providerInfo.user, providerInfo.name) : null;
315
- const issue = providerIssueFor(providerIssues, providerModels[0]);
316
- return /* @__PURE__ */ jsxs("div", { children: [
317
- /* @__PURE__ */ jsx("div", {
318
- className: "px-3 py-1.5 theme-muted-panel sticky top-0",
319
- children: /* @__PURE__ */ jsxs("div", {
320
- className: "flex items-center gap-2",
321
- children: [
322
- /* @__PURE__ */ jsx(CspIcon, {
323
- cspKind: providerInfo?.cspKind ?? "other",
324
- className: "h-3 w-3"
325
- }),
326
- /* @__PURE__ */ jsx("span", {
327
- className: "text-xs font-semibold theme-muted-text uppercase tracking-wide",
328
- children: providerName
329
- }),
330
- /* @__PURE__ */ jsx("span", {
331
- className: "min-w-0 flex-1 truncate text-xs theme-muted-text",
332
- children: headerSuffix ? `· ${headerSuffix}` : ""
333
- }),
334
- /* @__PURE__ */ jsxs("span", {
335
- className: "text-xs theme-muted-text",
336
- children: [
337
- "(",
338
- providerModels.length,
339
- ")"
340
- ]
341
- }),
342
- issue && /* @__PURE__ */ jsx("span", {
343
- className: "text-[10px] text-amber-600 bg-amber-500/10 px-1.5 py-0.5 rounded-full flex-shrink-0",
344
- children: issueLabel(issue)
345
- }),
346
- isOwner && settingsHref && /* @__PURE__ */ jsx(LinkComponent, {
347
- target: {
348
- kind: "external",
349
- href: settingsHref
350
- },
351
- onClick: (e) => e.stopPropagation(),
352
- className: "p-0.5 rounded hover:theme-hover theme-muted-text hover:text-blue-600 transition-colors",
353
- title: "Manage provider",
354
- children: /* @__PURE__ */ jsx(FaCog, { className: "h-3 w-3" })
355
- })
356
- ]
357
- })
358
- }),
359
- issue && /* @__PURE__ */ jsxs("div", {
360
- className: "flex items-start gap-2 px-4 py-1.5 bg-amber-500/5",
361
- children: [/* @__PURE__ */ jsx(FaExclamationTriangle, { className: "h-3 w-3 text-amber-500 flex-shrink-0 mt-0.5" }), /* @__PURE__ */ jsx("span", {
362
- className: "text-xs text-amber-600 line-clamp-2",
363
- children: issueDetail(issue)
364
- })]
365
- }),
366
- providerModels.map((model) => {
367
- const modelName = getModelLabel(model);
368
- const isSelected = model.id === selectedProvider;
369
- return /* @__PURE__ */ jsxs("button", {
370
- type: "button",
371
- "aria-disabled": !!issue,
372
- title: issue ? issueSummary(issue) : void 0,
373
- onClick: () => {
374
- if (!issue) handleSelect(model.id);
375
- },
376
- className: cx("w-full flex items-center gap-3 px-4 py-2 text-left", issue ? "opacity-40 cursor-not-allowed" : "cursor-pointer hover:bg-blue-500/5 transition-colors", isSelected && "bg-blue-500/10"),
377
- children: [/* @__PURE__ */ jsx("span", {
378
- title: modelName,
379
- className: cx("flex-1 min-w-0 text-sm truncate", isSelected ? "text-blue-600 font-medium" : ""),
380
- style: !isSelected ? { color: "var(--theme-panel, #1f2937)" } : void 0,
381
- children: modelName
382
- }), isSelected && /* @__PURE__ */ jsx(FaCheck, { className: "h-3.5 w-3.5 text-blue-600 flex-shrink-0" })]
383
- }, model.id);
384
- })
385
- ] }, providerKey);
386
- })
387
- ]
388
- })]
389
- }), document.body)]
147
+ issue && /* @__PURE__ */ jsx(FaExclamationTriangle, { className: "h-3.5 w-3.5 shrink-0 text-amber-500" })
148
+ ] }) : /* @__PURE__ */ jsx("span", {
149
+ className: clsx("truncate", targetSessionStatus === "connecting" && "animate-pulse"),
150
+ children: targetSessionStatus === "connecting" ? t.connecting : t.select
151
+ }), /* @__PURE__ */ jsx(FaChevronDown, { className: "h-3 w-3 shrink-0" })]
152
+ })
390
153
  });
391
154
  }
392
155
  //#endregion
@@ -6,9 +6,9 @@ import { formatFileSize } from "../utils.js";
6
6
  import Markdown from "../ui/Markdown.js";
7
7
  import { fencePaste } from "../core/pasteLanguage.js";
8
8
  import { splitPastes } from "../core/pastes.js";
9
- import cx from "classnames";
10
- import { Fragment } from "react";
11
- import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
+ import { clsx } from "clsx";
10
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
+ import { Fragment as Fragment$1 } from "react";
12
12
  //#region src/ai/components/PastedText.tsx
13
13
  /** A paste sent, or about to be sent, as its saved file. */
14
14
  function PasteFileCard({ id, lines, bytes, saving = false, error, onRemove }) {
@@ -16,7 +16,7 @@ function PasteFileCard({ id, lines, bytes, saving = false, error, onRemove }) {
16
16
  const detail = error ?? (saving ? t.saving : `${t.lines(lines)} · ${formatFileSize(bytes)}`);
17
17
  return /* @__PURE__ */ jsxs("div", {
18
18
  "data-testid": "paste-file-card",
19
- className: cx("inline-flex max-w-full items-center gap-2.5 rounded-xl px-3 py-2 align-top whitespace-normal", "bg-(--theme-panel-bg)", error ? "border border-red-500" : "chat-hairline"),
19
+ className: clsx("inline-flex max-w-full items-center gap-2.5 rounded-xl px-3 py-2 align-top whitespace-normal", "bg-(--theme-panel-bg)", error ? "border border-red-500" : "chat-hairline"),
20
20
  children: [
21
21
  /* @__PURE__ */ jsx(FiFile, { className: "h-5 w-5 flex-shrink-0 theme-muted-text" }),
22
22
  /* @__PURE__ */ jsxs("div", {
@@ -25,7 +25,7 @@ function PasteFileCard({ id, lines, bytes, saving = false, error, onRemove }) {
25
25
  className: "truncate text-sm font-medium theme-text",
26
26
  children: id ? `paste-${id}.txt` : t.title
27
27
  }), /* @__PURE__ */ jsx("div", {
28
- className: cx("truncate text-xs", error ? "text-red-500" : "theme-muted-text"),
28
+ className: clsx("truncate text-xs", error ? "text-red-500" : "theme-muted-text"),
29
29
  children: detail
30
30
  })]
31
31
  }),
@@ -45,23 +45,23 @@ function PasteFileCard({ id, lines, bytes, saving = false, error, onRemove }) {
45
45
  * it reads as, one sent as its file as a card. The blank lines the composer puts around a
46
46
  * paste would stack on the block's own spacing, so they are dropped. */
47
47
  function UserMessageText({ content, pastes }) {
48
- if (!pastes?.length) return /* @__PURE__ */ jsx(Fragment$1, { children: content });
48
+ if (!pastes?.length) return /* @__PURE__ */ jsx(Fragment, { children: content });
49
49
  const segments = splitPastes(content, pastes);
50
50
  const shows = (seg) => typeof seg !== "string" || seg.trim() !== "";
51
- const blockSpacing = (i) => cx(segments.slice(0, i).some(shows) ? "mt-2.5" : "mt-0", segments.slice(i + 1).some(shows) ? "mb-2.5" : "mb-0");
52
- return /* @__PURE__ */ jsx(Fragment$1, { children: segments.map((segment, i) => {
51
+ const blockSpacing = (i) => clsx(segments.slice(0, i).some(shows) ? "mt-2.5" : "mt-0", segments.slice(i + 1).some(shows) ? "mb-2.5" : "mb-0");
52
+ return /* @__PURE__ */ jsx(Fragment, { children: segments.map((segment, i) => {
53
53
  if (typeof segment === "string") {
54
54
  let text = segment;
55
55
  if (i > 0) text = text.replace(/^\n+/, "");
56
56
  if (i < segments.length - 1) text = text.replace(/\n+$/, "");
57
- return text ? /* @__PURE__ */ jsx(Fragment, { children: text }, i) : null;
57
+ return text ? /* @__PURE__ */ jsx(Fragment$1, { children: text }, i) : null;
58
58
  }
59
59
  if (segment.inline && segment.text !== void 0) return /* @__PURE__ */ jsx("span", {
60
- className: cx("chat-paste-code block", blockSpacing(i)),
60
+ className: clsx("chat-paste-code block", blockSpacing(i)),
61
61
  children: /* @__PURE__ */ jsx(Markdown, { children: fencePaste(segment.text) })
62
62
  }, i);
63
63
  return /* @__PURE__ */ jsx("span", {
64
- className: cx("flex [&>*]:flex-1", blockSpacing(i)),
64
+ className: clsx("flex [&>*]:flex-1", blockSpacing(i)),
65
65
  children: /* @__PURE__ */ jsx(PasteFileCard, {
66
66
  id: segment.id,
67
67
  lines: segment.lines,
@@ -1 +1,3 @@
1
+ /** Why the selected model can't answer, with a way to fix it for the
2
+ * provider's owner, who alone can change its key or endpoint. */
1
3
  export default function ProviderIssueBanner(): import("react").JSX.Element | null;
@@ -1,29 +1,47 @@
1
1
  import { FaExclamationTriangle } from "../../icons/fa/FaExclamationTriangle.js";
2
+ import { IoSettingsOutline } from "../../icons/io5/IoSettingsOutline.js";
2
3
  import { useChatConfig } from "../core/config.js";
3
- import { providerIssueFor } from "../core/providerIssues.js";
4
+ import { issueDetail, providerIssueFor } from "../core/providerIssues.js";
4
5
  import { useChat } from "../core/ChatProvider.js";
6
+ import { getProviderKeyFromModelId } from "./ModelSelector.js";
5
7
  import { jsx, jsxs } from "react/jsx-runtime";
6
8
  //#region src/ai/components/ProviderIssueBanner.tsx
9
+ /** Why the selected model can't answer, with a way to fix it for the
10
+ * provider's owner, who alone can change its key or endpoint. */
7
11
  function ProviderIssueBanner() {
8
- const { strings } = useChatConfig();
9
- const { models, providerIssues, selectedProvider } = useChat();
12
+ const { strings, extraLinks, LinkComponent } = useChatConfig();
13
+ const { models, providers, providerIssues, selectedProvider, currentUser } = useChat();
10
14
  const selectedModel = models.find((m) => m.id === selectedProvider);
11
15
  const issue = providerIssueFor(providerIssues, selectedModel);
12
- if (!issue) return null;
13
- const title = issue.status === "unauthorized" ? strings.providerIssue.bannerKeyRejected(issue.provider) : strings.providerIssue.bannerUnreachable(issue.provider);
14
- const detail = issue.message || (issue.status === "unauthorized" ? strings.providerIssue.keyRejectedHint : strings.providerIssue.unreachableHint);
16
+ if (!selectedModel || !issue) return null;
17
+ const t = strings.providerIssue;
18
+ const title = issue.status === "unauthorized" ? t.bannerKeyRejected(issue.provider) : t.bannerUnreachable(issue.provider);
19
+ const providerKey = getProviderKeyFromModelId(selectedModel.id);
20
+ const provider = providers.find((p) => `${p.user}:${p.name}` === providerKey);
21
+ const settingsHref = provider?.user === currentUser.username ? extraLinks.providerSettings?.(provider.user, provider.name) : void 0;
15
22
  return /* @__PURE__ */ jsxs("div", {
16
23
  className: "mx-4 mt-2 flex items-start gap-3 p-3 rounded-lg bg-amber-500/10 border border-amber-500/20",
17
- children: [/* @__PURE__ */ jsx(FaExclamationTriangle, { className: "w-4 h-4 text-amber-500 shrink-0 mt-0.5" }), /* @__PURE__ */ jsxs("div", {
18
- className: "flex-1 min-w-0",
19
- children: [/* @__PURE__ */ jsx("p", {
20
- className: "text-sm font-medium text-amber-700",
21
- children: title
22
- }), /* @__PURE__ */ jsx("p", {
23
- className: "text-xs text-amber-600/80 mt-0.5",
24
- children: detail
25
- })]
26
- })]
24
+ children: [
25
+ /* @__PURE__ */ jsx(FaExclamationTriangle, { className: "w-4 h-4 text-amber-500 shrink-0 mt-0.5" }),
26
+ /* @__PURE__ */ jsxs("div", {
27
+ className: "flex-1 min-w-0",
28
+ children: [/* @__PURE__ */ jsx("p", {
29
+ className: "text-sm font-medium text-amber-700",
30
+ children: title
31
+ }), /* @__PURE__ */ jsx("p", {
32
+ className: "text-xs text-amber-600/80 mt-0.5",
33
+ children: issueDetail(t, issue)
34
+ })]
35
+ }),
36
+ settingsHref && /* @__PURE__ */ jsxs(LinkComponent, {
37
+ target: {
38
+ kind: "external",
39
+ href: settingsHref
40
+ },
41
+ className: "flex shrink-0 items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-amber-700 transition-colors hover:bg-amber-500/10",
42
+ children: [/* @__PURE__ */ jsx(IoSettingsOutline, { className: "h-3.5 w-3.5" }), strings.modelPicker.manageProvider]
43
+ })
44
+ ]
27
45
  });
28
46
  }
29
47
  //#endregion
@@ -1,5 +1,5 @@
1
1
  import { LuChevronRight } from "../../icons/lu/LuChevronRight.js";
2
- import cx from "classnames";
2
+ import { clsx } from "clsx";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
  //#region src/ai/components/Reasoning.tsx
5
5
  /** The line a turn's reasoning folds under. It opens the reasoning in place,
@@ -12,16 +12,16 @@ function ReasoningToggle({ open, onToggle, shimmer = false, children }) {
12
12
  "aria-expanded": open,
13
13
  className: "group/reasoning -ml-1 flex items-center gap-x-1 rounded-md px-1 py-0.5 text-sm theme-muted-text transition-colors hover:theme-text",
14
14
  children: [/* @__PURE__ */ jsx("span", {
15
- className: cx(shimmer && "animate-shimmer text-shimmer"),
15
+ className: clsx(shimmer && "animate-shimmer text-shimmer"),
16
16
  children
17
- }), /* @__PURE__ */ jsx(LuChevronRight, { className: cx("h-3.5 w-3.5 transition-transform duration-200 motion-reduce:transition-none", open && "rotate-90") })]
17
+ }), /* @__PURE__ */ jsx(LuChevronRight, { className: clsx("h-3.5 w-3.5 transition-transform duration-200 motion-reduce:transition-none", open && "rotate-90") })]
18
18
  });
19
19
  }
20
20
  /** The reasoning itself: smaller and quieter than the answer, hung off a
21
21
  * hairline of the accent so it reads as the assistant's aside. */
22
22
  function ReasoningBody({ className, children }) {
23
23
  return /* @__PURE__ */ jsx("div", {
24
- className: cx("chat-reasoning-body ml-1 mt-1.5 mb-3 pl-4 text-sm leading-relaxed theme-muted-text", className),
24
+ className: clsx("chat-reasoning-body ml-1 mt-1.5 mb-3 pl-4 text-sm leading-relaxed theme-muted-text", className),
25
25
  children
26
26
  });
27
27
  }