codeblog-app 2.2.6 → 2.3.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/package.json +9 -7
- package/src/ai/__tests__/chat.test.ts +11 -2
- package/src/ai/__tests__/compat.test.ts +46 -0
- package/src/ai/__tests__/home.ai-stream.integration.test.ts +77 -0
- package/src/ai/__tests__/provider-registry.test.ts +61 -0
- package/src/ai/__tests__/provider.test.ts +58 -18
- package/src/ai/__tests__/stream-events.test.ts +152 -0
- package/src/ai/chat.ts +200 -88
- package/src/ai/configure.ts +13 -4
- package/src/ai/models.ts +26 -0
- package/src/ai/provider-registry.ts +150 -0
- package/src/ai/provider.ts +99 -137
- package/src/ai/stream-events.ts +64 -0
- package/src/ai/tools.ts +10 -6
- package/src/ai/types.ts +105 -0
- package/src/auth/index.ts +3 -1
- package/src/auth/oauth.ts +17 -2
- package/src/cli/__tests__/commands.test.ts +6 -2
- package/src/cli/cmd/ai.ts +10 -0
- package/src/cli/cmd/setup.ts +275 -5
- package/src/cli/ui.ts +131 -24
- package/src/config/index.ts +38 -1
- package/src/index.ts +4 -1
- package/src/mcp/__tests__/client.test.ts +2 -2
- package/src/mcp/__tests__/e2e.ts +10 -6
- package/src/mcp/client.ts +33 -63
- package/src/storage/chat.ts +3 -1
- package/src/tui/__tests__/input-intent.test.ts +27 -0
- package/src/tui/__tests__/stream-assembler.test.ts +33 -0
- package/src/tui/ai-stream.ts +28 -0
- package/src/tui/app.tsx +27 -1
- package/src/tui/commands.ts +41 -7
- package/src/tui/context/theme.tsx +2 -1
- package/src/tui/input-intent.ts +26 -0
- package/src/tui/routes/home.tsx +590 -190
- package/src/tui/routes/setup.tsx +20 -8
- package/src/tui/stream-assembler.ts +49 -0
- package/src/util/log.ts +3 -1
- package/tsconfig.json +1 -1
package/src/tui/routes/setup.tsx
CHANGED
|
@@ -10,12 +10,22 @@ const HC = {
|
|
|
10
10
|
dim: "#999999",
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
function resolveThemeDef(name: string) {
|
|
14
|
+
const fallback = THEMES.codeblog ?? Object.values(THEMES).find(Boolean)
|
|
15
|
+
if (!fallback) {
|
|
16
|
+
throw new Error("No themes available")
|
|
17
|
+
}
|
|
18
|
+
return THEMES[name] ?? fallback
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
export function ThemeSetup() {
|
|
14
22
|
const theme = useTheme()
|
|
15
23
|
const modes = ["dark", "light"] as const
|
|
16
24
|
const [step, setStep] = createSignal<"mode" | "theme">("mode")
|
|
17
25
|
const [modeIdx, setModeIdx] = createSignal(0)
|
|
18
26
|
const [themeIdx, setThemeIdx] = createSignal(0)
|
|
27
|
+
const getThemeName = (index: number) => THEME_NAMES[index] ?? "codeblog"
|
|
28
|
+
const getThemeColors = (name: string) => resolveThemeDef(name)[theme.mode]
|
|
19
29
|
|
|
20
30
|
useKeyboard((evt) => {
|
|
21
31
|
if (step() === "mode") {
|
|
@@ -30,7 +40,7 @@ export function ThemeSetup() {
|
|
|
30
40
|
return
|
|
31
41
|
}
|
|
32
42
|
if (evt.name === "return") {
|
|
33
|
-
theme.setMode(modes[modeIdx()])
|
|
43
|
+
theme.setMode(modes[modeIdx()] ?? "dark")
|
|
34
44
|
setStep("theme")
|
|
35
45
|
evt.preventDefault()
|
|
36
46
|
return
|
|
@@ -41,14 +51,14 @@ export function ThemeSetup() {
|
|
|
41
51
|
if (evt.name === "up" || evt.name === "k") {
|
|
42
52
|
const next = (themeIdx() - 1 + THEME_NAMES.length) % THEME_NAMES.length
|
|
43
53
|
setThemeIdx(next)
|
|
44
|
-
theme.set(
|
|
54
|
+
theme.set(getThemeName(next))
|
|
45
55
|
evt.preventDefault()
|
|
46
56
|
return
|
|
47
57
|
}
|
|
48
58
|
if (evt.name === "down" || evt.name === "j") {
|
|
49
59
|
const next = (themeIdx() + 1) % THEME_NAMES.length
|
|
50
60
|
setThemeIdx(next)
|
|
51
|
-
theme.set(
|
|
61
|
+
theme.set(getThemeName(next))
|
|
52
62
|
evt.preventDefault()
|
|
53
63
|
return
|
|
54
64
|
}
|
|
@@ -104,7 +114,7 @@ export function ThemeSetup() {
|
|
|
104
114
|
</text>
|
|
105
115
|
<box height={1} />
|
|
106
116
|
{THEME_NAMES.map((name, i) => {
|
|
107
|
-
const c =
|
|
117
|
+
const c = getThemeColors(name)
|
|
108
118
|
return (
|
|
109
119
|
<box flexDirection="row" paddingLeft={2}>
|
|
110
120
|
<text fg={themeIdx() === i ? c.primary : theme.colors.textMuted}>
|
|
@@ -136,22 +146,24 @@ export function ThemeSetup() {
|
|
|
136
146
|
|
|
137
147
|
export function ThemePicker(props: { onDone: () => void }) {
|
|
138
148
|
const theme = useTheme()
|
|
139
|
-
const [idx, setIdx] = createSignal(THEME_NAMES.indexOf(theme.name))
|
|
149
|
+
const [idx, setIdx] = createSignal(Math.max(0, THEME_NAMES.indexOf(theme.name)))
|
|
140
150
|
const [tab, setTab] = createSignal<"theme" | "mode">("theme")
|
|
151
|
+
const getThemeName = (index: number) => THEME_NAMES[index] ?? "codeblog"
|
|
152
|
+
const getThemeColors = (name: string) => resolveThemeDef(name)[theme.mode]
|
|
141
153
|
|
|
142
154
|
useKeyboard((evt) => {
|
|
143
155
|
if (tab() === "theme") {
|
|
144
156
|
if (evt.name === "up" || evt.name === "k") {
|
|
145
157
|
const next = (idx() - 1 + THEME_NAMES.length) % THEME_NAMES.length
|
|
146
158
|
setIdx(next)
|
|
147
|
-
theme.set(
|
|
159
|
+
theme.set(getThemeName(next))
|
|
148
160
|
evt.preventDefault()
|
|
149
161
|
return
|
|
150
162
|
}
|
|
151
163
|
if (evt.name === "down" || evt.name === "j") {
|
|
152
164
|
const next = (idx() + 1) % THEME_NAMES.length
|
|
153
165
|
setIdx(next)
|
|
154
|
-
theme.set(
|
|
166
|
+
theme.set(getThemeName(next))
|
|
155
167
|
evt.preventDefault()
|
|
156
168
|
return
|
|
157
169
|
}
|
|
@@ -204,7 +216,7 @@ export function ThemePicker(props: { onDone: () => void }) {
|
|
|
204
216
|
</text>
|
|
205
217
|
<box height={1} />
|
|
206
218
|
{THEME_NAMES.map((name, i) => {
|
|
207
|
-
const c =
|
|
219
|
+
const c = getThemeColors(name)
|
|
208
220
|
return (
|
|
209
221
|
<box flexDirection="row">
|
|
210
222
|
<text fg={idx() === i ? c.primary : theme.colors.textMuted}>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export class TuiStreamAssembler {
|
|
2
|
+
private text = ""
|
|
3
|
+
private finished = false
|
|
4
|
+
private lastSeq = 0
|
|
5
|
+
|
|
6
|
+
reset() {
|
|
7
|
+
this.text = ""
|
|
8
|
+
this.finished = false
|
|
9
|
+
this.lastSeq = 0
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getText() {
|
|
13
|
+
return this.text
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pushDelta(delta: string, seq?: number) {
|
|
17
|
+
if (!delta || this.finished) return this.text
|
|
18
|
+
if (seq !== undefined && seq <= this.lastSeq) return this.text
|
|
19
|
+
if (seq !== undefined) this.lastSeq = seq
|
|
20
|
+
|
|
21
|
+
this.text += delta
|
|
22
|
+
return this.text
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
pushFinal(finalText: string) {
|
|
26
|
+
if (!finalText) {
|
|
27
|
+
this.finished = true
|
|
28
|
+
return this.text
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!this.text) {
|
|
32
|
+
this.text = finalText
|
|
33
|
+
this.finished = true
|
|
34
|
+
return this.text
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (finalText.length >= this.text.length && finalText.includes(this.text)) {
|
|
38
|
+
this.text = finalText
|
|
39
|
+
this.finished = true
|
|
40
|
+
return this.text
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (finalText.length > this.text.length) {
|
|
44
|
+
this.text = finalText
|
|
45
|
+
}
|
|
46
|
+
this.finished = true
|
|
47
|
+
return this.text
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/util/log.ts
CHANGED
|
@@ -52,9 +52,11 @@ export namespace Log {
|
|
|
52
52
|
if (options.level) level = options.level
|
|
53
53
|
cleanup(Global.Path.log)
|
|
54
54
|
if (options.print) return
|
|
55
|
+
const isoNow = new Date().toISOString()
|
|
56
|
+
const timestampBase = isoNow.split(".")[0] ?? isoNow
|
|
55
57
|
logpath = path.join(
|
|
56
58
|
Global.Path.log,
|
|
57
|
-
options.dev ? "dev.log" :
|
|
59
|
+
options.dev ? "dev.log" : `${timestampBase.replace(/:/g, "")}.log`,
|
|
58
60
|
)
|
|
59
61
|
const logfile = Bun.file(logpath)
|
|
60
62
|
await fs.truncate(logpath).catch(() => {})
|