saeeol 1.2.1 → 1.2.2
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/package.json +11 -11
- package/src/cli/cmd/tui/component/dialog/dialog-agent.tsx +32 -0
- package/src/cli/cmd/tui/component/dialog/dialog-command.tsx +190 -0
- package/src/cli/cmd/tui/component/dialog/dialog-console-org.tsx +103 -0
- package/src/cli/cmd/tui/component/dialog/dialog-go-upsell.tsx +159 -0
- package/src/cli/cmd/tui/component/dialog/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog/dialog-model.tsx +238 -0
- package/src/cli/cmd/tui/component/dialog/dialog-provider.tsx +343 -0
- package/src/cli/cmd/tui/component/dialog/dialog-session-delete-failed.tsx +103 -0
- package/src/cli/cmd/tui/component/dialog/dialog-session-list.tsx +301 -0
- package/src/cli/cmd/tui/component/dialog/dialog-session-rename.tsx +35 -0
- package/src/cli/cmd/tui/component/dialog/dialog-skill.tsx +37 -0
- package/src/cli/cmd/tui/component/dialog/dialog-stash.tsx +87 -0
- package/src/cli/cmd/tui/component/dialog/dialog-status.tsx +190 -0
- package/src/cli/cmd/tui/component/dialog/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog/dialog-theme-list.tsx +50 -0
- package/src/cli/cmd/tui/component/dialog/dialog-variant.tsx +39 -0
- package/src/cli/cmd/tui/component/dialog/dialog-workspace-create.tsx +200 -0
- package/src/cli/cmd/tui/component/dialog/dialog-workspace-unavailable.tsx +81 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +1 -32
- package/src/cli/cmd/tui/component/dialog-command.tsx +1 -190
- package/src/cli/cmd/tui/component/dialog-console-org.tsx +1 -103
- package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +1 -159
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +1 -86
- package/src/cli/cmd/tui/component/dialog-model.tsx +1 -238
- package/src/cli/cmd/tui/component/dialog-provider.tsx +1 -343
- package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +1 -103
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +1 -301
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +1 -35
- package/src/cli/cmd/tui/component/dialog-skill.tsx +1 -37
- package/src/cli/cmd/tui/component/dialog-stash.tsx +1 -87
- package/src/cli/cmd/tui/component/dialog-status.tsx +1 -190
- package/src/cli/cmd/tui/component/dialog-tag.tsx +1 -44
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +1 -50
- package/src/cli/cmd/tui/component/dialog-variant.tsx +1 -39
- package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +1 -200
- package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +1 -81
- package/src/tool/apply_patch.ts +1 -334
- package/src/tool/bash.ts +1 -656
- package/src/tool/core/external-directory.ts +55 -0
- package/src/tool/core/invalid.ts +21 -0
- package/src/tool/core/recall.ts +164 -0
- package/src/tool/core/recall.txt +12 -0
- package/src/tool/core/schema.ts +16 -0
- package/src/tool/core/tool.ts +162 -0
- package/src/tool/core/truncate.ts +160 -0
- package/src/tool/core/truncation-dir.ts +4 -0
- package/src/tool/diagnostics.ts +1 -20
- package/src/tool/edit-replacers.ts +1 -288
- package/src/tool/edit-utils.ts +1 -86
- package/src/tool/edit.ts +1 -262
- package/src/tool/external-directory.ts +1 -55
- package/src/tool/file/apply_patch.ts +334 -0
- package/src/tool/file/apply_patch.txt +33 -0
- package/src/tool/file/bash.ts +656 -0
- package/src/tool/file/bash.txt +119 -0
- package/src/tool/file/edit-replacers.ts +288 -0
- package/src/tool/file/edit-utils.ts +86 -0
- package/src/tool/file/edit.ts +262 -0
- package/src/tool/file/edit.txt +10 -0
- package/src/tool/file/read.ts +389 -0
- package/src/tool/file/read.txt +14 -0
- package/src/tool/file/write.ts +114 -0
- package/src/tool/file/write.txt +8 -0
- package/src/tool/glob.ts +1 -115
- package/src/tool/grep.ts +1 -151
- package/src/tool/integration/diagnostics.ts +20 -0
- package/src/tool/integration/lsp.ts +113 -0
- package/src/tool/integration/lsp.txt +24 -0
- package/src/tool/integration/mcp-exa.ts +73 -0
- package/src/tool/integration/package.ts +168 -0
- package/src/tool/integration/registry.ts +375 -0
- package/src/tool/invalid.ts +1 -21
- package/src/tool/lsp.ts +1 -113
- package/src/tool/mcp-exa.ts +1 -73
- package/src/tool/package.ts +1 -168
- package/src/tool/plan.ts +1 -30
- package/src/tool/question.ts +1 -52
- package/src/tool/read.ts +1 -389
- package/src/tool/recall.ts +1 -164
- package/src/tool/registry.ts +1 -375
- package/src/tool/schema.ts +1 -16
- package/src/tool/search/glob.ts +115 -0
- package/src/tool/search/glob.txt +6 -0
- package/src/tool/search/grep.ts +151 -0
- package/src/tool/search/grep.txt +8 -0
- package/src/tool/search/warpgrep.ts +107 -0
- package/src/tool/search/warpgrep.txt +10 -0
- package/src/tool/search/webfetch.ts +202 -0
- package/src/tool/search/webfetch.txt +13 -0
- package/src/tool/search/websearch.ts +71 -0
- package/src/tool/search/websearch.txt +14 -0
- package/src/tool/skill.ts +1 -91
- package/src/tool/task.ts +1 -197
- package/src/tool/todo.ts +1 -62
- package/src/tool/tool.ts +1 -162
- package/src/tool/truncate.ts +1 -160
- package/src/tool/truncation-dir.ts +1 -4
- package/src/tool/warpgrep.ts +1 -107
- package/src/tool/webfetch.ts +1 -202
- package/src/tool/websearch.ts +1 -71
- package/src/tool/workflow/plan-enter.txt +14 -0
- package/src/tool/workflow/plan-exit.txt +13 -0
- package/src/tool/workflow/plan.ts +30 -0
- package/src/tool/workflow/question.ts +52 -0
- package/src/tool/workflow/question.txt +11 -0
- package/src/tool/workflow/skill.ts +91 -0
- package/src/tool/workflow/skill.txt +5 -0
- package/src/tool/workflow/task.ts +197 -0
- package/src/tool/workflow/task.txt +57 -0
- package/src/tool/workflow/todo.ts +62 -0
- package/src/tool/workflow/todowrite.txt +167 -0
- package/src/tool/write.ts +1 -114
|
@@ -1,159 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { useKeyboard } from "@opentui/solid"
|
|
3
|
-
import open from "open"
|
|
4
|
-
import { createSignal, onCleanup, onMount } from "solid-js"
|
|
5
|
-
import { selectedForeground, useTheme } from "@tui/context/theme"
|
|
6
|
-
import { useDialog, type DialogContext } from "@tui/ui/dialog"
|
|
7
|
-
import { Link } from "@tui/ui/link"
|
|
8
|
-
import { GoLogo } from "./logo"
|
|
9
|
-
import { BgPulse, type BgPulseMask } from "./bg-pulse"
|
|
10
|
-
|
|
11
|
-
const GO_URL = "https://saeeol.ai/go"
|
|
12
|
-
const PAD_X = 3
|
|
13
|
-
const PAD_TOP_OUTER = 1
|
|
14
|
-
|
|
15
|
-
export type DialogGoUpsellProps = {
|
|
16
|
-
onClose?: (dontShowAgain?: boolean) => void
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function subscribe(props: DialogGoUpsellProps, dialog: ReturnType<typeof useDialog>) {
|
|
20
|
-
open(GO_URL).catch(() => {})
|
|
21
|
-
props.onClose?.()
|
|
22
|
-
dialog.clear()
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function dismiss(props: DialogGoUpsellProps, dialog: ReturnType<typeof useDialog>) {
|
|
26
|
-
props.onClose?.(true)
|
|
27
|
-
dialog.clear()
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function DialogGoUpsell(props: DialogGoUpsellProps) {
|
|
31
|
-
const dialog = useDialog()
|
|
32
|
-
const { theme } = useTheme()
|
|
33
|
-
const fg = selectedForeground(theme)
|
|
34
|
-
const [selected, setSelected] = createSignal<"dismiss" | "subscribe">("subscribe")
|
|
35
|
-
const [center, setCenter] = createSignal<{ x: number; y: number } | undefined>()
|
|
36
|
-
const [masks, setMasks] = createSignal<BgPulseMask[]>([])
|
|
37
|
-
let content: BoxRenderable | undefined
|
|
38
|
-
let logoBox: BoxRenderable | undefined
|
|
39
|
-
let headingBox: BoxRenderable | undefined
|
|
40
|
-
let descBox: BoxRenderable | undefined
|
|
41
|
-
let buttonsBox: BoxRenderable | undefined
|
|
42
|
-
|
|
43
|
-
const sync = () => {
|
|
44
|
-
if (!content || !logoBox) return
|
|
45
|
-
setCenter({
|
|
46
|
-
x: logoBox.x - content.x + logoBox.width / 2,
|
|
47
|
-
y: logoBox.y - content.y + logoBox.height / 2 + PAD_TOP_OUTER,
|
|
48
|
-
})
|
|
49
|
-
const next: BgPulseMask[] = []
|
|
50
|
-
const baseY = PAD_TOP_OUTER
|
|
51
|
-
for (const b of [headingBox, descBox, buttonsBox]) {
|
|
52
|
-
if (!b) continue
|
|
53
|
-
next.push({
|
|
54
|
-
x: b.x - content.x,
|
|
55
|
-
y: b.y - content.y + baseY,
|
|
56
|
-
width: b.width,
|
|
57
|
-
height: b.height,
|
|
58
|
-
pad: 2,
|
|
59
|
-
strength: 0.78,
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
setMasks(next)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
onMount(() => {
|
|
66
|
-
sync()
|
|
67
|
-
for (const b of [content, logoBox, headingBox, descBox, buttonsBox]) b?.on("resize", sync)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
onCleanup(() => {
|
|
71
|
-
for (const b of [content, logoBox, headingBox, descBox, buttonsBox]) b?.off("resize", sync)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
useKeyboard((evt) => {
|
|
75
|
-
if (evt.name === "left" || evt.name === "right" || evt.name === "tab") {
|
|
76
|
-
setSelected((s) => (s === "subscribe" ? "dismiss" : "subscribe"))
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
if (evt.name === "return") {
|
|
80
|
-
evt.preventDefault()
|
|
81
|
-
evt.stopPropagation()
|
|
82
|
-
if (selected() === "subscribe") subscribe(props, dialog)
|
|
83
|
-
else dismiss(props, dialog)
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<box ref={(item: BoxRenderable) => (content = item)}>
|
|
89
|
-
<box position="absolute" top={-PAD_TOP_OUTER} left={0} right={0} bottom={0} zIndex={0}>
|
|
90
|
-
<BgPulse centerX={center()?.x} centerY={center()?.y} masks={masks()} />
|
|
91
|
-
</box>
|
|
92
|
-
<box paddingLeft={PAD_X} paddingRight={PAD_X} paddingBottom={1} gap={1}>
|
|
93
|
-
<box ref={(item: BoxRenderable) => (headingBox = item)} flexDirection="row" justifyContent="space-between">
|
|
94
|
-
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
|
95
|
-
Free limit reached
|
|
96
|
-
</text>
|
|
97
|
-
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
|
98
|
-
esc
|
|
99
|
-
</text>
|
|
100
|
-
</box>
|
|
101
|
-
<box ref={(item: BoxRenderable) => (descBox = item)} gap={0}>
|
|
102
|
-
<box flexDirection="row">
|
|
103
|
-
<text fg={theme.textMuted}>Subscribe to </text>
|
|
104
|
-
<text attributes={TextAttributes.BOLD} fg={theme.textMuted}>
|
|
105
|
-
Saeeol Go
|
|
106
|
-
</text>
|
|
107
|
-
<text fg={theme.textMuted}> for reliable access to the</text>
|
|
108
|
-
</box>
|
|
109
|
-
<text fg={theme.textMuted}>best open-source models, starting at $5/month.</text>
|
|
110
|
-
</box>
|
|
111
|
-
<box alignItems="center" gap={1} paddingBottom={1}>
|
|
112
|
-
<box ref={(item: BoxRenderable) => (logoBox = item)}>
|
|
113
|
-
<GoLogo />
|
|
114
|
-
</box>
|
|
115
|
-
<Link href={GO_URL} fg={theme.primary} />
|
|
116
|
-
</box>
|
|
117
|
-
<box ref={(item: BoxRenderable) => (buttonsBox = item)} flexDirection="row" justifyContent="space-between">
|
|
118
|
-
<box
|
|
119
|
-
paddingLeft={2}
|
|
120
|
-
paddingRight={2}
|
|
121
|
-
backgroundColor={selected() === "dismiss" ? theme.primary : RGBA.fromInts(0, 0, 0, 0)}
|
|
122
|
-
onMouseOver={() => setSelected("dismiss")}
|
|
123
|
-
onMouseUp={() => dismiss(props, dialog)}
|
|
124
|
-
>
|
|
125
|
-
<text
|
|
126
|
-
fg={selected() === "dismiss" ? fg : theme.textMuted}
|
|
127
|
-
attributes={selected() === "dismiss" ? TextAttributes.BOLD : undefined}
|
|
128
|
-
>
|
|
129
|
-
don't show again
|
|
130
|
-
</text>
|
|
131
|
-
</box>
|
|
132
|
-
<box
|
|
133
|
-
paddingLeft={2}
|
|
134
|
-
paddingRight={2}
|
|
135
|
-
backgroundColor={selected() === "subscribe" ? theme.primary : RGBA.fromInts(0, 0, 0, 0)}
|
|
136
|
-
onMouseOver={() => setSelected("subscribe")}
|
|
137
|
-
onMouseUp={() => subscribe(props, dialog)}
|
|
138
|
-
>
|
|
139
|
-
<text
|
|
140
|
-
fg={selected() === "subscribe" ? fg : theme.text}
|
|
141
|
-
attributes={selected() === "subscribe" ? TextAttributes.BOLD : undefined}
|
|
142
|
-
>
|
|
143
|
-
subscribe
|
|
144
|
-
</text>
|
|
145
|
-
</box>
|
|
146
|
-
</box>
|
|
147
|
-
</box>
|
|
148
|
-
</box>
|
|
149
|
-
)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
DialogGoUpsell.show = (dialog: DialogContext) => {
|
|
153
|
-
return new Promise<boolean>((resolve) => {
|
|
154
|
-
dialog.replace(
|
|
155
|
-
() => <DialogGoUpsell onClose={(dontShow) => resolve(dontShow ?? false)} />,
|
|
156
|
-
() => resolve(false),
|
|
157
|
-
)
|
|
158
|
-
})
|
|
159
|
-
}
|
|
1
|
+
export * from "./dialog/dialog-go-upsell"
|
|
@@ -1,86 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { useLocal } from "@tui/context/local"
|
|
3
|
-
import { useSync } from "@tui/context/sync"
|
|
4
|
-
import { map, pipe, entries, sortBy } from "remeda"
|
|
5
|
-
import { DialogSelect, type DialogSelectRef, type DialogSelectOption } from "@tui/ui/dialog-select"
|
|
6
|
-
import { useTheme } from "../context/theme"
|
|
7
|
-
import { Keybind } from "@/util/keybind"
|
|
8
|
-
import { TextAttributes } from "@opentui/core"
|
|
9
|
-
import { useSDK } from "@tui/context/sdk"
|
|
10
|
-
|
|
11
|
-
function Status(props: { enabled: boolean; loading: boolean }) {
|
|
12
|
-
const { theme } = useTheme()
|
|
13
|
-
if (props.loading) {
|
|
14
|
-
return <span style={{ fg: theme.textMuted }}>⋯ Loading</span>
|
|
15
|
-
}
|
|
16
|
-
if (props.enabled) {
|
|
17
|
-
return <span style={{ fg: theme.success, attributes: TextAttributes.BOLD }}>✓ Enabled</span>
|
|
18
|
-
}
|
|
19
|
-
return <span style={{ fg: theme.textMuted }}>○ Disabled</span>
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function DialogMcp() {
|
|
23
|
-
const local = useLocal()
|
|
24
|
-
const sync = useSync()
|
|
25
|
-
const sdk = useSDK()
|
|
26
|
-
const [, setRef] = createSignal<DialogSelectRef<unknown>>()
|
|
27
|
-
const [loading, setLoading] = createSignal<string | null>(null)
|
|
28
|
-
|
|
29
|
-
const options = createMemo(() => {
|
|
30
|
-
// Track sync data and loading state to trigger re-render when they change
|
|
31
|
-
const mcpData = sync.data.mcp
|
|
32
|
-
const loadingMcp = loading()
|
|
33
|
-
|
|
34
|
-
return pipe(
|
|
35
|
-
mcpData ?? {},
|
|
36
|
-
entries(),
|
|
37
|
-
sortBy(([name]) => name),
|
|
38
|
-
map(([name, status]) => ({
|
|
39
|
-
value: name,
|
|
40
|
-
title: name,
|
|
41
|
-
description: status.status === "failed" ? "failed" : status.status,
|
|
42
|
-
footer: <Status enabled={local.mcp.isEnabled(name)} loading={loadingMcp === name} />,
|
|
43
|
-
category: undefined,
|
|
44
|
-
})),
|
|
45
|
-
)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
const keybinds = createMemo(() => [
|
|
49
|
-
{
|
|
50
|
-
keybind: Keybind.parse("space")[0],
|
|
51
|
-
title: "toggle",
|
|
52
|
-
onTrigger: async (option: DialogSelectOption<string>) => {
|
|
53
|
-
// Prevent toggling while an operation is already in progress
|
|
54
|
-
if (loading() !== null) return
|
|
55
|
-
|
|
56
|
-
setLoading(option.value)
|
|
57
|
-
try {
|
|
58
|
-
await local.mcp.toggle(option.value)
|
|
59
|
-
// Refresh MCP status from server
|
|
60
|
-
const status = await sdk.client.mcp.status()
|
|
61
|
-
if (status.data) {
|
|
62
|
-
sync.set("mcp", status.data)
|
|
63
|
-
} else {
|
|
64
|
-
console.error("Failed to refresh MCP status: no data returned")
|
|
65
|
-
}
|
|
66
|
-
} catch (error) {
|
|
67
|
-
console.error("Failed to toggle MCP:", error)
|
|
68
|
-
} finally {
|
|
69
|
-
setLoading(null)
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
])
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<DialogSelect
|
|
77
|
-
ref={setRef}
|
|
78
|
-
title="MCPs"
|
|
79
|
-
options={options()}
|
|
80
|
-
keybind={keybinds()}
|
|
81
|
-
onSelect={(_option) => {
|
|
82
|
-
// Don't close on select, only on escape
|
|
83
|
-
}}
|
|
84
|
-
/>
|
|
85
|
-
)
|
|
86
|
-
}
|
|
1
|
+
export * from "./dialog/dialog-mcp"
|
|
@@ -1,238 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { createEffect, createMemo, createSignal, Show } from "solid-js"
|
|
3
|
-
import { useLocal } from "@tui/context/local"
|
|
4
|
-
import { useSync } from "@tui/context/sync"
|
|
5
|
-
import { map, pipe, flatMap, entries, filter, sortBy, take, groupBy } from "remeda"
|
|
6
|
-
import { DialogSelect } from "@tui/ui/dialog-select"
|
|
7
|
-
import { useDialog } from "@tui/ui/dialog"
|
|
8
|
-
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
|
|
9
|
-
import { DialogVariant } from "./dialog-variant"
|
|
10
|
-
import { useKeybind } from "../context/keybind"
|
|
11
|
-
import type { Model } from "@saeeol/sdk/v2"
|
|
12
|
-
import * as fuzzysort from "fuzzysort"
|
|
13
|
-
import { useConnected } from "./use-connected"
|
|
14
|
-
import { ModelInfoPanel } from "@/saeeol/components/model-info-panel"
|
|
15
|
-
import { t } from "@/util/i18n"
|
|
16
|
-
|
|
17
|
-
export function DialogModel(props: { providerID?: string }) {
|
|
18
|
-
const local = useLocal()
|
|
19
|
-
const sync = useSync()
|
|
20
|
-
const dialog = useDialog()
|
|
21
|
-
const keybind = useKeybind()
|
|
22
|
-
const [query, setQuery] = createSignal("")
|
|
23
|
-
const dimensions = useTerminalDimensions()
|
|
24
|
-
|
|
25
|
-
const connected = useConnected()
|
|
26
|
-
const providers = createDialogProviderOptions()
|
|
27
|
-
// Memoize anything that iterates all Saeeol models to avoid calculating it for
|
|
28
|
-
// each Saeeol model and tanking the UI at a couple hundred models
|
|
29
|
-
const saeeolRank = createMemo(() => {
|
|
30
|
-
const provider = sync.data.provider.find((provider) => provider.id === "saeeol")
|
|
31
|
-
const models = provider?.models ?? {}
|
|
32
|
-
return new Map(Object.entries(models).map(([id, info]) => [id, info.recommendedIndex ?? Infinity] as const))
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
const showExtra = createMemo(() => connected() && !props.providerID)
|
|
36
|
-
const wide = createMemo(() => dimensions().width >= 108)
|
|
37
|
-
const [preview, setPreview] = createSignal<{
|
|
38
|
-
model: Model
|
|
39
|
-
provider: string
|
|
40
|
-
}>()
|
|
41
|
-
|
|
42
|
-
const lookup = (providerID: string, modelID: string) => {
|
|
43
|
-
const provider = sync.data.provider.find((x) => x.id === providerID)
|
|
44
|
-
const model = provider?.models?.[modelID]
|
|
45
|
-
if (!provider || !model) return
|
|
46
|
-
return {
|
|
47
|
-
model,
|
|
48
|
-
provider: provider.name,
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
createEffect(() => {
|
|
53
|
-
dialog.setSize(wide() ? "xlarge" : "large")
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
createEffect(() => {
|
|
57
|
-
const current = local.model.current()
|
|
58
|
-
if (!current) return
|
|
59
|
-
const next = lookup(current.providerID, current.modelID)
|
|
60
|
-
if (!next) return
|
|
61
|
-
setPreview(next)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
const options = createMemo(() => {
|
|
65
|
-
const needle = query().trim()
|
|
66
|
-
const favorites = connected() ? local.model.favorite() : []
|
|
67
|
-
const recents = local.model.recent()
|
|
68
|
-
|
|
69
|
-
function toOptions(items: typeof favorites, category: string) {
|
|
70
|
-
if (!showExtra()) return []
|
|
71
|
-
return items.flatMap((item) => {
|
|
72
|
-
const provider = sync.data.provider.find((x) => x.id === item.providerID)
|
|
73
|
-
if (!provider) return []
|
|
74
|
-
const model = provider.models?.[item.modelID]
|
|
75
|
-
if (!model) return []
|
|
76
|
-
return [
|
|
77
|
-
{
|
|
78
|
-
key: item,
|
|
79
|
-
value: { providerID: provider.id, modelID: model.id },
|
|
80
|
-
title: model.name ?? item.modelID,
|
|
81
|
-
description: provider.name,
|
|
82
|
-
category,
|
|
83
|
-
disabled: provider.id === "saeeol" && model.id.includes("-nano"),
|
|
84
|
-
footer: model.cost?.input === 0 && provider.id === "saeeol" ? "Free" : undefined,
|
|
85
|
-
onSelect: () => {
|
|
86
|
-
onSelect(provider.id, model.id)
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
]
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const favoriteOptions = toOptions(favorites, t("cmd.model.favorites"))
|
|
94
|
-
const recentOptions = toOptions(
|
|
95
|
-
recents.filter(
|
|
96
|
-
(item) => !favorites.some((fav) => fav.providerID === item.providerID && fav.modelID === item.modelID),
|
|
97
|
-
),
|
|
98
|
-
t("cmd.model.recent"),
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
const providerOptions = pipe(
|
|
102
|
-
sync.data.provider,
|
|
103
|
-
sortBy(
|
|
104
|
-
(provider) => provider.id !== "saeeol",
|
|
105
|
-
(provider) => provider.name,
|
|
106
|
-
),
|
|
107
|
-
flatMap((provider) =>
|
|
108
|
-
pipe(
|
|
109
|
-
provider.models,
|
|
110
|
-
entries(),
|
|
111
|
-
filter(([_, info]) => info.status !== "deprecated"),
|
|
112
|
-
filter(([_, info]) => (props.providerID ? info.providerID === props.providerID : true)),
|
|
113
|
-
map(([model, info]) => ({
|
|
114
|
-
value: { providerID: provider.id, modelID: model },
|
|
115
|
-
title: info.name ?? model,
|
|
116
|
-
description: favorites.some((item) => item.providerID === provider.id && item.modelID === model)
|
|
117
|
-
? "(Favorite)"
|
|
118
|
-
: undefined,
|
|
119
|
-
category: connected()
|
|
120
|
-
? provider.id === "saeeol" && info.recommendedIndex !== undefined
|
|
121
|
-
? t("cmd.model.recommended")
|
|
122
|
-
: provider.name
|
|
123
|
-
: undefined,
|
|
124
|
-
disabled: provider.id === "saeeol" && model.includes("-nano"),
|
|
125
|
-
footer: info.cost?.input === 0 && provider.id === "saeeol" ? "Free" : undefined,
|
|
126
|
-
onSelect() {
|
|
127
|
-
onSelect(provider.id, model)
|
|
128
|
-
},
|
|
129
|
-
})),
|
|
130
|
-
filter((x) => {
|
|
131
|
-
if (favorites.some((item) => item.providerID === x.value.providerID && item.modelID === x.value.modelID))
|
|
132
|
-
return false
|
|
133
|
-
if (recents.some((item) => item.providerID === x.value.providerID && item.modelID === x.value.modelID))
|
|
134
|
-
return false
|
|
135
|
-
return true
|
|
136
|
-
}),
|
|
137
|
-
sortBy(
|
|
138
|
-
(x) => (x.value.providerID === "saeeol" ? (saeeolRank().get(x.value.modelID) ?? Infinity) : 0),
|
|
139
|
-
(x) => x.footer !== "Free",
|
|
140
|
-
(x) => x.title,
|
|
141
|
-
),
|
|
142
|
-
),
|
|
143
|
-
),
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
const popularProviders = !connected()
|
|
147
|
-
? pipe(
|
|
148
|
-
providers(),
|
|
149
|
-
map((option) => ({
|
|
150
|
-
...option,
|
|
151
|
-
category: t("cmd.model.popular_providers"),
|
|
152
|
-
})),
|
|
153
|
-
take(6),
|
|
154
|
-
)
|
|
155
|
-
: []
|
|
156
|
-
if (needle) {
|
|
157
|
-
const rank = <U extends { title: string; category?: string }>(items: U[]) =>
|
|
158
|
-
fuzzysort.go(needle, items, { keys: ["title", "category"] }).map((x) => x.obj)
|
|
159
|
-
// rank within each provider category to preserve category order
|
|
160
|
-
const rankedProviders = pipe(
|
|
161
|
-
providerOptions,
|
|
162
|
-
groupBy((x) => x.category ?? ""),
|
|
163
|
-
entries(),
|
|
164
|
-
flatMap(([_, items]) => rank(items)),
|
|
165
|
-
)
|
|
166
|
-
return [...rank(favoriteOptions), ...rank(recentOptions), ...rankedProviders, ...rank(popularProviders)]
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return [...favoriteOptions, ...recentOptions, ...providerOptions, ...popularProviders]
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
const provider = createMemo(() =>
|
|
173
|
-
props.providerID ? sync.data.provider.find((x) => x.id === props.providerID) : null,
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
const title = createMemo(() => {
|
|
177
|
-
const value = provider()
|
|
178
|
-
if (!value) return t("cmd.model.title")
|
|
179
|
-
return value.name
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
function onSelect(providerID: string, modelID: string) {
|
|
183
|
-
local.model.set({ providerID, modelID }, { recent: true })
|
|
184
|
-
const list = local.model.variant.list()
|
|
185
|
-
const cur = local.model.variant.selected()
|
|
186
|
-
if (cur === "default" || (cur && list.includes(cur))) {
|
|
187
|
-
dialog.clear()
|
|
188
|
-
return
|
|
189
|
-
}
|
|
190
|
-
if (list.length > 0) {
|
|
191
|
-
dialog.replace(() => <DialogVariant />)
|
|
192
|
-
return
|
|
193
|
-
}
|
|
194
|
-
dialog.clear()
|
|
195
|
-
}
|
|
196
|
-
return (
|
|
197
|
-
<box flexDirection="row">
|
|
198
|
-
<box flexGrow={1} flexShrink={1}>
|
|
199
|
-
<DialogSelect<ReturnType<typeof options>[number]["value"]>
|
|
200
|
-
options={options()}
|
|
201
|
-
keybind={[
|
|
202
|
-
{
|
|
203
|
-
keybind: keybind.all.model_provider_list?.[0],
|
|
204
|
-
title: connected() ? t("cmd.model.connect_provider") : t("cmd.model.view_all_providers"),
|
|
205
|
-
onTrigger() {
|
|
206
|
-
dialog.replace(() => <DialogProvider />)
|
|
207
|
-
},
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
keybind: keybind.all.model_favorite_toggle?.[0],
|
|
211
|
-
title: t("cmd.model.favorite"),
|
|
212
|
-
disabled: !connected(),
|
|
213
|
-
onTrigger: (option) => {
|
|
214
|
-
local.model.toggleFavorite(option.value as { providerID: string; modelID: string })
|
|
215
|
-
},
|
|
216
|
-
},
|
|
217
|
-
]}
|
|
218
|
-
onFilter={setQuery}
|
|
219
|
-
onMove={(option) => {
|
|
220
|
-
if (typeof option.value === "string") {
|
|
221
|
-
setPreview(undefined)
|
|
222
|
-
return
|
|
223
|
-
}
|
|
224
|
-
const next = lookup(option.value.providerID, option.value.modelID)
|
|
225
|
-
if (!next) return
|
|
226
|
-
setPreview(next)
|
|
227
|
-
}}
|
|
228
|
-
skipFilter={true}
|
|
229
|
-
title={title()}
|
|
230
|
-
current={local.model.current()}
|
|
231
|
-
/>
|
|
232
|
-
</box>
|
|
233
|
-
<Show when={wide() && preview()}>
|
|
234
|
-
{(item) => <ModelInfoPanel model={item().model} provider={item().provider} />}
|
|
235
|
-
</Show>
|
|
236
|
-
</box>
|
|
237
|
-
)
|
|
238
|
-
}
|
|
1
|
+
export * from "./dialog/dialog-model"
|