@xp266/dshtui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dshtui.js +65 -0
- package/cordis.patch.yml +81 -0
- package/lib/contract/index.d.mts +2 -0
- package/lib/contract/index.mjs +5 -0
- package/lib/dialog.d.mts +156 -0
- package/lib/dialog.mjs +3 -0
- package/lib/index-DqnaSXqP.d.mts +823 -0
- package/lib/index.d.mts +17 -0
- package/lib/index.mjs +11404 -0
- package/lib/prod-react-Dzof4qJx.mjs +4 -0
- package/lib/surface-DdxzIgIY.mjs +3126 -0
- package/lib/vendor.d.mts +3 -0
- package/lib/vendor.mjs +4 -0
- package/package.json +128 -0
- package/src/apply-theme.ts +8 -0
- package/src/boot-log.ts +35 -0
- package/src/chat/blocks.ts +41 -0
- package/src/chat/bridge.ts +1076 -0
- package/src/chat/builtin-tool-views.ts +53 -0
- package/src/chat/chat-face.ts +16 -0
- package/src/chat/chat-nodes.ts +53 -0
- package/src/chat/efforts.ts +4 -0
- package/src/chat/interactions.ts +329 -0
- package/src/chat/models.ts +263 -0
- package/src/chat/partial-json.ts +125 -0
- package/src/chat/presets.ts +26 -0
- package/src/chat/question-view.ts +106 -0
- package/src/chat/retry-status.ts +33 -0
- package/src/chat/session-groups.ts +34 -0
- package/src/chat/session-list.ts +311 -0
- package/src/chat/store.ts +636 -0
- package/src/chat/todo-view.ts +66 -0
- package/src/chat/tool-view.ts +311 -0
- package/src/chat/tool-views.ts +114 -0
- package/src/contract/index.ts +704 -0
- package/src/contract/upstream.ts +165 -0
- package/src/core/caret-nonce.ts +19 -0
- package/src/core/composer-layout.ts +65 -0
- package/src/core/edit.ts +152 -0
- package/src/core/field-view.ts +52 -0
- package/src/core/fields.ts +185 -0
- package/src/core/metrics.ts +26 -0
- package/src/core/paste.ts +149 -0
- package/src/core/segments.ts +112 -0
- package/src/core/text.ts +290 -0
- package/src/dialog.ts +11 -0
- package/src/env.ts +38 -0
- package/src/harness-home.ts +22 -0
- package/src/hot-theme.ts +56 -0
- package/src/index.tsx +185 -0
- package/src/kernel/registry.ts +102 -0
- package/src/kernel/surface.ts +33 -0
- package/src/log.ts +249 -0
- package/src/model/message.ts +89 -0
- package/src/model/selection.ts +63 -0
- package/src/performance-guard.ts +28 -0
- package/src/runtime/prod-react.ts +10 -0
- package/src/terminal/background.ts +66 -0
- package/src/terminal/capabilities.ts +54 -0
- package/src/terminal/clipboard-backends.ts +69 -0
- package/src/terminal/clipboard.ts +161 -0
- package/src/terminal/cursor-shape.ts +13 -0
- package/src/terminal/glyphs.ts +44 -0
- package/src/terminal/mouse.ts +154 -0
- package/src/terminal/probe.ts +61 -0
- package/src/terminal/screen.ts +349 -0
- package/src/theme-settings.ts +31 -0
- package/src/theme.ts +319 -0
- package/src/ui/app.tsx +454 -0
- package/src/ui/chrome/caps.tsx +9 -0
- package/src/ui/chrome/hint-service.ts +87 -0
- package/src/ui/chrome/input-status.ts +27 -0
- package/src/ui/chrome/key-gate.tsx +29 -0
- package/src/ui/chrome/status-bar.tsx +147 -0
- package/src/ui/contributions.ts +47 -0
- package/src/ui/dialog/defaults-dialog.tsx +152 -0
- package/src/ui/dialog/dialog-item.tsx +38 -0
- package/src/ui/dialog/dialog.tsx +386 -0
- package/src/ui/dialog/effort-dialog.tsx +45 -0
- package/src/ui/dialog/geometry.ts +131 -0
- package/src/ui/dialog/items.ts +124 -0
- package/src/ui/dialog/list-dialog.tsx +63 -0
- package/src/ui/dialog/models-dialog.tsx +203 -0
- package/src/ui/dialog/presets-dialog.tsx +46 -0
- package/src/ui/dialog/providers-dialog.tsx +313 -0
- package/src/ui/dialog/sessions-dialog.tsx +163 -0
- package/src/ui/dialog/sizes.ts +7 -0
- package/src/ui/dialog/status-lines.ts +11 -0
- package/src/ui/dialog/todo-dialog.tsx +29 -0
- package/src/ui/dialog/use-dialog-input.ts +186 -0
- package/src/ui/extension-point.ts +188 -0
- package/src/ui/home-logo.ts +132 -0
- package/src/ui/hooks/use-async-action.ts +25 -0
- package/src/ui/hooks/use-async-list.ts +71 -0
- package/src/ui/hooks/use-caret.ts +24 -0
- package/src/ui/hooks/use-chat-events.ts +153 -0
- package/src/ui/hooks/use-mouse-selection.ts +352 -0
- package/src/ui/hooks/use-scroll.ts +49 -0
- package/src/ui/hooks/use-terminal-size.ts +20 -0
- package/src/ui/input/commands.ts +118 -0
- package/src/ui/input/composer-bus.ts +14 -0
- package/src/ui/input/composer-fields.ts +111 -0
- package/src/ui/input/composer-keys.ts +21 -0
- package/src/ui/input/composer-paste.ts +20 -0
- package/src/ui/input/input-bar.tsx +279 -0
- package/src/ui/input/use-composer.ts +495 -0
- package/src/ui/key-arbiter.ts +37 -0
- package/src/ui/keymap.ts +15 -0
- package/src/ui/layout-service.ts +77 -0
- package/src/ui/message/layout.ts +763 -0
- package/src/ui/message/md/block.ts +247 -0
- package/src/ui/message/md/engine.ts +430 -0
- package/src/ui/message/md/extensions.ts +58 -0
- package/src/ui/message/md/highlight.ts +626 -0
- package/src/ui/message/md/index.ts +14 -0
- package/src/ui/message/md/inline.ts +97 -0
- package/src/ui/message/md/palette.ts +87 -0
- package/src/ui/message/message-list.tsx +69 -0
- package/src/ui/message/message-row.tsx +137 -0
- package/src/ui/message/message-views.ts +16 -0
- package/src/ui/message/renderers.ts +39 -0
- package/src/ui/message/tool-diff.ts +188 -0
- package/src/ui/message/warmup.ts +41 -0
- package/src/ui/message/wave.ts +98 -0
- package/src/ui/overlay.ts +22 -0
- package/src/ui/panels/approval-panel.tsx +193 -0
- package/src/ui/panels/exports.ts +3 -0
- package/src/ui/panels/question-model.ts +424 -0
- package/src/ui/panels/question-panel.tsx +154 -0
- package/src/ui/panels/surface.tsx +106 -0
- package/src/ui/panels-builtin.tsx +86 -0
- package/src/ui/pointer/builtins.ts +281 -0
- package/src/ui/pointer/registry.ts +25 -0
- package/src/ui/region.tsx +31 -0
- package/src/ui/selection/service.ts +92 -0
- package/src/ui/selection-registry.ts +88 -0
- package/src/ui/selection.tsx +148 -0
- package/src/ui/spinner-tick.tsx +24 -0
- package/src/ui/use-available-windows.ts +31 -0
- package/src/ui/widgets/actions.tsx +84 -0
- package/src/ui/widgets/button.tsx +57 -0
- package/src/ui/widgets/checkbox.tsx +58 -0
- package/src/ui/widgets/header.tsx +36 -0
- package/src/ui/widgets/index.ts +8 -0
- package/src/ui/widgets/input.tsx +76 -0
- package/src/ui/widgets/registry.ts +32 -0
- package/src/ui/widgets/search.tsx +59 -0
- package/src/ui/widgets/select.tsx +97 -0
- package/src/ui/widgets/static.tsx +37 -0
- package/src/ui/widgets/types.ts +1 -0
- package/src/ui/window-services-bridge.ts +71 -0
- package/src/ui/window-services.ts +20 -0
- package/src/ui/windows/models-window.tsx +15 -0
- package/src/ui/windows/providers-window.tsx +14 -0
- package/src/ui/windows/service-window.tsx +18 -0
- package/src/ui/windows/sessions-window.tsx +25 -0
- package/src/ui/windows-builtin.tsx +94 -0
- package/src/ui/windows.ts +18 -0
- package/src/vendor.ts +12 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Box, Text } from 'ink'
|
|
2
|
+
import { COLORS } from '../../theme.ts'
|
|
3
|
+
import { glyphs } from '../../terminal/glyphs.ts'
|
|
4
|
+
import { selectBlock, CAROUSEL_BUTTON_WIDTH } from '../dialog/geometry.ts'
|
|
5
|
+
import { textWidth } from '../../core/text.ts'
|
|
6
|
+
import { SelectableText } from '../selection.tsx'
|
|
7
|
+
import { BUILTIN_WIDGET_ORDER, registerWidget } from './registry.ts'
|
|
8
|
+
import type { PaintArgs, ClickHit, ClickActions, WidgetKeyApi } from './types.ts'
|
|
9
|
+
import type { DialogItem } from '../dialog/items.ts'
|
|
10
|
+
|
|
11
|
+
type SelectItem = Extract<DialogItem, { type: 'select' }>
|
|
12
|
+
|
|
13
|
+
export function carouselHit(item: SelectItem, width: number, localX: number): -1 | 1 | null {
|
|
14
|
+
if (item.options.length <= 1) return null
|
|
15
|
+
const block = selectBlock(width, item.value)
|
|
16
|
+
if (localX >= block.blockStart && localX < block.blockStart + CAROUSEL_BUTTON_WIDTH) return -1
|
|
17
|
+
if (localX >= block.blockStart + block.blockWidth - CAROUSEL_BUTTON_WIDTH && localX < block.blockStart + block.blockWidth) return 1
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function paintSelect(paint: PaintArgs<SelectItem>) {
|
|
22
|
+
const { item, width, y, x } = paint
|
|
23
|
+
const block = selectBlock(width, item.value)
|
|
24
|
+
const hasArrows = item.options.length > 1
|
|
25
|
+
const pad = hasArrows ? block.leftPad : block.fullLeftPad
|
|
26
|
+
const inner = block.blockWidth - (hasArrows ? CAROUSEL_BUTTON_WIDTH * 2 : 0)
|
|
27
|
+
const valueCol = x + block.blockStart + (hasArrows ? CAROUSEL_BUTTON_WIDTH : 0)
|
|
28
|
+
const valueText = ' '.repeat(pad) + block.text + ' '.repeat(Math.max(0, inner - pad - textWidth(block.text)))
|
|
29
|
+
const carousel = (
|
|
30
|
+
<Box width={width} justifyContent="space-between">
|
|
31
|
+
<SelectableText y={y} col={x} text={`${item.label}:`} />
|
|
32
|
+
<Box width={block.blockWidth} flexDirection="row">
|
|
33
|
+
{hasArrows && (
|
|
34
|
+
<Text color={COLORS.ink} backgroundColor={paint.pressed === 'left' ? COLORS.carouselButtonPressedBg : COLORS.carouselButtonBg}>{` ${glyphs.carouselLeft} `}</Text>
|
|
35
|
+
)}
|
|
36
|
+
<SelectableText
|
|
37
|
+
y={y}
|
|
38
|
+
col={valueCol}
|
|
39
|
+
text={valueText}
|
|
40
|
+
color={paint.focused ? COLORS.carouselSelectedText : COLORS.ink}
|
|
41
|
+
backgroundColor={COLORS.carouselCurrentBg}
|
|
42
|
+
/>
|
|
43
|
+
{hasArrows && (
|
|
44
|
+
<Text color={COLORS.ink} backgroundColor={paint.pressed === 'right' ? COLORS.carouselButtonPressedBg : COLORS.carouselButtonBg}>{` ${glyphs.carouselRight} `}</Text>
|
|
45
|
+
)}
|
|
46
|
+
</Box>
|
|
47
|
+
</Box>
|
|
48
|
+
)
|
|
49
|
+
if (!item.spaced) return carousel
|
|
50
|
+
return (
|
|
51
|
+
<Box flexDirection="column">
|
|
52
|
+
{paint.clip === 0 && carousel}
|
|
53
|
+
<Box height={1}>
|
|
54
|
+
<Text> </Text>
|
|
55
|
+
</Box>
|
|
56
|
+
</Box>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function cycle(item: SelectItem, direction: -1 | 1): void {
|
|
61
|
+
const index = Math.max(0, item.options.indexOf(item.value))
|
|
62
|
+
const length = item.options.length
|
|
63
|
+
const next = item.options[(index + direction + length) % length]
|
|
64
|
+
if (next !== undefined) item.onChange(next)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
registerWidget<SelectItem>('select', {
|
|
68
|
+
selectable: true,
|
|
69
|
+
height(item) {
|
|
70
|
+
return item.spaced === true ? 2 : 1
|
|
71
|
+
},
|
|
72
|
+
paintWidth(item) {
|
|
73
|
+
return textWidth(item.label)
|
|
74
|
+
},
|
|
75
|
+
render: paintSelect,
|
|
76
|
+
onLeftRight(item, direction) {
|
|
77
|
+
if (item.options.length <= 1) return false
|
|
78
|
+
cycle(item, direction)
|
|
79
|
+
return true
|
|
80
|
+
},
|
|
81
|
+
onEnter(item, api) {
|
|
82
|
+
if (item.onEnter !== undefined) {
|
|
83
|
+
item.onEnter()
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
api.navigate('down')
|
|
87
|
+
return true
|
|
88
|
+
},
|
|
89
|
+
onClick(item, hit: ClickHit, actions: ClickActions) {
|
|
90
|
+
if (hit.localY !== 0) return false
|
|
91
|
+
const direction = carouselHit(item, hit.width, hit.localX)
|
|
92
|
+
if (direction === null) return false
|
|
93
|
+
cycle(item, direction)
|
|
94
|
+
actions.flash(direction === -1 ? 'left' : 'right')
|
|
95
|
+
return true
|
|
96
|
+
},
|
|
97
|
+
}, { order: BUILTIN_WIDGET_ORDER })
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Box } from 'ink'
|
|
2
|
+
import { COLORS } from '../../theme.ts'
|
|
3
|
+
import { textWidth, wrapLines } from '../../core/text.ts'
|
|
4
|
+
import { SelectableText } from '../selection.tsx'
|
|
5
|
+
import { BUILTIN_WIDGET_ORDER, registerWidget } from './registry.ts'
|
|
6
|
+
import type { DialogItem } from '../dialog/items.ts'
|
|
7
|
+
|
|
8
|
+
type StaticItem = Extract<DialogItem, { type: 'static' }>
|
|
9
|
+
|
|
10
|
+
registerWidget<StaticItem>('static', {
|
|
11
|
+
selectable: false,
|
|
12
|
+
height(item, width) {
|
|
13
|
+
return Math.max(1, wrapLines(item.label, Math.max(4, width)).length)
|
|
14
|
+
},
|
|
15
|
+
paintWidth(item) {
|
|
16
|
+
return textWidth(item.label)
|
|
17
|
+
},
|
|
18
|
+
render({ item, width, y, x }) {
|
|
19
|
+
const lines = item.label === '' ? [''] : wrapLines(item.label, Math.max(4, width))
|
|
20
|
+
if (lines.length === 1) {
|
|
21
|
+
return <SelectableText y={y} col={x} text={item.label === '' ? ' ' : item.label} color={item.label === '' ? undefined : COLORS.toolBodyText} />
|
|
22
|
+
}
|
|
23
|
+
return (
|
|
24
|
+
<Box flexDirection="column">
|
|
25
|
+
{lines.map((line, index) => (
|
|
26
|
+
<SelectableText
|
|
27
|
+
key={index}
|
|
28
|
+
y={y + index}
|
|
29
|
+
col={x}
|
|
30
|
+
text={line === '' ? ' ' : line}
|
|
31
|
+
color={line === '' ? undefined : COLORS.toolBodyText}
|
|
32
|
+
/>
|
|
33
|
+
))}
|
|
34
|
+
</Box>
|
|
35
|
+
)
|
|
36
|
+
},
|
|
37
|
+
}, { order: BUILTIN_WIDGET_ORDER })
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { ClickActions, ClickHit, PaintArgs, WidgetDef, WidgetKeyApi } from '../../contract/index.ts'
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { registerWindowService } from './window-services.ts'
|
|
2
|
+
import type { ChatBridge } from '../chat/bridge.ts'
|
|
3
|
+
import type { ModelApi } from './dialog/models-dialog.tsx'
|
|
4
|
+
import type { SessionsApi } from './dialog/sessions-dialog.tsx'
|
|
5
|
+
import type { PresetsApi } from './dialog/presets-dialog.tsx'
|
|
6
|
+
import type { EffortsApi } from './dialog/effort-dialog.tsx'
|
|
7
|
+
import type { DefaultsApi } from './dialog/defaults-dialog.tsx'
|
|
8
|
+
import type { TodoItemLike } from '../chat/todo-view.ts'
|
|
9
|
+
|
|
10
|
+
/** Builtin window services sit in a high-order layer; plugin services override by default. */
|
|
11
|
+
export const BUILTIN_SERVICE_ORDER = 500
|
|
12
|
+
|
|
13
|
+
export function registerWindowServices(bridge: ChatBridge): () => void {
|
|
14
|
+
const models: ModelApi = {
|
|
15
|
+
listModels: bridge.listModels,
|
|
16
|
+
selectModel: bridge.selectModel,
|
|
17
|
+
fetchCustomModels: bridge.fetchCustomModels,
|
|
18
|
+
saveCustomProvider: bridge.saveCustomProvider,
|
|
19
|
+
listProviderDirectory: bridge.listProviderDirectory,
|
|
20
|
+
fetchProviderModels: bridge.fetchProviderModels,
|
|
21
|
+
saveProviderKey: bridge.saveProviderKey,
|
|
22
|
+
saveProviderModels: bridge.saveProviderModels,
|
|
23
|
+
deleteProvider: bridge.deleteProvider,
|
|
24
|
+
readModelEntries: bridge.readModelEntries,
|
|
25
|
+
describeModel: bridge.describeModel,
|
|
26
|
+
saveModelEntry: bridge.saveModelEntry,
|
|
27
|
+
deleteModelEntry: bridge.deleteModelEntry,
|
|
28
|
+
}
|
|
29
|
+
const sessions: SessionsApi = {
|
|
30
|
+
listSessions: bridge.listSessions,
|
|
31
|
+
onSessionsChanged: bridge.onSessionListChanged,
|
|
32
|
+
openSession: bridge.openSession,
|
|
33
|
+
newSession: bridge.newSession,
|
|
34
|
+
archiveSession: bridge.archiveSession,
|
|
35
|
+
activeSessionId: bridge.activeSessionId,
|
|
36
|
+
}
|
|
37
|
+
const presets: PresetsApi = {
|
|
38
|
+
listPresets: bridge.listPresets,
|
|
39
|
+
currentPreset: bridge.currentPreset,
|
|
40
|
+
selectPreset: bridge.selectPreset,
|
|
41
|
+
}
|
|
42
|
+
const efforts: EffortsApi = {
|
|
43
|
+
listEfforts: bridge.listEfforts,
|
|
44
|
+
currentEffort: bridge.currentEffort,
|
|
45
|
+
selectEffort: bridge.selectEffort,
|
|
46
|
+
}
|
|
47
|
+
const defaults: DefaultsApi = {
|
|
48
|
+
listPresets: bridge.listPresets,
|
|
49
|
+
defaultPresetId: bridge.defaultPresetId,
|
|
50
|
+
setDefaultPreset: bridge.setDefaultPreset,
|
|
51
|
+
listPermissionPresets: bridge.listPermissionPresets,
|
|
52
|
+
defaultPermission: bridge.defaultPermission,
|
|
53
|
+
setDefaultPermission: bridge.setDefaultPermission,
|
|
54
|
+
themeMode: bridge.themePreference,
|
|
55
|
+
setThemeMode: bridge.setThemePreference,
|
|
56
|
+
}
|
|
57
|
+
const disposers = [
|
|
58
|
+
registerWindowService('models', models, { order: BUILTIN_SERVICE_ORDER }),
|
|
59
|
+
registerWindowService('sessions', sessions, { order: BUILTIN_SERVICE_ORDER }),
|
|
60
|
+
registerWindowService('presets', presets, { order: BUILTIN_SERVICE_ORDER }),
|
|
61
|
+
registerWindowService('efforts', efforts, { order: BUILTIN_SERVICE_ORDER }),
|
|
62
|
+
registerWindowService('defaults', defaults, { order: BUILTIN_SERVICE_ORDER }),
|
|
63
|
+
]
|
|
64
|
+
return () => {
|
|
65
|
+
for (const dispose of disposers) dispose()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function registerTodosService(todos: readonly TodoItemLike[]): () => void {
|
|
70
|
+
return registerWindowService('todos', todos, { order: BUILTIN_SERVICE_ORDER })
|
|
71
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react'
|
|
2
|
+
import { keyedRegistry } from '../kernel/registry.ts'
|
|
3
|
+
|
|
4
|
+
export const windowServices = keyedRegistry<unknown>()
|
|
5
|
+
|
|
6
|
+
export const registerWindowService = windowServices.register.bind(windowServices)
|
|
7
|
+
export const subscribeWindowServices = windowServices.subscribe.bind(windowServices)
|
|
8
|
+
|
|
9
|
+
export function hasWindowServices(required: readonly string[] | undefined): boolean {
|
|
10
|
+
if (required === undefined || required.length === 0) return true
|
|
11
|
+
return required.every(name => windowServices.has(name))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useWindowService<T>(name: string): T | undefined {
|
|
15
|
+
return useSyncExternalStore(
|
|
16
|
+
subscribeWindowServices,
|
|
17
|
+
() => (windowServices.has(name) ? (windowServices.get(name) as T) : undefined),
|
|
18
|
+
() => undefined,
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ModelsDialog } from '../dialog/models-dialog.tsx'
|
|
2
|
+
import { useWindowService } from '../window-services.ts'
|
|
3
|
+
import type { ModelApi } from '../dialog/models-dialog.tsx'
|
|
4
|
+
import type { WindowProps } from '../windows.ts'
|
|
5
|
+
|
|
6
|
+
export interface ModelsWindowProps extends WindowProps {
|
|
7
|
+
onModelSelected(provider: string, model: string): void
|
|
8
|
+
onAddProvider(): void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function ModelsWindow({ onModelSelected, onAddProvider, handleRef, ...props }: ModelsWindowProps) {
|
|
12
|
+
const api = useWindowService<ModelApi>('models')
|
|
13
|
+
if (api === undefined) return null
|
|
14
|
+
return <ModelsDialog ref={handleRef} api={api} onModelSelected={onModelSelected} onAddProvider={onAddProvider} {...props} />
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ProvidersDialog } from '../dialog/providers-dialog.tsx'
|
|
2
|
+
import { useWindowService } from '../window-services.ts'
|
|
3
|
+
import type { ModelApi } from '../dialog/models-dialog.tsx'
|
|
4
|
+
import type { WindowProps } from '../windows.ts'
|
|
5
|
+
|
|
6
|
+
export interface ProvidersWindowProps extends WindowProps {
|
|
7
|
+
onModelSelected(provider: string, model: string): void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ProvidersWindow({ onModelSelected, handleRef, ...props }: ProvidersWindowProps) {
|
|
11
|
+
const api = useWindowService<ModelApi>('models')
|
|
12
|
+
if (api === undefined) return null
|
|
13
|
+
return <ProvidersDialog ref={handleRef} api={api} onModelSelected={onModelSelected} {...props} />
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ComponentType, Ref } from 'react'
|
|
2
|
+
import { useWindowService } from '../window-services.ts'
|
|
3
|
+
import type { DialogHandle } from '../dialog/dialog.tsx'
|
|
4
|
+
import type { WindowProps } from '../windows.ts'
|
|
5
|
+
|
|
6
|
+
export interface ServiceDialogProps<Api> {
|
|
7
|
+
api: Api
|
|
8
|
+
onClose(): void
|
|
9
|
+
ref?: Ref<DialogHandle>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createServiceWindow<Api>(name: string, Dialog: ComponentType<ServiceDialogProps<Api>>): ComponentType<WindowProps> {
|
|
13
|
+
return function ServiceWindow({ handleRef, onClose }: WindowProps) {
|
|
14
|
+
const api = useWindowService<Api>(name)
|
|
15
|
+
if (api === undefined) return null
|
|
16
|
+
return <Dialog ref={handleRef} api={api} onClose={onClose} />
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SessionsDialog } from '../dialog/sessions-dialog.tsx'
|
|
2
|
+
import { useWindowService } from '../window-services.ts'
|
|
3
|
+
import type { SessionsApi } from '../dialog/sessions-dialog.tsx'
|
|
4
|
+
import type { WindowProps } from '../windows.ts'
|
|
5
|
+
|
|
6
|
+
export interface SessionsWindowProps extends WindowProps {
|
|
7
|
+
onBeforeSessionSelected?(): void
|
|
8
|
+
onSessionSelected(): void
|
|
9
|
+
onNewSession?(): void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function SessionsWindow({ onBeforeSessionSelected, onSessionSelected, onNewSession, handleRef, ...props }: SessionsWindowProps) {
|
|
13
|
+
const api = useWindowService<SessionsApi>('sessions')
|
|
14
|
+
if (api === undefined) return null
|
|
15
|
+
return (
|
|
16
|
+
<SessionsDialog
|
|
17
|
+
ref={handleRef}
|
|
18
|
+
api={api}
|
|
19
|
+
onBeforeSessionSelected={onBeforeSessionSelected}
|
|
20
|
+
onSessionSelected={onSessionSelected}
|
|
21
|
+
onNewSession={onNewSession}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ModelsWindow } from './windows/models-window.tsx'
|
|
2
|
+
import { ProvidersWindow } from './windows/providers-window.tsx'
|
|
3
|
+
import { SessionsWindow } from './windows/sessions-window.tsx'
|
|
4
|
+
import { PresetsDialog } from './dialog/presets-dialog.tsx'
|
|
5
|
+
import { EffortDialog } from './dialog/effort-dialog.tsx'
|
|
6
|
+
import { DefaultsDialog } from './dialog/defaults-dialog.tsx'
|
|
7
|
+
import { TodoDialog } from './dialog/todo-dialog.tsx'
|
|
8
|
+
import { createServiceWindow } from './windows/service-window.tsx'
|
|
9
|
+
import { registerWindow } from './windows.ts'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Builtin windows register as high-order fallback layers: a plugin
|
|
13
|
+
* contribution for the same id (default order 100) always wins regardless of
|
|
14
|
+
* registration timing, and disposing the plugin restores the builtin.
|
|
15
|
+
*/
|
|
16
|
+
export const BUILTIN_WINDOW_ORDER = 500
|
|
17
|
+
|
|
18
|
+
export interface BuiltinWindowDeps {
|
|
19
|
+
onModelSelected(provider: string, model: string): void
|
|
20
|
+
onAddProvider(): void
|
|
21
|
+
onBeforeSessionSelected(): void
|
|
22
|
+
onSessionSelected(): void
|
|
23
|
+
onNewSession(): void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function registerBuiltinWindows(deps: BuiltinWindowDeps): () => void {
|
|
27
|
+
const disposers = [
|
|
28
|
+
registerWindow({
|
|
29
|
+
id: 'models',
|
|
30
|
+
title: 'models',
|
|
31
|
+
order: BUILTIN_WINDOW_ORDER + 0,
|
|
32
|
+
required: ['models'],
|
|
33
|
+
component: props => <ModelsWindow {...props} onModelSelected={deps.onModelSelected} onAddProvider={deps.onAddProvider} />,
|
|
34
|
+
command: { name: 'models', description: 'Open model selection' },
|
|
35
|
+
}),
|
|
36
|
+
registerWindow({
|
|
37
|
+
id: 'providers',
|
|
38
|
+
title: 'providers',
|
|
39
|
+
order: BUILTIN_WINDOW_ORDER + 5,
|
|
40
|
+
required: ['models'],
|
|
41
|
+
component: props => <ProvidersWindow {...props} onModelSelected={deps.onModelSelected} />,
|
|
42
|
+
command: { name: 'providers', description: 'Open provider selection' },
|
|
43
|
+
}),
|
|
44
|
+
registerWindow({
|
|
45
|
+
id: 'sessions',
|
|
46
|
+
title: 'sessions',
|
|
47
|
+
order: BUILTIN_WINDOW_ORDER + 10,
|
|
48
|
+
required: ['sessions'],
|
|
49
|
+
component: props => (
|
|
50
|
+
<SessionsWindow
|
|
51
|
+
{...props}
|
|
52
|
+
onBeforeSessionSelected={deps.onBeforeSessionSelected}
|
|
53
|
+
onSessionSelected={deps.onSessionSelected}
|
|
54
|
+
onNewSession={deps.onNewSession}
|
|
55
|
+
/>
|
|
56
|
+
),
|
|
57
|
+
command: { name: 'sessions', description: 'Open session picker' },
|
|
58
|
+
}),
|
|
59
|
+
registerWindow({
|
|
60
|
+
id: 'preset',
|
|
61
|
+
title: 'preset',
|
|
62
|
+
order: BUILTIN_WINDOW_ORDER + 20,
|
|
63
|
+
required: ['presets'],
|
|
64
|
+
component: createServiceWindow('presets', PresetsDialog),
|
|
65
|
+
command: { name: 'preset', description: 'Select agent preset' },
|
|
66
|
+
}),
|
|
67
|
+
registerWindow({
|
|
68
|
+
id: 'effort',
|
|
69
|
+
title: 'reasoning effort',
|
|
70
|
+
order: BUILTIN_WINDOW_ORDER + 30,
|
|
71
|
+
required: ['efforts'],
|
|
72
|
+
component: createServiceWindow('efforts', EffortDialog),
|
|
73
|
+
command: { name: 'reasoning-effort', description: "Select the current model's reasoning effort" },
|
|
74
|
+
}),
|
|
75
|
+
registerWindow({
|
|
76
|
+
id: 'defaults',
|
|
77
|
+
title: 'defaults',
|
|
78
|
+
order: BUILTIN_WINDOW_ORDER + 40,
|
|
79
|
+
required: ['defaults'],
|
|
80
|
+
component: createServiceWindow('defaults', DefaultsDialog),
|
|
81
|
+
command: { name: 'defaults', description: 'Set default permission and agent preset' },
|
|
82
|
+
}),
|
|
83
|
+
registerWindow({
|
|
84
|
+
id: 'todo',
|
|
85
|
+
title: 'todo',
|
|
86
|
+
order: BUILTIN_WINDOW_ORDER + 50,
|
|
87
|
+
required: ['todos'],
|
|
88
|
+
component: createServiceWindow('todos', TodoDialog),
|
|
89
|
+
}),
|
|
90
|
+
]
|
|
91
|
+
return () => {
|
|
92
|
+
for (const dispose of disposers) dispose()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { keyedRegistry } from '../kernel/registry.ts'
|
|
2
|
+
import type { WindowContribution } from '../contract/index.ts'
|
|
3
|
+
|
|
4
|
+
export type { WindowCommandSpec, WindowContribution, WindowProps } from '../contract/index.ts'
|
|
5
|
+
|
|
6
|
+
const inner = keyedRegistry<WindowContribution>()
|
|
7
|
+
|
|
8
|
+
export function registerWindow(contribution: WindowContribution): () => void {
|
|
9
|
+
return inner.register(contribution.id, contribution, { order: contribution.order })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function listWindows(): WindowContribution[] {
|
|
13
|
+
return inner.values()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function subscribeWindows(listener: () => void): () => void {
|
|
17
|
+
return inner.subscribe(listener)
|
|
18
|
+
}
|
package/src/vendor.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import './runtime/prod-react.ts'
|
|
2
|
+
/**
|
|
3
|
+
* Shared react/ink runtime for third-party TUI plugins.
|
|
4
|
+
*
|
|
5
|
+
* Plugin authors must import react and ink from this entry instead of adding
|
|
6
|
+
* them as dependencies: the profile module graph only contains what dshtui
|
|
7
|
+
* itself links, so a separate react install forks the React instance and
|
|
8
|
+
* breaks hooks. Re-exporting keeps module identity identical to dshtui's
|
|
9
|
+
* own copy while the bundler treats both as externals.
|
|
10
|
+
*/
|
|
11
|
+
export { default as React, Children, Component, Fragment, Profiler, StrictMode, Suspense, createContext, createElement, createRef, forwardRef, isValidElement, lazy, memo, startTransition, useCallback, useContext, useDebugValue, useDeferredValue, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useOptimistic, useReducer, useRef, useState, useSyncExternalStore, useTransition } from 'react'
|
|
12
|
+
export { Box, Text, Static, useApp, useInput, useStdin, useStdout, useFocus, useFocusManager, measureElement } from 'ink'
|