@parallelworks/ui 0.18.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -6
- package/dist/ai/adapter/memoryStore.d.ts +8 -0
- package/dist/ai/adapter/memoryStore.js +152 -0
- package/dist/ai/adapter/mock/index.d.ts +2 -0
- package/dist/ai/adapter/mock/index.js +3 -0
- package/dist/ai/adapter/mock/mockAdapter.d.ts +4 -0
- package/dist/ai/adapter/mock/mockAdapter.js +81 -0
- package/dist/ai/adapter/openai/createOpenAIStream.d.ts +9 -0
- package/dist/ai/adapter/openai/createOpenAIStream.js +33 -0
- package/dist/ai/adapter/openai/errors.d.ts +3 -0
- package/dist/ai/adapter/openai/errors.js +37 -0
- package/dist/ai/adapter/openai/index.d.ts +6 -0
- package/dist/ai/adapter/openai/index.js +6 -0
- package/dist/ai/adapter/openai/openaiAdapter.d.ts +9 -0
- package/dist/ai/adapter/openai/openaiAdapter.js +54 -0
- package/dist/ai/adapter/openai/responses.d.ts +4 -0
- package/dist/ai/adapter/openai/responses.js +108 -0
- package/dist/ai/adapter/openai/sse.d.ts +24 -0
- package/dist/ai/adapter/openai/sse.js +112 -0
- package/dist/ai/adapter/openai/wire.d.ts +40 -0
- package/dist/ai/adapter/types.d.ts +131 -0
- package/dist/ai/adapter/types.js +11 -0
- package/dist/ai/agent/classifier.d.ts +2 -0
- package/dist/ai/agent/classifier.js +12 -0
- package/dist/ai/agent/diff.d.ts +35 -0
- package/dist/ai/agent/diff.js +223 -0
- package/dist/ai/agent/diffHighlight.d.ts +6 -0
- package/dist/ai/agent/diffHighlight.js +101 -0
- package/dist/ai/agent/index.d.ts +6 -0
- package/dist/ai/agent/permissions.d.ts +11 -0
- package/dist/ai/agent/permissions.js +29 -0
- package/dist/ai/agent/toolfmt.d.ts +18 -0
- package/dist/ai/agent/toolfmt.js +383 -0
- package/dist/ai/agent/transcript.d.ts +38 -0
- package/dist/ai/agent/transcript.js +127 -0
- package/dist/ai/ai.css +325 -0
- package/dist/ai/components/AllocationSelector.d.ts +4 -0
- package/dist/ai/components/AllocationSelector.js +56 -0
- package/dist/ai/components/AttachmentManager.d.ts +1 -0
- package/dist/ai/components/AttachmentManager.js +144 -0
- package/dist/ai/components/AttachmentUpload.d.ts +24 -0
- package/dist/ai/components/AttachmentUpload.js +179 -0
- package/dist/ai/components/BlockedGroupBanner.d.ts +1 -0
- package/dist/ai/components/BlockedGroupBanner.js +29 -0
- package/dist/ai/components/BranchNavigator.d.ts +8 -0
- package/dist/ai/components/BranchNavigator.js +57 -0
- package/dist/ai/components/ChatEmptyState.d.ts +3 -0
- package/dist/ai/components/ChatEmptyState.js +171 -0
- package/dist/ai/components/ChatInput.d.ts +45 -0
- package/dist/ai/components/ChatInput.js +265 -0
- package/dist/ai/components/ChatLayout.d.ts +5 -0
- package/dist/ai/components/ChatLayout.js +15 -0
- package/dist/ai/components/ChatMessage.d.ts +24 -0
- package/dist/ai/components/ChatMessage.js +338 -0
- package/dist/ai/components/ChatMessageList.d.ts +35 -0
- package/dist/ai/components/ChatMessageList.js +301 -0
- package/dist/ai/components/ChatSidebar.d.ts +5 -0
- package/dist/ai/components/ChatSidebar.js +345 -0
- package/dist/ai/components/ChatThread.d.ts +3 -0
- package/dist/ai/components/ChatThread.js +198 -0
- package/dist/ai/components/ComposerChrome.d.ts +18 -0
- package/dist/ai/components/ComposerChrome.js +50 -0
- package/dist/ai/components/ComposerFrame.d.ts +16 -0
- package/dist/ai/components/ComposerFrame.js +33 -0
- package/dist/ai/components/DragOverlay.d.ts +5 -0
- package/dist/ai/components/DragOverlay.js +20 -0
- package/dist/ai/components/ModelSelector.d.ts +6 -0
- package/dist/ai/components/ModelSelector.js +343 -0
- package/dist/ai/components/ShareDialog.d.ts +11 -0
- package/dist/ai/components/ShareDialog.js +260 -0
- package/dist/ai/components/SlashMenu.d.ts +28 -0
- package/dist/ai/components/SlashMenu.js +91 -0
- package/dist/ai/components/agent/ActivityLine.d.ts +21 -0
- package/dist/ai/components/agent/ActivityLine.js +151 -0
- package/dist/ai/components/agent/AgentMessageParts.d.ts +54 -0
- package/dist/ai/components/agent/AgentMessageParts.js +721 -0
- package/dist/ai/components/agent/PermissionPicker.d.ts +11 -0
- package/dist/ai/components/agent/PermissionPicker.js +38 -0
- package/dist/ai/components/attachmentKeys.d.ts +4 -0
- package/dist/ai/components/attachmentKeys.js +10 -0
- package/dist/ai/core/ChatProvider.d.ts +66 -0
- package/dist/ai/core/ChatProvider.js +848 -0
- package/dist/ai/core/KeyboardShortcutsProvider.d.ts +5 -0
- package/dist/ai/core/KeyboardShortcutsProvider.js +9 -0
- package/dist/ai/core/chatReducer.d.ts +114 -0
- package/dist/ai/core/chatReducer.js +223 -0
- package/dist/ai/core/config.d.ts +47 -0
- package/dist/ai/core/config.js +45 -0
- package/dist/ai/core/events.d.ts +1 -0
- package/dist/ai/core/events.js +4 -0
- package/dist/ai/core/exportConversation.d.ts +4 -0
- package/dist/ai/core/exportConversation.js +42 -0
- package/dist/ai/core/greeting.d.ts +3 -0
- package/dist/ai/core/greeting.js +38 -0
- package/dist/ai/core/parts.d.ts +11 -0
- package/dist/ai/core/parts.js +84 -0
- package/dist/ai/core/useDragDrop.d.ts +14 -0
- package/dist/ai/core/useDragDrop.js +36 -0
- package/dist/ai/core/useKeyboardShortcuts.d.ts +12 -0
- package/dist/ai/core/useKeyboardShortcuts.js +77 -0
- package/dist/ai/core/useSSEStream.d.ts +34 -0
- package/dist/ai/core/useSSEStream.js +98 -0
- package/dist/ai/index.d.ts +40 -0
- package/dist/ai/index.js +44 -0
- package/dist/ai/strings.d.ts +167 -0
- package/dist/ai/strings.js +175 -0
- package/dist/ai/types.d.ts +236 -0
- package/dist/ai/ui/Avatar.d.ts +8 -0
- package/dist/ai/ui/Avatar.js +35 -0
- package/dist/ai/ui/Dropdown.d.ts +31 -0
- package/dist/ai/ui/Dropdown.js +110 -0
- package/dist/ai/ui/Markdown.d.ts +8 -0
- package/dist/ai/ui/Markdown.js +79 -0
- package/dist/ai/ui/Notice.d.ts +21 -0
- package/dist/ai/ui/Notice.js +77 -0
- package/dist/ai/ui/buttons.d.ts +1 -0
- package/dist/ai/ui/codeBreaks.d.ts +16 -0
- package/dist/ai/ui/codeBreaks.js +36 -0
- package/dist/ai/ui/prose.d.ts +1 -0
- package/dist/ai/ui/prose.js +13 -0
- package/dist/ai/ui/sidebar.d.ts +81 -0
- package/dist/ai/ui/sidebar.js +148 -0
- package/dist/ai/utils.d.ts +4 -0
- package/dist/ai/utils.js +23 -0
- package/dist/assets/{editor.worker-CuBRIwi-.js → editor.worker-BmHUIVG4.js} +194 -163
- package/dist/assets/{json.worker-8ytS02vz.js → json.worker-D8bJ92nw.js} +384 -338
- package/dist/assets/{yaml.worker-DJC-VUcv.js → yaml.worker-C4gyGAko.js} +15657 -13823
- package/dist/components/AddMenu.js +46 -0
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/Avatar.js +46 -0
- package/dist/{chunks/BareModal-BtRiQezg.js → components/BareModal.js} +1 -1
- package/dist/components/Breadcrumbs.d.ts +1 -1
- package/dist/components/Breadcrumbs.js +83 -0
- package/dist/components/Button.js +83 -0
- package/dist/components/Callout.js +23 -0
- package/dist/components/Card.js +31 -0
- package/dist/components/CollapsiblePanel.js +77 -0
- package/dist/components/ConfirmModal.js +74 -0
- package/dist/components/CopyToClipboard.js +81 -0
- package/dist/{chunks/CreateModal-BY6DLmur.js → components/CreateModal.js} +10 -189
- package/dist/components/DescriptionList.js +42 -0
- package/dist/components/Dropdown.d.ts +1 -1
- package/dist/components/Dropdown.js +349 -0
- package/dist/components/EmptyState.d.ts +1 -1
- package/dist/components/EmptyState.js +27 -0
- package/dist/components/Filter.js +32 -0
- package/dist/components/FilterPill.js +13 -0
- package/dist/components/IconButton.js +34 -0
- package/dist/components/Indicator.d.ts +2 -2
- package/dist/{chunks/Indicator-hBSIxOPK.js → components/Indicator.js} +1 -1
- package/dist/components/Input.d.ts +1 -1
- package/dist/components/Input.js +88 -0
- package/dist/components/LetterBadge.d.ts +1 -1
- package/dist/components/LetterBadge.js +13 -0
- package/dist/components/Listbox.js +123 -0
- package/dist/{chunks/Loader-BXoXTZGc.js → components/Loader.js} +1 -1
- package/dist/components/Markdown.js +15 -0
- package/dist/components/MarkdownImpl.d.ts +1 -1
- package/dist/components/MultiSelect.js +171 -0
- package/dist/components/Overview.js +39 -0
- package/dist/components/PageHeader.js +194 -0
- package/dist/components/Pagination.js +35 -0
- package/dist/components/Provider.d.ts +16 -65
- package/dist/{chunks/Provider-Ghua9PkN.js → components/Provider.js} +54 -20
- package/dist/{chunks/RequiredMark-DfLdYbYZ.js → components/RequiredMark.js} +1 -1
- package/dist/components/SectionHeader.js +11 -0
- package/dist/components/SettingModeOption.js +30 -0
- package/dist/components/SettingRow.d.ts +1 -1
- package/dist/components/SettingRow.js +35 -0
- package/dist/components/SettingsCard.js +31 -0
- package/dist/components/SettingsGroup.d.ts +1 -1
- package/dist/components/SettingsGroup.js +17 -0
- package/dist/components/Sort.js +42 -0
- package/dist/components/StatusBadge.js +63 -0
- package/dist/components/SwitchToggle.js +59 -0
- package/dist/{chunks/SwitchToggleSmall-FcsaYSTs.js → components/SwitchToggleSmall.js} +1 -1
- package/dist/{chunks/Table-4OHjdKdO.js → components/Table.js} +7 -5
- package/dist/components/Toggle.js +20 -0
- package/dist/{chunks/Tooltip-Crxo_eU_.js → components/Tooltip.js} +6 -4
- package/dist/{chunks/StatusBadge-C1Blx_cW.js → components/dropdownUtils.js} +2 -63
- package/dist/components/focus.d.ts +2 -0
- package/dist/components/focus.js +7 -0
- package/dist/components/ghostButton.js +7 -0
- package/dist/components/inputClasses.js +4 -0
- package/dist/components/keys.d.ts +12 -0
- package/dist/components/keys.js +27 -0
- package/dist/components/relativeTime.js +32 -0
- package/dist/{chunks/useCssIsDark-DHr05dG2.js → components/useCssIsDark.js} +1 -1
- package/dist/duration.d.ts +3 -0
- package/dist/duration.js +17 -0
- package/dist/editor/Editor.js +13 -0
- package/dist/editor/EditorField.js +53 -0
- package/dist/editor/FormCodePanel.js +173 -0
- package/dist/editor/Monaco.d.ts +1 -20
- package/dist/editor/Monaco.js +112 -0
- package/dist/editor/index.d.ts +2 -1
- package/dist/editor/index.js +7 -2
- package/dist/editor/themes.js +91 -0
- package/dist/editor/workers.js +29 -0
- package/dist/editor/yaml.d.ts +5 -0
- package/dist/editor/yaml.js +15 -0
- package/dist/engine.d.ts +98 -0
- package/dist/file-explorer/FileExplorer.d.ts +11 -2
- package/dist/file-explorer/FileExplorer.js +1402 -0
- package/dist/file-explorer/FileExplorerContext.d.ts +1 -1
- package/dist/file-explorer/FileExplorerContext.js +370 -0
- package/dist/file-explorer/FilePreview/CodePreviewEditor.d.ts +1 -1
- package/dist/{chunks/CodePreviewEditor-4lx3kE-w.js → file-explorer/FilePreview/CodePreviewEditor.js} +3 -2
- package/dist/file-explorer/FilePreview/Inline.js +45 -0
- package/dist/file-explorer/FilePreview/StreamedBadge.js +16 -0
- package/dist/file-explorer/FilePreview/Viewer.js +526 -0
- package/dist/file-explorer/FilePreview/index.js +266 -0
- package/dist/file-explorer/FilePreview/parse.js +63 -0
- package/dist/file-explorer/FilePreview/previewCache.js +98 -0
- package/dist/file-explorer/FilePreview/previewType.d.ts +1 -1
- package/dist/file-explorer/FilePreview/previewType.js +151 -0
- package/dist/file-explorer/FilePreview/readCappedBytes.js +196 -0
- package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +95 -0
- package/dist/file-explorer/FilePreview/useObjectText.js +109 -0
- package/dist/file-explorer/FilePreview/usePresignedUrl.js +73 -0
- package/dist/file-explorer/FileRow.d.ts +1 -1
- package/dist/file-explorer/FileRow.js +165 -0
- package/dist/file-explorer/FileRowsWindow.js +92 -0
- package/dist/file-explorer/FileTable.js +172 -0
- package/dist/file-explorer/FileTableHeaders.js +49 -0
- package/dist/file-explorer/LoadMoreRow.js +46 -0
- package/dist/file-explorer/TreeView.js +376 -0
- package/dist/file-explorer/helpers.d.ts +4 -4
- package/dist/file-explorer/helpers.js +537 -0
- package/dist/file-explorer/index.d.ts +3 -3
- package/dist/file-explorer/index.js +7 -5705
- package/dist/file-explorer/lib/batch.js +39 -0
- package/dist/file-explorer/lib/errors.js +9 -0
- package/dist/file-explorer/lib/paging.js +124 -0
- package/dist/file-explorer/lib/types.d.ts +6 -1
- package/dist/file-explorer/lib/utils.d.ts +1 -3
- package/dist/file-explorer/lib/utils.js +322 -0
- package/dist/file-explorer/toasts.js +400 -0
- package/dist/file-explorer/treeRows.js +73 -0
- package/dist/file-explorer/useNearEnd.d.ts +1 -1
- package/dist/file-explorer/useNearEnd.js +27 -0
- package/dist/form/FieldWrapper.d.ts +2 -2
- package/dist/form/FieldWrapper.js +54 -0
- package/dist/form/Fields/BooleanField.js +51 -0
- package/dist/form/Fields/CheckboxGroupField.js +72 -0
- package/dist/form/Fields/ColorField.js +33 -0
- package/dist/form/Fields/DropdownField.js +49 -0
- package/dist/form/Fields/DurationField.js +130 -0
- package/dist/form/Fields/LabelField.js +19 -0
- package/dist/form/Fields/MultiDropdownField.js +40 -0
- package/dist/form/Fields/NumberField.js +51 -0
- package/dist/form/Fields/ObjectField.js +54 -0
- package/dist/form/Fields/RadioField.js +49 -0
- package/dist/form/Fields/RangeField.js +58 -0
- package/dist/form/Fields/ResourceOptionIcon.d.ts +1 -1
- package/dist/form/Fields/ResourceOptionIcon.js +21 -0
- package/dist/form/Fields/StepField.js +6 -0
- package/dist/form/Fields/StringField.js +37 -0
- package/dist/form/Fields/TextAreaField.js +31 -0
- package/dist/form/Fields/durationValidity.js +11 -0
- package/dist/form/Form.d.ts +4 -21
- package/dist/form/Form.js +643 -0
- package/dist/form/SpecialTypes/DropdownWrapper.js +30 -0
- package/dist/form/SpecialTypes/FormikCustomDropdown.js +122 -0
- package/dist/form/SpecialTypes/FormikCustomInput.js +111 -0
- package/dist/form/SpecialTypes/FormikMultiSelectDropdown.js +31 -0
- package/dist/form/SpecialTypes/FormikNumericInput.js +60 -0
- package/dist/form/Wizard/WizardContainer.js +138 -0
- package/dist/form/Wizard/WizardNavigation.js +92 -0
- package/dist/form/Wizard/WizardStepContent.js +35 -0
- package/dist/form/Wizard/WizardStepIndicator.js +108 -0
- package/dist/form/Wizard/useWizardState.js +118 -0
- package/dist/form/Wizard/utils.js +34 -0
- package/dist/form/fieldContext.js +26 -0
- package/dist/form/fieldRegistry.js +50 -0
- package/dist/form/index.d.ts +11 -12
- package/dist/form/index.js +18 -2445
- package/dist/form/lib.d.ts +6 -1
- package/dist/{chunks/FieldWrapper-Bef01EWO.js → form/lib.js} +5 -104
- package/dist/form/types/fieldComponentTypes.d.ts +3 -2
- package/dist/form/types/fieldTypes.d.ts +6 -20
- package/dist/form/useParsedOpts.d.ts +1 -1
- package/dist/form/useParsedOpts.js +62 -0
- package/dist/form/usePrevious.js +11 -0
- package/dist/form/utils/checkShowIfObject.js +14 -0
- package/dist/form/utils/{slurmDuration.d.ts → duration.d.ts} +2 -2
- package/dist/form/utils/duration.js +69 -0
- package/dist/form/utils/getParentValue.js +23 -0
- package/dist/form/utils/getPlaceholder.js +9 -0
- package/dist/form/utils/getValueUsingPath.js +23 -0
- package/dist/form/utils/secondaryField.js +22 -0
- package/dist/graph/AnnotationBanner.js +141 -0
- package/dist/graph/Collapse.js +41 -0
- package/dist/{workflow/index.js → graph/DependencyGraph.js} +76 -1163
- package/dist/graph/JobSummary.js +104 -0
- package/dist/{workflow → graph}/MatrixGroup.d.ts +2 -2
- package/dist/graph/MatrixGroup.js +107 -0
- package/dist/graph/Reveal.js +57 -0
- package/dist/graph/TreeView.js +711 -0
- package/dist/{workflow → graph}/index.d.ts +1 -1
- package/dist/graph/index.js +2 -0
- package/dist/{workflow → graph}/types.d.ts +3 -3
- package/dist/graph/types.js +6 -0
- package/dist/icons.d.ts +20 -21
- package/dist/index.d.ts +38 -40
- package/dist/index.js +47 -599
- package/dist/list/ActionToolbar.js +184 -0
- package/dist/list/HoverCard.js +73 -0
- package/dist/list/ListPage.js +145 -0
- package/dist/list/ListPager.js +59 -0
- package/dist/list/ListTable.d.ts +1 -1
- package/dist/list/ListTable.js +163 -0
- package/dist/list/ListViewControls.d.ts +2 -2
- package/dist/list/ListViewControls.js +372 -0
- package/dist/list/RowContextMenu.js +350 -0
- package/dist/list/UserHoverCard.js +117 -0
- package/dist/list/index.d.ts +9 -9
- package/dist/list/index.js +11 -2
- package/dist/list/listSearch.js +137 -0
- package/dist/list/listView.js +210 -0
- package/dist/list/textEntry.js +10 -0
- package/dist/logviewer/LogViewer.d.ts +1 -1
- package/dist/logviewer/LogViewer.js +750 -0
- package/dist/logviewer/index.d.ts +1 -0
- package/dist/logviewer/index.js +3 -742
- package/dist/logviewer/segments.d.ts +9 -0
- package/dist/logviewer/segments.js +80 -0
- package/dist/styles/fonts.css +4 -4
- package/dist/styles/theme.css +6 -4
- package/dist/styles.css +1 -1
- package/dist/theme/index.js +2 -2
- package/package.json +38 -14
- package/LICENSE +0 -202
- package/dist/chunks/Monaco-C1POOdVv.js +0 -1046
- package/dist/chunks/PageHeader-l49p1nM9.js +0 -690
- package/dist/chunks/SwitchToggle-CHPP8uZQ.js +0 -755
- package/dist/chunks/editor-9_uVtUyy.js +0 -347
- package/dist/chunks/list-qrojZZNv.js +0 -1411
- package/dist/editor/usesGitlab.d.ts +0 -97
- package/dist/form/Fields/BucketReadOnlyField.d.ts +0 -7
- package/dist/form/types/workflow-schema.d.ts +0 -1145
- package/dist/form/utils/pwResolver.d.ts +0 -18
- package/dist/{chunks/MarkdownImpl-Bq4QhrtA.js → components/MarkdownImpl.js} +1 -1
- /package/dist/{workflow → graph}/AnnotationBanner.d.ts +0 -0
- /package/dist/{workflow → graph}/Collapse.d.ts +0 -0
- /package/dist/{workflow → graph}/DependencyGraph.d.ts +0 -0
- /package/dist/{workflow → graph}/JobSummary.d.ts +0 -0
- /package/dist/{workflow → graph}/Reveal.d.ts +0 -0
- /package/dist/{workflow → graph}/TreeView.d.ts +0 -0
package/README.md
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
# @parallelworks/ui
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
React components built on one theme contract. Each surface is its own subpath,
|
|
4
|
+
so an application ships only what it imports:
|
|
5
|
+
|
|
6
|
+
| Subpath | Contents |
|
|
7
|
+
| --- | --- |
|
|
8
|
+
| `.` | theme contract, primitives (buttons, inputs, tables, tooltips, modals, dropdowns, settings layouts, page header), `UIProvider` |
|
|
9
|
+
| `./theme` | `deriveTheme`, presets, `applyTheme` |
|
|
10
|
+
| `./icons` | the icon barrel |
|
|
11
|
+
| `./list` | list pages, tables, row menus and list controls |
|
|
12
|
+
| `./form` | `DynamicForm` and its field registry |
|
|
13
|
+
| `./graph` | job dependency graph and tree view |
|
|
14
|
+
| `./editor` | Monaco editor (`monaco-editor` and `monaco-yaml` are optional peers) |
|
|
15
|
+
| `./logviewer` | log viewer with ANSI color and search |
|
|
16
|
+
| `./file-explorer` | object storage explorer over a host-supplied provider |
|
|
17
|
+
| `./ai`, `./ai/openai`, `./ai/mock` | chat interface and adapters |
|
|
18
|
+
| `./theme.css`, `./base.css`, `./fonts.css`, `./styles.css`, `./logviewer.css`, `./ai.css` | stylesheets |
|
|
19
|
+
|
|
20
|
+
Hosts bind notifications, strings, navigation, data hooks and the workflow
|
|
21
|
+
engine through `UIProvider`.
|
|
6
22
|
|
|
7
23
|
## Theme contract
|
|
8
24
|
|
|
@@ -35,9 +51,9 @@ import '@parallelworks/ui/styles.css'
|
|
|
35
51
|
```
|
|
36
52
|
|
|
37
53
|
The prebuilt stylesheet nests all of its rules in the `pw-ui` cascade layer,
|
|
38
|
-
so
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
so its precedence does not depend on where it loads. It includes Tailwind's
|
|
55
|
+
preflight reset; a host with its own Tailwind build should use the `@source`
|
|
56
|
+
route instead to avoid a double preflight.
|
|
41
57
|
|
|
42
58
|
## Icons
|
|
43
59
|
|
|
@@ -47,3 +63,67 @@ import { AddIcon, LoaderIcon, TrashIcon } from '@parallelworks/ui/icons'
|
|
|
47
63
|
|
|
48
64
|
The barrel re-exports a curated, stable set of icon names over `react-icons`
|
|
49
65
|
so applications never depend on a specific icon pack directly.
|
|
66
|
+
|
|
67
|
+
## Workflow engine
|
|
68
|
+
|
|
69
|
+
The workflow surfaces (`/form`'s `DynamicForm`, `/graph`'s `DependencyGraph`,
|
|
70
|
+
the editor's workflow completions, and the log viewer's workflow commands) do
|
|
71
|
+
not implement a workflow format themselves. They call a `WorkflowEngine` that
|
|
72
|
+
the host passes once:
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import { UIProvider } from '@parallelworks/ui'
|
|
76
|
+
|
|
77
|
+
<UIProvider engine={engine}>{app}</UIProvider>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The engine converts input definitions into the form's field tree, resolves
|
|
81
|
+
`${{ }}` expressions, and supplies the job graph's dependencies, matrix
|
|
82
|
+
grouping, step labels and log paths. The form and graph throw without one; the
|
|
83
|
+
editor and log viewer fall back to plain behavior.
|
|
84
|
+
|
|
85
|
+
## AI chat
|
|
86
|
+
|
|
87
|
+
`@parallelworks/ui/ai` is a chat interface (thread, composer, streaming,
|
|
88
|
+
sidebar, model selector, attachments, sharing, branching) decoupled from any
|
|
89
|
+
backend through a `ChatAdapter`. State lives in `ChatProvider` and is read
|
|
90
|
+
with `useChat`:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { ChatProvider, ChatLayout, ChatThread } from '@parallelworks/ui/ai'
|
|
94
|
+
import { createMockChatAdapter } from '@parallelworks/ui/ai/mock'
|
|
95
|
+
|
|
96
|
+
export function Chat() {
|
|
97
|
+
return (
|
|
98
|
+
<ChatProvider
|
|
99
|
+
adapter={createMockChatAdapter()}
|
|
100
|
+
currentUser={{ id: 'u1', username: 'you', name: 'You' }}
|
|
101
|
+
navigation={{ toConversation: id => {}, toNewChat: () => {}, toAttachments: () => {} }}
|
|
102
|
+
notify={{ success: console.log, error: console.error, info: console.log }}
|
|
103
|
+
>
|
|
104
|
+
<ChatLayout>
|
|
105
|
+
<ChatThread />
|
|
106
|
+
</ChatLayout>
|
|
107
|
+
</ChatProvider>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Swap the mock for `createOpenAIChatAdapter({ baseUrl, apiKey })` from
|
|
113
|
+
`@parallelworks/ui/ai/openai`, or implement `ChatAdapter` against your own
|
|
114
|
+
backend. It requires `conversations`, `models` and `streamCompletion`; the
|
|
115
|
+
optional capability groups (`providers`, `attachments`, `sharing`,
|
|
116
|
+
`allocations`) hide their UI when absent. Adapter methods throw
|
|
117
|
+
`ChatAdapterError` with a user-facing `message`. Model entries and wire
|
|
118
|
+
messages keep OpenAI-compatible snake_case field names.
|
|
119
|
+
|
|
120
|
+
Tailwind v4 consumers add the chat rules (markdown rendering, streaming
|
|
121
|
+
animations) and streamdown's classes next to the theme contract:
|
|
122
|
+
|
|
123
|
+
```css
|
|
124
|
+
@import 'streamdown/styles.css';
|
|
125
|
+
@import '@parallelworks/ui/ai.css';
|
|
126
|
+
@source '../node_modules/streamdown/dist/*.js';
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The prebuilt `@parallelworks/ui/styles.css` already includes both.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ConversationsAdapter, type StreamCompletion } from './types.js';
|
|
2
|
+
export interface MemoryConversationStore {
|
|
3
|
+
conversations: ConversationsAdapter;
|
|
4
|
+
recordingStream(inner: StreamCompletion): StreamCompletion;
|
|
5
|
+
}
|
|
6
|
+
export declare function createMemoryConversationStore(options?: {
|
|
7
|
+
storageKey?: string;
|
|
8
|
+
}): MemoryConversationStore;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { ChatAdapterError } from "./types.js";
|
|
2
|
+
//#region src/ai/adapter/memoryStore.ts
|
|
3
|
+
function newId() {
|
|
4
|
+
return crypto.randomUUID();
|
|
5
|
+
}
|
|
6
|
+
function createMemoryConversationStore(options) {
|
|
7
|
+
const storageKey = options?.storageKey;
|
|
8
|
+
let conversations = [];
|
|
9
|
+
if (storageKey) try {
|
|
10
|
+
const raw = globalThis.localStorage?.getItem(storageKey);
|
|
11
|
+
if (raw) conversations = JSON.parse(raw);
|
|
12
|
+
} catch {
|
|
13
|
+
conversations = [];
|
|
14
|
+
}
|
|
15
|
+
const persist = () => {
|
|
16
|
+
if (storageKey) try {
|
|
17
|
+
globalThis.localStorage?.setItem(storageKey, JSON.stringify(conversations));
|
|
18
|
+
} catch {}
|
|
19
|
+
};
|
|
20
|
+
const find = (id) => {
|
|
21
|
+
const conversation = conversations.find((c) => c.id === id);
|
|
22
|
+
if (!conversation) throw new ChatAdapterError("Conversation not found", { status: 404 });
|
|
23
|
+
return conversation;
|
|
24
|
+
};
|
|
25
|
+
const toSummary = (c) => ({
|
|
26
|
+
id: c.id,
|
|
27
|
+
...c.title !== null ? { title: c.title } : {},
|
|
28
|
+
messageCount: c.messages.length,
|
|
29
|
+
createdAt: c.createdAt,
|
|
30
|
+
updatedAt: c.updatedAt,
|
|
31
|
+
isOwner: true,
|
|
32
|
+
canCollaborate: true
|
|
33
|
+
});
|
|
34
|
+
const recordUser = (req) => {
|
|
35
|
+
const conversation = conversations.find((c) => c.id === req.conversationId);
|
|
36
|
+
if (!conversation) return;
|
|
37
|
+
const last = req.messages[req.messages.length - 1];
|
|
38
|
+
const userContent = typeof last?.content === "string" ? last.content : "";
|
|
39
|
+
conversation.messages.push({
|
|
40
|
+
id: req.userMessageId,
|
|
41
|
+
role: "user",
|
|
42
|
+
content: userContent,
|
|
43
|
+
parentId: req.parentMessageId ?? null,
|
|
44
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
45
|
+
});
|
|
46
|
+
conversation.activeBranchId = req.userMessageId;
|
|
47
|
+
conversation.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
48
|
+
persist();
|
|
49
|
+
};
|
|
50
|
+
const recordAssistant = (req, assistant) => {
|
|
51
|
+
const conversation = conversations.find((c) => c.id === req.conversationId);
|
|
52
|
+
if (!conversation) return;
|
|
53
|
+
conversation.messages.push({
|
|
54
|
+
...assistant,
|
|
55
|
+
role: "assistant"
|
|
56
|
+
});
|
|
57
|
+
conversation.activeBranchId = assistant.id;
|
|
58
|
+
conversation.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
59
|
+
persist();
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
conversations: {
|
|
63
|
+
async list() {
|
|
64
|
+
return [...conversations].sort((a, b) => b.createdAt.localeCompare(a.createdAt)).map(toSummary);
|
|
65
|
+
},
|
|
66
|
+
async get(id) {
|
|
67
|
+
const c = find(id);
|
|
68
|
+
return {
|
|
69
|
+
id: c.id,
|
|
70
|
+
title: c.title,
|
|
71
|
+
messages: [...c.messages],
|
|
72
|
+
activeBranchId: c.activeBranchId,
|
|
73
|
+
createdAt: c.createdAt,
|
|
74
|
+
updatedAt: c.updatedAt,
|
|
75
|
+
isOwner: true,
|
|
76
|
+
canCollaborate: true
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
async create(title) {
|
|
80
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
81
|
+
const conversation = {
|
|
82
|
+
id: newId(),
|
|
83
|
+
title: title ?? null,
|
|
84
|
+
createdAt: now,
|
|
85
|
+
updatedAt: now,
|
|
86
|
+
activeBranchId: null,
|
|
87
|
+
messages: []
|
|
88
|
+
};
|
|
89
|
+
conversations.push(conversation);
|
|
90
|
+
persist();
|
|
91
|
+
return toSummary(conversation);
|
|
92
|
+
},
|
|
93
|
+
async rename(id, title) {
|
|
94
|
+
find(id).title = title;
|
|
95
|
+
persist();
|
|
96
|
+
},
|
|
97
|
+
async remove(id) {
|
|
98
|
+
conversations = conversations.filter((c) => c.id !== id);
|
|
99
|
+
persist();
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
recordingStream(inner) {
|
|
103
|
+
return async (req, handlers, signal) => {
|
|
104
|
+
let partialContent = "";
|
|
105
|
+
let partialReasoning = "";
|
|
106
|
+
const wrappedHandlers = {
|
|
107
|
+
...handlers,
|
|
108
|
+
onContent: (chunk) => {
|
|
109
|
+
partialContent += chunk;
|
|
110
|
+
handlers.onContent(chunk);
|
|
111
|
+
},
|
|
112
|
+
onReasoning: (chunk) => {
|
|
113
|
+
partialReasoning += chunk;
|
|
114
|
+
handlers.onReasoning?.(chunk);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
recordUser(req);
|
|
118
|
+
try {
|
|
119
|
+
const result = await inner(req, wrappedHandlers, signal);
|
|
120
|
+
const assistantId = result.messageId || newId();
|
|
121
|
+
recordAssistant(req, {
|
|
122
|
+
id: assistantId,
|
|
123
|
+
content: result.content || null,
|
|
124
|
+
parentId: req.userMessageId,
|
|
125
|
+
model: result.model ?? req.model,
|
|
126
|
+
...result.reasoning ? { reasoning: result.reasoning } : {},
|
|
127
|
+
...result.responsesOutput.length ? { responsesOutput: result.responsesOutput } : {},
|
|
128
|
+
...result.toolCalls.length ? { toolCalls: result.toolCalls } : {},
|
|
129
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
...result,
|
|
133
|
+
messageId: assistantId
|
|
134
|
+
};
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (error instanceof DOMException && error.name === "AbortError" && (partialContent || partialReasoning)) recordAssistant(req, {
|
|
137
|
+
id: newId(),
|
|
138
|
+
content: partialContent || null,
|
|
139
|
+
parentId: req.userMessageId,
|
|
140
|
+
model: req.model,
|
|
141
|
+
...partialReasoning ? { reasoning: partialReasoning } : {},
|
|
142
|
+
stopped: true,
|
|
143
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
144
|
+
});
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
export { createMemoryConversationStore };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createMemoryConversationStore } from "../memoryStore.js";
|
|
2
|
+
//#region src/ai/adapter/mock/mockAdapter.ts
|
|
3
|
+
var CANNED_REPLIES = [
|
|
4
|
+
"Here's a canned reply from the mock adapter. It streams word by word so the typewriter animation, cancel behavior, and background streams all exercise the same code paths as a real backend.\n\n- No network requests are made\n- Conversations persist in localStorage\n- Try cancelling mid-stream: the partial reply is kept",
|
|
5
|
+
"Everything you see is generated locally. The mock adapter implements the same `ChatAdapter` interface as the OpenAI-compatible adapter and any backend's own, so the UI can't tell the difference.\n\n```ts\nconst adapter = createMockChatAdapter()\n```\n\nMarkdown, `inline code`, and code blocks render through the same pipeline.",
|
|
6
|
+
"Branching works too: edit one of your earlier messages or hit regenerate on a reply, then use the arrows to flip between branches."
|
|
7
|
+
];
|
|
8
|
+
var THINKING_PREAMBLE = "Thinking about the question, weighing a few canned options, and picking the next reply in the rotation...";
|
|
9
|
+
function sleep(ms, signal) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
if (signal?.aborted) {
|
|
12
|
+
reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const onAbort = () => {
|
|
16
|
+
clearTimeout(timer);
|
|
17
|
+
reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
18
|
+
};
|
|
19
|
+
const timer = setTimeout(() => {
|
|
20
|
+
signal?.removeEventListener("abort", onAbort);
|
|
21
|
+
resolve();
|
|
22
|
+
}, ms);
|
|
23
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function createMockChatAdapter(options) {
|
|
27
|
+
const store = createMemoryConversationStore({ storageKey: options?.storageKey ?? "ai-chat-mock-conversations" });
|
|
28
|
+
let replyIndex = 0;
|
|
29
|
+
return {
|
|
30
|
+
conversations: store.conversations,
|
|
31
|
+
models: { async list() {
|
|
32
|
+
return {
|
|
33
|
+
models: [{
|
|
34
|
+
id: "mock:local/canned-small",
|
|
35
|
+
object: "model",
|
|
36
|
+
created: 0,
|
|
37
|
+
owned_by: "mock",
|
|
38
|
+
provider: "Mock Provider",
|
|
39
|
+
tool_calling_mode: "none"
|
|
40
|
+
}, {
|
|
41
|
+
id: "mock:local/canned-thinking",
|
|
42
|
+
object: "model",
|
|
43
|
+
created: 0,
|
|
44
|
+
owned_by: "mock",
|
|
45
|
+
provider: "Mock Provider",
|
|
46
|
+
tool_calling_mode: "none"
|
|
47
|
+
}],
|
|
48
|
+
unreachableSessions: []
|
|
49
|
+
};
|
|
50
|
+
} },
|
|
51
|
+
streamCompletion: store.recordingStream(async (req, handlers, signal) => {
|
|
52
|
+
if (signal?.aborted) throw new DOMException("The operation was aborted.", "AbortError");
|
|
53
|
+
let reasoning = "";
|
|
54
|
+
if (req.model.includes("thinking")) for (const word of THINKING_PREAMBLE.split(/(?<= )/)) {
|
|
55
|
+
await sleep(40, signal);
|
|
56
|
+
reasoning += word;
|
|
57
|
+
handlers.onReasoning?.(word);
|
|
58
|
+
}
|
|
59
|
+
const reply = CANNED_REPLIES[replyIndex % CANNED_REPLIES.length] ?? "";
|
|
60
|
+
replyIndex++;
|
|
61
|
+
await sleep(250, signal);
|
|
62
|
+
let content = "";
|
|
63
|
+
for (const word of reply.split(/(?<= )/)) {
|
|
64
|
+
await sleep(25, signal);
|
|
65
|
+
content += word;
|
|
66
|
+
handlers.onContent(word);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
content,
|
|
70
|
+
messageId: "",
|
|
71
|
+
toolCalls: [],
|
|
72
|
+
finishReason: "stop",
|
|
73
|
+
model: req.model,
|
|
74
|
+
reasoning,
|
|
75
|
+
responsesOutput: []
|
|
76
|
+
};
|
|
77
|
+
})
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { createMockChatAdapter };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type StreamCompletion, type StreamRequest } from '../types.js';
|
|
2
|
+
export interface OpenAIStreamConfig {
|
|
3
|
+
completionsUrl: string;
|
|
4
|
+
responsesUrl?: string;
|
|
5
|
+
buildHeaders?: (req: StreamRequest) => Record<string, string>;
|
|
6
|
+
credentials?: RequestCredentials;
|
|
7
|
+
fetch?: typeof globalThis.fetch;
|
|
8
|
+
}
|
|
9
|
+
export declare function createOpenAICompatibleStream(config: OpenAIStreamConfig): StreamCompletion;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ChatAdapterError } from "../types.js";
|
|
2
|
+
import { errorMessageFromResponse } from "./errors.js";
|
|
3
|
+
import { buildResponsesRequestBody, parseResponsesSSELine } from "./responses.js";
|
|
4
|
+
import { processSSEStream } from "./sse.js";
|
|
5
|
+
//#region src/ai/adapter/openai/createOpenAIStream.ts
|
|
6
|
+
function createOpenAICompatibleStream(config) {
|
|
7
|
+
return async (req, handlers, signal) => {
|
|
8
|
+
const useResponses = req.useResponses === true && !!config.responsesUrl;
|
|
9
|
+
const url = useResponses ? config.responsesUrl : config.completionsUrl;
|
|
10
|
+
const body = useResponses ? buildResponsesRequestBody(req.model, req.messages) : {
|
|
11
|
+
model: req.model,
|
|
12
|
+
messages: req.messages,
|
|
13
|
+
stream: true
|
|
14
|
+
};
|
|
15
|
+
const res = await (config.fetch ?? globalThis.fetch.bind(globalThis))(url, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: {
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
Accept: "text/event-stream",
|
|
20
|
+
...config.buildHeaders?.(req)
|
|
21
|
+
},
|
|
22
|
+
...config.credentials ? { credentials: config.credentials } : {},
|
|
23
|
+
body: JSON.stringify(body),
|
|
24
|
+
...signal ? { signal } : {}
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok) throw new ChatAdapterError(await errorMessageFromResponse(res), { status: res.status });
|
|
27
|
+
const reader = res.body?.getReader();
|
|
28
|
+
if (!reader) throw new ChatAdapterError("No response body");
|
|
29
|
+
return processSSEStream(reader, handlers.onContent, void 0, void 0, handlers.onReasoning, handlers.onMessageId, useResponses ? parseResponsesSSELine : void 0);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { createOpenAICompatibleStream };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region src/ai/adapter/openai/errors.ts
|
|
2
|
+
function apiErrorMessage(errorData, fallback) {
|
|
3
|
+
if (typeof errorData !== "object" || errorData === null) return fallback;
|
|
4
|
+
const data = errorData;
|
|
5
|
+
const candidate = data["message"] ?? data["error"] ?? data["detail"];
|
|
6
|
+
if (typeof candidate === "string" && candidate.trim()) return candidate;
|
|
7
|
+
if (candidate && typeof candidate === "object") {
|
|
8
|
+
const nested = candidate["message"];
|
|
9
|
+
if (typeof nested === "string" && nested.trim()) return nested;
|
|
10
|
+
try {
|
|
11
|
+
return JSON.stringify(candidate);
|
|
12
|
+
} catch {
|
|
13
|
+
return fallback;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
function statusFallbackMessage(status) {
|
|
19
|
+
if (status === 401) return "Your session has expired. Please refresh the page and try again.";
|
|
20
|
+
if (status === 403) return "You do not have permission to use this AI provider.";
|
|
21
|
+
if (status === 404) return "The AI provider or model was not found. Please select a different provider.";
|
|
22
|
+
if (status === 429) return "Too many requests. Please wait a moment and try again.";
|
|
23
|
+
if (status >= 500) return "The AI service is temporarily unavailable. Please try again later.";
|
|
24
|
+
return "Failed to send message";
|
|
25
|
+
}
|
|
26
|
+
async function errorMessageFromResponse(res) {
|
|
27
|
+
let message = statusFallbackMessage(res.status);
|
|
28
|
+
try {
|
|
29
|
+
const contentType = res.headers.get("content-type");
|
|
30
|
+
const text = await res.text();
|
|
31
|
+
if (contentType?.includes("application/json") && text) message = apiErrorMessage(JSON.parse(text), message);
|
|
32
|
+
else if (text && res.status < 500) message = text.substring(0, 200);
|
|
33
|
+
} catch {}
|
|
34
|
+
return message;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { apiErrorMessage, errorMessageFromResponse, statusFallbackMessage };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createOpenAICompatibleStream, type OpenAIStreamConfig, } from './createOpenAIStream.js';
|
|
2
|
+
export { apiErrorMessage, errorMessageFromResponse, statusFallbackMessage, } from './errors.js';
|
|
3
|
+
export { createOpenAIChatAdapter, type OpenAIChatAdapterOptions, } from './openaiAdapter.js';
|
|
4
|
+
export { buildResponsesRequestBody, parseResponsesSSELine, } from './responses.js';
|
|
5
|
+
export { type ParsedSSELine, parseSSELine, processSSEStream, type SSELineParser, } from './sse.js';
|
|
6
|
+
export type { CompletionChunk, CompletionChunkChoice, CompletionMessage, CompletionRequest, ModelsWireResponse, } from './wire.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { apiErrorMessage, errorMessageFromResponse, statusFallbackMessage } from "./errors.js";
|
|
2
|
+
import { buildResponsesRequestBody, parseResponsesSSELine } from "./responses.js";
|
|
3
|
+
import { parseSSELine, processSSEStream } from "./sse.js";
|
|
4
|
+
import { createOpenAICompatibleStream } from "./createOpenAIStream.js";
|
|
5
|
+
import { createOpenAIChatAdapter } from "./openaiAdapter.js";
|
|
6
|
+
export { apiErrorMessage, buildResponsesRequestBody, createOpenAIChatAdapter, createOpenAICompatibleStream, errorMessageFromResponse, parseResponsesSSELine, parseSSELine, processSSEStream, statusFallbackMessage };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ChatAdapter } from '../types.js';
|
|
2
|
+
export interface OpenAIChatAdapterOptions {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
models?: string[];
|
|
6
|
+
storageKey?: string;
|
|
7
|
+
fetch?: typeof globalThis.fetch;
|
|
8
|
+
}
|
|
9
|
+
export declare function createOpenAIChatAdapter(options: OpenAIChatAdapterOptions): ChatAdapter;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ChatAdapterError } from "../types.js";
|
|
2
|
+
import { errorMessageFromResponse } from "./errors.js";
|
|
3
|
+
import { createOpenAICompatibleStream } from "./createOpenAIStream.js";
|
|
4
|
+
import { createMemoryConversationStore } from "../memoryStore.js";
|
|
5
|
+
//#region src/ai/adapter/openai/openaiAdapter.ts
|
|
6
|
+
function providerLabelFor(baseUrl) {
|
|
7
|
+
try {
|
|
8
|
+
return new URL(baseUrl).host;
|
|
9
|
+
} catch {
|
|
10
|
+
return "OpenAI compatible";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function staticModel(id, provider) {
|
|
14
|
+
return {
|
|
15
|
+
id,
|
|
16
|
+
object: "model",
|
|
17
|
+
created: 0,
|
|
18
|
+
owned_by: "openai-compatible",
|
|
19
|
+
provider,
|
|
20
|
+
tool_calling_mode: "none"
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function createOpenAIChatAdapter(options) {
|
|
24
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
25
|
+
const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
26
|
+
const headers = options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {};
|
|
27
|
+
const store = createMemoryConversationStore({ storageKey: options.storageKey ?? "ai-chat-openai-conversations" });
|
|
28
|
+
return {
|
|
29
|
+
conversations: store.conversations,
|
|
30
|
+
models: { async list() {
|
|
31
|
+
const providerLabel = providerLabelFor(baseUrl);
|
|
32
|
+
if (options.models?.length) return {
|
|
33
|
+
models: options.models.map((id) => staticModel(id, providerLabel)),
|
|
34
|
+
unreachableSessions: []
|
|
35
|
+
};
|
|
36
|
+
const res = await doFetch(`${baseUrl}/models`, { headers });
|
|
37
|
+
if (!res.ok) throw new ChatAdapterError(await errorMessageFromResponse(res), { status: res.status });
|
|
38
|
+
return {
|
|
39
|
+
models: ((await res.json()).data ?? []).map((m) => ({
|
|
40
|
+
...staticModel(m.id, providerLabel),
|
|
41
|
+
...m
|
|
42
|
+
})),
|
|
43
|
+
unreachableSessions: []
|
|
44
|
+
};
|
|
45
|
+
} },
|
|
46
|
+
streamCompletion: store.recordingStream(createOpenAICompatibleStream({
|
|
47
|
+
completionsUrl: `${baseUrl}/chat/completions`,
|
|
48
|
+
buildHeaders: () => headers,
|
|
49
|
+
...options.fetch ? { fetch: options.fetch } : {}
|
|
50
|
+
}))
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { createOpenAIChatAdapter };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ParsedSSELine } from './sse.js';
|
|
2
|
+
import type { CompletionMessage } from './wire.js';
|
|
3
|
+
export declare function buildResponsesRequestBody(model: string, messages: CompletionMessage[]): Record<string, unknown>;
|
|
4
|
+
export declare function parseResponsesSSELine(line: string): ParsedSSELine;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
//#region src/ai/adapter/openai/responses.ts
|
|
2
|
+
function contentString(content) {
|
|
3
|
+
return typeof content === "string" ? content : "";
|
|
4
|
+
}
|
|
5
|
+
function buildResponsesRequestBody(model, messages) {
|
|
6
|
+
const input = [];
|
|
7
|
+
let attachmentIds;
|
|
8
|
+
for (const m of messages) switch (m.role) {
|
|
9
|
+
case "system":
|
|
10
|
+
input.push({
|
|
11
|
+
type: "message",
|
|
12
|
+
role: "system",
|
|
13
|
+
content: contentString(m.content)
|
|
14
|
+
});
|
|
15
|
+
break;
|
|
16
|
+
case "user":
|
|
17
|
+
if (m.attachment_ids?.length) attachmentIds = m.attachment_ids;
|
|
18
|
+
input.push({
|
|
19
|
+
type: "message",
|
|
20
|
+
role: "user",
|
|
21
|
+
content: contentString(m.content)
|
|
22
|
+
});
|
|
23
|
+
break;
|
|
24
|
+
case "assistant": {
|
|
25
|
+
const replayed = Array.isArray(m.responses_output) ? replayableOutput(m.responses_output) : [];
|
|
26
|
+
if (replayed.length > 0) {
|
|
27
|
+
for (const item of replayed) input.push(item);
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
const text = contentString(m.content);
|
|
31
|
+
const toolCalls = m.tool_calls ?? [];
|
|
32
|
+
if (text || toolCalls.length === 0) input.push({
|
|
33
|
+
type: "message",
|
|
34
|
+
role: "assistant",
|
|
35
|
+
content: text
|
|
36
|
+
});
|
|
37
|
+
for (const tc of toolCalls) input.push({
|
|
38
|
+
type: "function_call",
|
|
39
|
+
call_id: tc.id ?? "",
|
|
40
|
+
name: tc.function.name,
|
|
41
|
+
arguments: tc.function.arguments
|
|
42
|
+
});
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
case "tool": input.push({
|
|
46
|
+
type: "function_call_output",
|
|
47
|
+
call_id: m.tool_call_id ?? "",
|
|
48
|
+
output: contentString(m.content)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const body = {
|
|
52
|
+
model,
|
|
53
|
+
input,
|
|
54
|
+
stream: true,
|
|
55
|
+
store: false,
|
|
56
|
+
include: ["reasoning.encrypted_content"]
|
|
57
|
+
};
|
|
58
|
+
if (attachmentIds?.length) body["attachment_ids"] = attachmentIds;
|
|
59
|
+
return body;
|
|
60
|
+
}
|
|
61
|
+
var REPLAYABLE_ITEM_TYPES = /* @__PURE__ */ new Set([
|
|
62
|
+
"reasoning",
|
|
63
|
+
"message",
|
|
64
|
+
"function_call"
|
|
65
|
+
]);
|
|
66
|
+
function replayableOutput(output) {
|
|
67
|
+
if (!output?.length) return [];
|
|
68
|
+
return output.filter((item) => {
|
|
69
|
+
if (typeof item !== "object" || item === null) return false;
|
|
70
|
+
const itemType = item.type;
|
|
71
|
+
return typeof itemType === "string" && REPLAYABLE_ITEM_TYPES.has(itemType);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function parseResponsesSSELine(line) {
|
|
75
|
+
if (!line.startsWith("data: ")) return null;
|
|
76
|
+
const data = line.slice(6);
|
|
77
|
+
if (data === "[DONE]" || data === "\"[DONE]\"") return null;
|
|
78
|
+
let event;
|
|
79
|
+
try {
|
|
80
|
+
event = JSON.parse(data);
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
if (event.error) return { error: event.error.message || "Unknown error" };
|
|
85
|
+
const model = event.response?.model;
|
|
86
|
+
switch (event.type) {
|
|
87
|
+
case "pw.message_id": return event.messageId ? { messageId: event.messageId } : null;
|
|
88
|
+
case "response.output_text.delta":
|
|
89
|
+
case "response.refusal.delta": return typeof event.delta === "string" ? { content: event.delta } : null;
|
|
90
|
+
case "response.reasoning_summary_text.delta": return typeof event.delta === "string" ? { reasoningContent: event.delta } : null;
|
|
91
|
+
case "response.created": return model ? { model } : null;
|
|
92
|
+
case "response.completed": return {
|
|
93
|
+
...model && { model },
|
|
94
|
+
finishReason: "stop",
|
|
95
|
+
responsesOutput: replayableOutput(event.response?.output)
|
|
96
|
+
};
|
|
97
|
+
case "response.incomplete": return {
|
|
98
|
+
...model && { model },
|
|
99
|
+
finishReason: "length",
|
|
100
|
+
responsesOutput: replayableOutput(event.response?.output)
|
|
101
|
+
};
|
|
102
|
+
case "response.failed": return { error: event.response?.error?.message || "Response failed" };
|
|
103
|
+
case "error": return { error: event.message || "Stream error" };
|
|
104
|
+
default: return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
export { buildResponsesRequestBody, parseResponsesSSELine };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ToolCall, ToolCallDelta } from '../../types.js';
|
|
2
|
+
export type ParsedSSELine = {
|
|
3
|
+
id?: string | undefined;
|
|
4
|
+
messageId?: string | undefined;
|
|
5
|
+
content?: string | undefined;
|
|
6
|
+
reasoningContent?: string | undefined;
|
|
7
|
+
toolCalls?: ToolCallDelta[] | undefined;
|
|
8
|
+
finishReason?: string | null | undefined;
|
|
9
|
+
model?: string | undefined;
|
|
10
|
+
error?: string | undefined;
|
|
11
|
+
responsesOutput?: unknown[] | undefined;
|
|
12
|
+
} | null;
|
|
13
|
+
export type SSELineParser = (line: string) => ParsedSSELine;
|
|
14
|
+
export declare function parseSSELine(line: string): ParsedSSELine;
|
|
15
|
+
/** Process an SSE stream from a ReadableStream reader */
|
|
16
|
+
export declare function processSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>, onContent: (content: string) => void, onToolCall?: (toolCalls: ToolCall[]) => void, onError?: (error: string) => void, onReasoning?: (reasoning: string) => void, onMessageId?: (messageId: string) => void, parseLine?: SSELineParser): Promise<{
|
|
17
|
+
content: string;
|
|
18
|
+
messageId: string;
|
|
19
|
+
toolCalls: ToolCall[];
|
|
20
|
+
finishReason: string | null;
|
|
21
|
+
model: string | null;
|
|
22
|
+
reasoning: string;
|
|
23
|
+
responsesOutput: unknown[];
|
|
24
|
+
}>;
|