@silvery/examples 0.5.6 → 0.17.4
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/dist/UPNG-Cy7ViL8f.mjs +5074 -0
- package/dist/__vite-browser-external-2447137e-BML7CYau.mjs +4 -0
- package/dist/_banner-DLPxCqVy.mjs +44 -0
- package/dist/ansi-CCE2pVS0.mjs +16397 -0
- package/dist/apng-HhhBjRGt.mjs +68 -0
- package/dist/apng-mwUQbTTF.mjs +3 -0
- package/dist/apps/aichat/index.mjs +1299 -0
- package/dist/apps/app-todo.mjs +139 -0
- package/dist/apps/async-data.mjs +204 -0
- package/dist/apps/cli-wizard.mjs +339 -0
- package/dist/apps/clipboard.mjs +198 -0
- package/dist/apps/components.mjs +864 -0
- package/dist/apps/data-explorer.mjs +483 -0
- package/dist/apps/dev-tools.mjs +397 -0
- package/dist/apps/explorer.mjs +698 -0
- package/dist/apps/gallery.mjs +766 -0
- package/dist/apps/inline-bench.mjs +115 -0
- package/dist/apps/kanban.mjs +280 -0
- package/dist/apps/layout-ref.mjs +187 -0
- package/dist/apps/outline.mjs +203 -0
- package/dist/apps/paste-demo.mjs +189 -0
- package/dist/apps/scroll.mjs +86 -0
- package/dist/apps/search-filter.mjs +287 -0
- package/dist/apps/selection.mjs +355 -0
- package/dist/apps/spatial-focus-demo.mjs +388 -0
- package/dist/apps/task-list.mjs +258 -0
- package/dist/apps/terminal-caps-demo.mjs +315 -0
- package/dist/apps/terminal.mjs +872 -0
- package/dist/apps/text-selection-demo.mjs +254 -0
- package/dist/apps/textarea.mjs +178 -0
- package/dist/apps/theme.mjs +661 -0
- package/dist/apps/transform.mjs +215 -0
- package/dist/apps/virtual-10k.mjs +422 -0
- package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
- package/dist/backends-Bahh9mKN.mjs +1179 -0
- package/dist/backends-CCtCDQ94.mjs +3 -0
- package/dist/{cli.mjs → bin/cli.mjs} +21 -25
- package/dist/chunk-BSw8zbkd.mjs +37 -0
- package/dist/components/counter.mjs +48 -0
- package/dist/components/hello.mjs +31 -0
- package/dist/components/progress-bar.mjs +59 -0
- package/dist/components/select-list.mjs +85 -0
- package/dist/components/spinner.mjs +57 -0
- package/dist/components/text-input.mjs +62 -0
- package/dist/components/virtual-list.mjs +51 -0
- package/dist/flexily-zero-adapter-UB-ra8fR.mjs +3374 -0
- package/dist/gif-BZaqPPVX.mjs +3 -0
- package/dist/gif-BtnXuxLF.mjs +71 -0
- package/dist/gifenc-CLRW41dk.mjs +728 -0
- package/dist/jsx-runtime-dMs_8fNu.mjs +241 -0
- package/dist/key-mapping-5oYQdAQE.mjs +3 -0
- package/dist/key-mapping-D4LR1go6.mjs +130 -0
- package/dist/layout/dashboard.mjs +1204 -0
- package/dist/layout/live-resize.mjs +303 -0
- package/dist/layout/overflow.mjs +70 -0
- package/dist/layout/text-layout.mjs +335 -0
- package/dist/node-NuJ94BWl.mjs +1083 -0
- package/dist/plugins-D1KtkT4a.mjs +3057 -0
- package/dist/resvg-js-C_8Wps1F.mjs +201 -0
- package/dist/src-BTEVGpd9.mjs +23538 -0
- package/dist/src-CUUOuRH6.mjs +5322 -0
- package/dist/src-CzfRafCQ.mjs +814 -0
- package/dist/usingCtx-CsEf0xO3.mjs +57 -0
- package/dist/yoga-adapter-BVtQ5OJR.mjs +237 -0
- package/package.json +19 -14
- package/_banner.tsx +0 -60
- package/apps/aichat/components.tsx +0 -469
- package/apps/aichat/index.tsx +0 -220
- package/apps/aichat/script.ts +0 -460
- package/apps/aichat/state.ts +0 -325
- package/apps/aichat/types.ts +0 -19
- package/apps/app-todo.tsx +0 -201
- package/apps/async-data.tsx +0 -196
- package/apps/cli-wizard.tsx +0 -332
- package/apps/clipboard.tsx +0 -183
- package/apps/components.tsx +0 -658
- package/apps/data-explorer.tsx +0 -490
- package/apps/dev-tools.tsx +0 -395
- package/apps/explorer.tsx +0 -731
- package/apps/gallery.tsx +0 -653
- package/apps/inline-bench.tsx +0 -138
- package/apps/kanban.tsx +0 -265
- package/apps/layout-ref.tsx +0 -173
- package/apps/outline.tsx +0 -160
- package/apps/panes/index.tsx +0 -203
- package/apps/paste-demo.tsx +0 -185
- package/apps/scroll.tsx +0 -77
- package/apps/search-filter.tsx +0 -240
- package/apps/selection.tsx +0 -342
- package/apps/spatial-focus-demo.tsx +0 -368
- package/apps/task-list.tsx +0 -271
- package/apps/terminal-caps-demo.tsx +0 -334
- package/apps/terminal.tsx +0 -800
- package/apps/text-selection-demo.tsx +0 -189
- package/apps/textarea.tsx +0 -155
- package/apps/theme.tsx +0 -515
- package/apps/transform.tsx +0 -229
- package/apps/virtual-10k.tsx +0 -405
- package/apps/vterm-demo/index.tsx +0 -216
- package/components/counter.tsx +0 -45
- package/components/hello.tsx +0 -34
- package/components/progress-bar.tsx +0 -48
- package/components/select-list.tsx +0 -50
- package/components/spinner.tsx +0 -40
- package/components/text-input.tsx +0 -57
- package/components/virtual-list.tsx +0 -52
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs.map +0 -1
- package/layout/dashboard.tsx +0 -953
- package/layout/live-resize.tsx +0 -282
- package/layout/overflow.tsx +0 -51
- package/layout/text-layout.tsx +0 -283
package/apps/aichat/state.ts
DELETED
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TEA state machine for the AI coding agent demo.
|
|
3
|
-
*
|
|
4
|
-
* Pure (state, msg) → [state, effects] — all side effects are timer-based.
|
|
5
|
-
* The update function is created via factory to close over script/mode config.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { fx } from "silvery"
|
|
9
|
-
import type { TeaResult, TimerEffect } from "silvery"
|
|
10
|
-
import type { Exchange, ScriptEntry } from "./types.js"
|
|
11
|
-
import { RANDOM_AGENT_RESPONSES, INPUT_COST_PER_M, OUTPUT_COST_PER_M, CONTEXT_WINDOW } from "./script.js"
|
|
12
|
-
|
|
13
|
-
// ============================================================================
|
|
14
|
-
// Types
|
|
15
|
-
// ============================================================================
|
|
16
|
-
|
|
17
|
-
/** Streaming phases: thinking -> streaming text -> tool calls -> done */
|
|
18
|
-
export type StreamPhase = "thinking" | "streaming" | "tools" | "done"
|
|
19
|
-
|
|
20
|
-
export type DemoState = {
|
|
21
|
-
exchanges: Exchange[]
|
|
22
|
-
scriptIdx: number
|
|
23
|
-
streamPhase: StreamPhase
|
|
24
|
-
revealFraction: number
|
|
25
|
-
done: boolean
|
|
26
|
-
compacting: boolean
|
|
27
|
-
pulse: boolean
|
|
28
|
-
ctrlDPending: boolean
|
|
29
|
-
contextBaseline: number
|
|
30
|
-
offScript: boolean
|
|
31
|
-
nextId: number
|
|
32
|
-
autoTyping: { full: string; revealed: number } | null
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export type DemoMsg =
|
|
36
|
-
| { type: "mount" }
|
|
37
|
-
| { type: "advance" }
|
|
38
|
-
| { type: "endThinking" }
|
|
39
|
-
| { type: "streamTick" }
|
|
40
|
-
| { type: "endTools" }
|
|
41
|
-
| { type: "submit"; text: string }
|
|
42
|
-
| { type: "compact" }
|
|
43
|
-
| { type: "compactDone" }
|
|
44
|
-
| { type: "pulse" }
|
|
45
|
-
| { type: "autoAdvance" }
|
|
46
|
-
| { type: "typingTick" }
|
|
47
|
-
| { type: "autoTypingDone" }
|
|
48
|
-
| { type: "respondRandom" }
|
|
49
|
-
| { type: "setCtrlDPending"; pending: boolean }
|
|
50
|
-
|
|
51
|
-
export type DemoEffect = TimerEffect<DemoMsg>
|
|
52
|
-
export type DemoResult = TeaResult<DemoState, DemoEffect>
|
|
53
|
-
|
|
54
|
-
// ============================================================================
|
|
55
|
-
// Constants
|
|
56
|
-
// ============================================================================
|
|
57
|
-
|
|
58
|
-
const INTRO_TEXT = [
|
|
59
|
-
"Coding agent simulation showcasing ListView:",
|
|
60
|
-
" • ListView — unified virtualized list with cache",
|
|
61
|
-
" • Cache mode — completed exchanges cached for performance",
|
|
62
|
-
" • OSC 8 hyperlinks — clickable file paths and URLs",
|
|
63
|
-
" • $token theme colors — semantic color tokens",
|
|
64
|
-
].join("\n")
|
|
65
|
-
|
|
66
|
-
export const INIT_STATE: DemoState = {
|
|
67
|
-
exchanges: [{ id: 0, role: "system", content: INTRO_TEXT }],
|
|
68
|
-
scriptIdx: 0,
|
|
69
|
-
streamPhase: "done",
|
|
70
|
-
revealFraction: 1,
|
|
71
|
-
done: false,
|
|
72
|
-
compacting: false,
|
|
73
|
-
pulse: false,
|
|
74
|
-
ctrlDPending: false,
|
|
75
|
-
contextBaseline: 0,
|
|
76
|
-
offScript: false,
|
|
77
|
-
nextId: 1,
|
|
78
|
-
autoTyping: null,
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ============================================================================
|
|
82
|
-
// Token & Cost Utilities
|
|
83
|
-
// ============================================================================
|
|
84
|
-
|
|
85
|
-
export function formatTokens(n: number): string {
|
|
86
|
-
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
|
|
87
|
-
if (n >= 1000) return `${(n / 1000).toFixed(1)}K`
|
|
88
|
-
return String(n)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function formatCost(inputTokens: number, outputTokens: number): string {
|
|
92
|
-
const cost = (inputTokens * INPUT_COST_PER_M + outputTokens * OUTPUT_COST_PER_M) / 1_000_000
|
|
93
|
-
if (cost < 0.01) return `$${cost.toFixed(4)}`
|
|
94
|
-
return `$${cost.toFixed(2)}`
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Compute token stats for display and compaction.
|
|
99
|
-
*
|
|
100
|
-
* Token values in the script are CUMULATIVE — each exchange's `input` represents
|
|
101
|
-
* the total context consumed at that point. So:
|
|
102
|
-
* - `currentContext`: the LAST exchange's input tokens (= current context window usage)
|
|
103
|
-
* - `totalCost`: sum of all (input + output) for cost calculation (each API call costs)
|
|
104
|
-
*/
|
|
105
|
-
export function computeCumulativeTokens(exchanges: Exchange[]): {
|
|
106
|
-
input: number
|
|
107
|
-
output: number
|
|
108
|
-
currentContext: number
|
|
109
|
-
} {
|
|
110
|
-
let input = 0
|
|
111
|
-
let output = 0
|
|
112
|
-
let currentContext = 0
|
|
113
|
-
for (const ex of exchanges) {
|
|
114
|
-
if (ex.tokens) {
|
|
115
|
-
input += ex.tokens.input
|
|
116
|
-
output += ex.tokens.output
|
|
117
|
-
if (ex.tokens.input > currentContext) currentContext = ex.tokens.input
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return { input, output, currentContext }
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** Next scripted user message for footer placeholder. */
|
|
124
|
-
export function getNextMessage(state: DemoState, script: ScriptEntry[], autoMode: boolean): string {
|
|
125
|
-
if (autoMode || state.done || state.offScript || state.streamPhase !== "done" || state.exchanges.length === 0)
|
|
126
|
-
return ""
|
|
127
|
-
const entry = script[state.scriptIdx]
|
|
128
|
-
return entry?.role === "user" ? entry.content : ""
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ============================================================================
|
|
132
|
-
// Update Factory
|
|
133
|
-
// ============================================================================
|
|
134
|
-
|
|
135
|
-
export function createDemoUpdate(script: ScriptEntry[], fastMode: boolean, autoMode: boolean) {
|
|
136
|
-
function addExchange(state: DemoState, entry: ScriptEntry): DemoState {
|
|
137
|
-
const exchange: Exchange = { ...entry, id: state.nextId }
|
|
138
|
-
return { ...state, exchanges: [...state.exchanges, exchange], nextId: state.nextId + 1 }
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function startStreaming(state: DemoState, entry: ScriptEntry): [DemoState, DemoEffect[]] {
|
|
142
|
-
const s = addExchange(state, entry)
|
|
143
|
-
if (entry.role !== "agent" || fastMode) {
|
|
144
|
-
return [{ ...s, streamPhase: "done", revealFraction: 1 }, []]
|
|
145
|
-
}
|
|
146
|
-
if (entry.thinking) {
|
|
147
|
-
return [{ ...s, streamPhase: "thinking", revealFraction: 0 }, [fx.delay(1200, { type: "endThinking" })]]
|
|
148
|
-
}
|
|
149
|
-
return [{ ...s, streamPhase: "streaming", revealFraction: 0 }, [fx.interval(50, { type: "streamTick" }, "reveal")]]
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function autoAdvanceEffects(state: DemoState): DemoEffect[] {
|
|
153
|
-
if (state.done || state.compacting || state.streamPhase !== "done") return []
|
|
154
|
-
const next = script[state.scriptIdx]
|
|
155
|
-
if (!next) return autoMode ? [fx.delay(0, { type: "autoAdvance" })] : []
|
|
156
|
-
if (autoMode || next.role !== "user") return [fx.delay(fastMode ? 100 : 400, { type: "autoAdvance" })]
|
|
157
|
-
return []
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function doAdvance(state: DemoState, extraEffects: DemoEffect[] = []): DemoResult {
|
|
161
|
-
if (state.done || state.compacting || state.streamPhase !== "done") return state
|
|
162
|
-
if (state.scriptIdx >= script.length) {
|
|
163
|
-
return autoMode ? { ...state, done: true } : state
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const entry = script[state.scriptIdx]!
|
|
167
|
-
let s: DemoState = {
|
|
168
|
-
...state,
|
|
169
|
-
scriptIdx: state.scriptIdx + 1,
|
|
170
|
-
}
|
|
171
|
-
const effects = [...extraEffects]
|
|
172
|
-
let streamFx: DemoEffect[]
|
|
173
|
-
|
|
174
|
-
;[s, streamFx] = startStreaming(s, entry)
|
|
175
|
-
effects.push(...streamFx)
|
|
176
|
-
|
|
177
|
-
if (fastMode) {
|
|
178
|
-
while (s.scriptIdx < script.length && script[s.scriptIdx]!.role !== "user") {
|
|
179
|
-
;[s, streamFx] = startStreaming({ ...s, scriptIdx: s.scriptIdx + 1 }, script[s.scriptIdx]!)
|
|
180
|
-
effects.push(...streamFx)
|
|
181
|
-
}
|
|
182
|
-
effects.push(...autoAdvanceEffects(s))
|
|
183
|
-
} else if (entry.role === "user") {
|
|
184
|
-
if (s.scriptIdx < script.length && script[s.scriptIdx]!.role === "agent") {
|
|
185
|
-
;[s, streamFx] = startStreaming({ ...s, scriptIdx: s.scriptIdx + 1 }, script[s.scriptIdx]!)
|
|
186
|
-
effects.push(...streamFx)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return [s, effects]
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return function update(state: DemoState, msg: DemoMsg): DemoResult {
|
|
194
|
-
switch (msg.type) {
|
|
195
|
-
case "mount":
|
|
196
|
-
return doAdvance(state, [fx.interval(400, { type: "pulse" }, "pulse")])
|
|
197
|
-
|
|
198
|
-
case "advance":
|
|
199
|
-
case "autoAdvance": {
|
|
200
|
-
if (autoMode && !fastMode && state.streamPhase === "done" && !state.done && !state.compacting) {
|
|
201
|
-
const next = script[state.scriptIdx]
|
|
202
|
-
if (next?.role === "user") {
|
|
203
|
-
return [
|
|
204
|
-
{ ...state, autoTyping: { full: next.content, revealed: 0 } },
|
|
205
|
-
[fx.interval(30, { type: "typingTick" }, "typing")],
|
|
206
|
-
]
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (autoMode && state.scriptIdx >= script.length && state.streamPhase === "done") {
|
|
210
|
-
return { ...state, done: true }
|
|
211
|
-
}
|
|
212
|
-
return doAdvance(state)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
case "typingTick": {
|
|
216
|
-
if (!state.autoTyping) return state
|
|
217
|
-
const next = state.autoTyping.revealed + 1
|
|
218
|
-
if (next >= state.autoTyping.full.length) {
|
|
219
|
-
return [
|
|
220
|
-
{
|
|
221
|
-
...state,
|
|
222
|
-
autoTyping: { ...state.autoTyping, revealed: state.autoTyping.full.length },
|
|
223
|
-
},
|
|
224
|
-
[fx.cancel("typing"), fx.delay(300, { type: "autoTypingDone" })],
|
|
225
|
-
]
|
|
226
|
-
}
|
|
227
|
-
return { ...state, autoTyping: { ...state.autoTyping, revealed: next } }
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
case "autoTypingDone":
|
|
231
|
-
return doAdvance({ ...state, autoTyping: null })
|
|
232
|
-
|
|
233
|
-
case "endThinking":
|
|
234
|
-
return [
|
|
235
|
-
{ ...state, streamPhase: "streaming", revealFraction: 0 },
|
|
236
|
-
[fx.interval(50, { type: "streamTick" }, "reveal")],
|
|
237
|
-
]
|
|
238
|
-
|
|
239
|
-
case "streamTick": {
|
|
240
|
-
const last = state.exchanges[state.exchanges.length - 1]
|
|
241
|
-
const rate = last?.thinking ? 0.08 : 0.12
|
|
242
|
-
const frac = Math.min(state.revealFraction + rate, 1)
|
|
243
|
-
if (frac < 1) return { ...state, revealFraction: frac }
|
|
244
|
-
|
|
245
|
-
const tools = last?.toolCalls ?? []
|
|
246
|
-
if (tools.length > 0) {
|
|
247
|
-
const s = { ...state, streamPhase: "tools" as StreamPhase, revealFraction: 1 }
|
|
248
|
-
return [s, [fx.cancel("reveal"), fx.delay(600 * tools.length, { type: "endTools" })]]
|
|
249
|
-
}
|
|
250
|
-
const s = { ...state, streamPhase: "done" as StreamPhase, revealFraction: 1 }
|
|
251
|
-
return [s, [fx.cancel("reveal"), ...autoAdvanceEffects(s)]]
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
case "endTools": {
|
|
255
|
-
const s = { ...state, streamPhase: "done" as StreamPhase }
|
|
256
|
-
return [s, autoAdvanceEffects(s)]
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
case "submit": {
|
|
260
|
-
// Fast-forward streaming if still animating
|
|
261
|
-
const base =
|
|
262
|
-
state.streamPhase !== "done"
|
|
263
|
-
? { ...state, streamPhase: "done" as StreamPhase, revealFraction: 1, autoTyping: null }
|
|
264
|
-
: state.autoTyping
|
|
265
|
-
? { ...state, autoTyping: null }
|
|
266
|
-
: state
|
|
267
|
-
const cancelEffects: DemoEffect[] =
|
|
268
|
-
state.streamPhase !== "done" ? [fx.cancel("reveal"), fx.cancel("typing")] : [fx.cancel("typing")]
|
|
269
|
-
|
|
270
|
-
// Empty submit just fast-forwards (no text to queue)
|
|
271
|
-
if (!msg.text.trim()) return [base, cancelEffects]
|
|
272
|
-
if (base.done) return base
|
|
273
|
-
|
|
274
|
-
const s = addExchange(base, {
|
|
275
|
-
role: "user",
|
|
276
|
-
content: msg.text,
|
|
277
|
-
tokens: { input: msg.text.length * 4, output: 0 },
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
if (s.scriptIdx < script.length) {
|
|
281
|
-
let nextIdx = s.scriptIdx
|
|
282
|
-
while (nextIdx < script.length && script[nextIdx]!.role === "user") nextIdx++
|
|
283
|
-
return [{ ...s, scriptIdx: nextIdx }, [...cancelEffects, fx.delay(150, { type: "autoAdvance" })]]
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return [{ ...s, offScript: true }, [...cancelEffects, fx.delay(150, { type: "respondRandom" })]]
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
case "respondRandom": {
|
|
290
|
-
const resp = RANDOM_AGENT_RESPONSES[Math.floor(Math.random() * RANDOM_AGENT_RESPONSES.length)]!
|
|
291
|
-
const [s, effects] = startStreaming(state, resp)
|
|
292
|
-
return [{ ...s, offScript: true }, effects]
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
case "compact": {
|
|
296
|
-
if (state.done || state.compacting) return state
|
|
297
|
-
const cumulative = computeCumulativeTokens(state.exchanges)
|
|
298
|
-
return [
|
|
299
|
-
{
|
|
300
|
-
...state,
|
|
301
|
-
streamPhase: "done",
|
|
302
|
-
revealFraction: 1,
|
|
303
|
-
compacting: true,
|
|
304
|
-
contextBaseline: cumulative.currentContext,
|
|
305
|
-
exchanges: state.exchanges,
|
|
306
|
-
autoTyping: null,
|
|
307
|
-
},
|
|
308
|
-
[fx.cancel("reveal"), fx.cancel("typing"), fx.delay(fastMode ? 300 : 3000, { type: "compactDone" })],
|
|
309
|
-
]
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
case "compactDone":
|
|
313
|
-
return doAdvance({ ...state, compacting: false })
|
|
314
|
-
|
|
315
|
-
case "pulse":
|
|
316
|
-
return { ...state, pulse: !state.pulse }
|
|
317
|
-
|
|
318
|
-
case "setCtrlDPending":
|
|
319
|
-
return { ...state, ctrlDPending: msg.pending }
|
|
320
|
-
|
|
321
|
-
default:
|
|
322
|
-
return state
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
package/apps/aichat/types.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/** Tool call in an exchange (Read, Edit, Bash, etc.). */
|
|
2
|
-
export interface ToolCall {
|
|
3
|
-
tool: string
|
|
4
|
-
args: string
|
|
5
|
-
output: string[]
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/** A single exchange in the conversation. */
|
|
9
|
-
export interface Exchange {
|
|
10
|
-
id: number
|
|
11
|
-
role: "user" | "agent" | "system"
|
|
12
|
-
content: string
|
|
13
|
-
thinking?: string
|
|
14
|
-
toolCalls?: ToolCall[]
|
|
15
|
-
tokens?: { input: number; output: number }
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Script entry — exchange data before id is assigned. */
|
|
19
|
-
export type ScriptEntry = Omit<Exchange, "id">
|
package/apps/app-todo.tsx
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* App Todo - Layer 3 Example
|
|
3
|
-
*
|
|
4
|
-
* Demonstrates pipe() composition with createApp(), withReact(), and
|
|
5
|
-
* withTerminal() — the canonical pattern for building full apps.
|
|
6
|
-
*
|
|
7
|
-
* The plugin system separates concerns:
|
|
8
|
-
* - createApp() — store + event handlers (what the app does)
|
|
9
|
-
* - withReact() — element binding (what the app renders)
|
|
10
|
-
* - withTerminal() — I/O binding (where the app runs)
|
|
11
|
-
*
|
|
12
|
-
* pipe() composes them left-to-right: each plugin enhances the
|
|
13
|
-
* app object, wrapping run() so the final call needs no arguments.
|
|
14
|
-
*
|
|
15
|
-
* Usage: bun examples/apps/app-todo.tsx
|
|
16
|
-
*
|
|
17
|
-
* Controls:
|
|
18
|
-
* j/k - Move cursor down/up
|
|
19
|
-
* a - Add new todo
|
|
20
|
-
* x - Toggle completed
|
|
21
|
-
* d - Delete todo
|
|
22
|
-
* Esc/q - Quit
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import React from "react"
|
|
26
|
-
import { Box, Text, Muted, Kbd } from "silvery"
|
|
27
|
-
import { createApp, useApp, type AppHandle } from "@silvery/create/create-app"
|
|
28
|
-
import { pipe, withReact, withTerminal } from "@silvery/create/plugins"
|
|
29
|
-
import { ExampleBanner, type ExampleMeta } from "../_banner.js"
|
|
30
|
-
|
|
31
|
-
export const meta: ExampleMeta = {
|
|
32
|
-
name: "Todo App",
|
|
33
|
-
description: "Layer 3: pipe() + createApp() + withReact() + withTerminal()",
|
|
34
|
-
features: ["pipe()", "createApp()", "withReact()", "withTerminal()"],
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ============================================================================
|
|
38
|
-
// Types
|
|
39
|
-
// ============================================================================
|
|
40
|
-
|
|
41
|
-
type Todo = {
|
|
42
|
-
id: number
|
|
43
|
-
text: string
|
|
44
|
-
completed: boolean
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
type State = {
|
|
48
|
-
todos: Todo[]
|
|
49
|
-
cursor: number
|
|
50
|
-
nextId: number
|
|
51
|
-
addTodo: (text: string) => void
|
|
52
|
-
toggleTodo: () => void
|
|
53
|
-
deleteTodo: () => void
|
|
54
|
-
moveCursor: (delta: number) => void
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// ============================================================================
|
|
58
|
-
// Components
|
|
59
|
-
// ============================================================================
|
|
60
|
-
|
|
61
|
-
function TodoItem({ todo, isCursor }: { todo: Todo; isCursor: boolean }) {
|
|
62
|
-
return (
|
|
63
|
-
<Box>
|
|
64
|
-
<Text color={isCursor ? "$primary" : undefined}>{isCursor ? "› " : " "}</Text>
|
|
65
|
-
<Text color={todo.completed ? "$success" : undefined} strikethrough={todo.completed}>
|
|
66
|
-
{todo.completed ? "✓" : "○"} {todo.text}
|
|
67
|
-
</Text>
|
|
68
|
-
</Box>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function TodoList() {
|
|
73
|
-
const todos = useApp((s: State) => s.todos)
|
|
74
|
-
const cursor = useApp((s: State) => s.cursor)
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<Box flexDirection="column">
|
|
78
|
-
{todos.map((todo, i) => (
|
|
79
|
-
<TodoItem key={todo.id} todo={todo} isCursor={i === cursor} />
|
|
80
|
-
))}
|
|
81
|
-
{todos.length === 0 && <Muted>No todos. Press 'a' to add one.</Muted>}
|
|
82
|
-
</Box>
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function TodoApp() {
|
|
87
|
-
return (
|
|
88
|
-
<Box flexDirection="column" padding={1}>
|
|
89
|
-
<TodoList />
|
|
90
|
-
<Text> </Text>
|
|
91
|
-
<Muted>
|
|
92
|
-
<Kbd>j/k</Kbd> move <Kbd>x</Kbd> toggle <Kbd>a</Kbd> add <Kbd>d</Kbd> delete <Kbd>Esc/q</Kbd> quit
|
|
93
|
-
</Muted>
|
|
94
|
-
</Box>
|
|
95
|
-
)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// ============================================================================
|
|
99
|
-
// App — pipe() composition
|
|
100
|
-
// ============================================================================
|
|
101
|
-
|
|
102
|
-
// 1. createApp() defines the store and event handlers
|
|
103
|
-
const baseApp = createApp<Record<string, unknown>, State>(
|
|
104
|
-
() => (set, get) => ({
|
|
105
|
-
todos: [
|
|
106
|
-
{ id: 1, text: "Learn silvery plugin composition", completed: true },
|
|
107
|
-
{ id: 2, text: "Build an app with pipe()", completed: false },
|
|
108
|
-
{ id: 3, text: "Ship to production", completed: false },
|
|
109
|
-
],
|
|
110
|
-
cursor: 0,
|
|
111
|
-
nextId: 4,
|
|
112
|
-
|
|
113
|
-
addTodo: (text: string) =>
|
|
114
|
-
set((s) => ({
|
|
115
|
-
todos: [...s.todos, { id: s.nextId, text, completed: false }],
|
|
116
|
-
nextId: s.nextId + 1,
|
|
117
|
-
})),
|
|
118
|
-
|
|
119
|
-
toggleTodo: () =>
|
|
120
|
-
set((s) => ({
|
|
121
|
-
todos: s.todos.map((t, i) => (i === s.cursor ? { ...t, completed: !t.completed } : t)),
|
|
122
|
-
})),
|
|
123
|
-
|
|
124
|
-
deleteTodo: () =>
|
|
125
|
-
set((s) => {
|
|
126
|
-
const newTodos = s.todos.filter((_, i) => i !== s.cursor)
|
|
127
|
-
return {
|
|
128
|
-
todos: newTodos,
|
|
129
|
-
cursor: Math.min(s.cursor, newTodos.length - 1),
|
|
130
|
-
}
|
|
131
|
-
}),
|
|
132
|
-
|
|
133
|
-
moveCursor: (delta: number) =>
|
|
134
|
-
set((s) => ({
|
|
135
|
-
cursor: Math.max(0, Math.min(s.cursor + delta, s.todos.length - 1)),
|
|
136
|
-
})),
|
|
137
|
-
}),
|
|
138
|
-
|
|
139
|
-
{
|
|
140
|
-
"term:key": (data, ctx) => {
|
|
141
|
-
const { input: k, key } = data as {
|
|
142
|
-
input: string
|
|
143
|
-
key: { escape: boolean }
|
|
144
|
-
}
|
|
145
|
-
const state = ctx.get() as State
|
|
146
|
-
if (key.escape) return "exit"
|
|
147
|
-
switch (k) {
|
|
148
|
-
case "j":
|
|
149
|
-
state.moveCursor(1)
|
|
150
|
-
break
|
|
151
|
-
case "k":
|
|
152
|
-
state.moveCursor(-1)
|
|
153
|
-
break
|
|
154
|
-
case "x":
|
|
155
|
-
state.toggleTodo()
|
|
156
|
-
break
|
|
157
|
-
case "d":
|
|
158
|
-
if (state.todos.length > 0) state.deleteTodo()
|
|
159
|
-
break
|
|
160
|
-
case "a":
|
|
161
|
-
state.addTodo(`New todo ${state.nextId}`)
|
|
162
|
-
break
|
|
163
|
-
case "q":
|
|
164
|
-
return "exit"
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
// 2. pipe() composes plugins left-to-right:
|
|
171
|
-
// - withReact() binds the element, so run() needs no JSX argument
|
|
172
|
-
// - withTerminal() binds stdin/stdout, so run() needs no options
|
|
173
|
-
// Note: pipe() type composition requires casts at plugin boundaries
|
|
174
|
-
// because AppDefinition's typed run() doesn't structurally match
|
|
175
|
-
// the generic RunnableApp constraint used by plugins.
|
|
176
|
-
const app = pipe(
|
|
177
|
-
baseApp as any,
|
|
178
|
-
withReact(
|
|
179
|
-
<ExampleBanner meta={meta} controls="j/k move x toggle a add d delete Esc/q quit">
|
|
180
|
-
<TodoApp />
|
|
181
|
-
</ExampleBanner>,
|
|
182
|
-
),
|
|
183
|
-
withTerminal(process as any),
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
// ============================================================================
|
|
187
|
-
// Main
|
|
188
|
-
// ============================================================================
|
|
189
|
-
|
|
190
|
-
async function main() {
|
|
191
|
-
// 3. run() needs no arguments — element and terminal are already bound
|
|
192
|
-
const handle = (await app.run()) as AppHandle<State>
|
|
193
|
-
|
|
194
|
-
await handle.waitUntilExit()
|
|
195
|
-
|
|
196
|
-
console.log("\nFinal state:", handle.store.getState().todos.length, "todos")
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (import.meta.main) {
|
|
200
|
-
main().catch(console.error)
|
|
201
|
-
}
|