tjs-lang 0.2.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/CONTEXT.md +594 -0
- package/LICENSE +190 -0
- package/README.md +220 -0
- package/bin/benchmarks.ts +351 -0
- package/bin/dev.ts +205 -0
- package/bin/docs.js +170 -0
- package/bin/install-cursor.sh +71 -0
- package/bin/install-vscode.sh +71 -0
- package/bin/select-local-models.d.ts +1 -0
- package/bin/select-local-models.js +28 -0
- package/bin/select-local-models.ts +31 -0
- package/demo/autocomplete.test.ts +232 -0
- package/demo/docs.json +186 -0
- package/demo/examples.test.ts +598 -0
- package/demo/index.html +91 -0
- package/demo/src/autocomplete.ts +482 -0
- package/demo/src/capabilities.ts +859 -0
- package/demo/src/demo-nav.ts +2097 -0
- package/demo/src/examples.test.ts +161 -0
- package/demo/src/examples.ts +476 -0
- package/demo/src/imports.test.ts +196 -0
- package/demo/src/imports.ts +421 -0
- package/demo/src/index.ts +639 -0
- package/demo/src/module-store.ts +635 -0
- package/demo/src/module-sw.ts +132 -0
- package/demo/src/playground.ts +949 -0
- package/demo/src/service-host.ts +389 -0
- package/demo/src/settings.ts +440 -0
- package/demo/src/style.ts +280 -0
- package/demo/src/tjs-playground.ts +1605 -0
- package/demo/src/ts-examples.ts +478 -0
- package/demo/src/ts-playground.ts +1092 -0
- package/demo/static/favicon.svg +30 -0
- package/demo/static/photo-1.jpg +0 -0
- package/demo/static/photo-2.jpg +0 -0
- package/demo/static/texts/ai-history.txt +9 -0
- package/demo/static/texts/coffee-origins.txt +9 -0
- package/demo/static/texts/renewable-energy.txt +9 -0
- package/dist/index.js +256 -0
- package/dist/index.js.map +37 -0
- package/dist/tjs-batteries.js +4 -0
- package/dist/tjs-batteries.js.map +15 -0
- package/dist/tjs-full.js +256 -0
- package/dist/tjs-full.js.map +37 -0
- package/dist/tjs-transpiler.js +220 -0
- package/dist/tjs-transpiler.js.map +21 -0
- package/dist/tjs-vm.js +4 -0
- package/dist/tjs-vm.js.map +14 -0
- package/docs/CNAME +1 -0
- package/docs/favicon.svg +30 -0
- package/docs/index.html +91 -0
- package/docs/index.js +10468 -0
- package/docs/index.js.map +92 -0
- package/docs/photo-1.jpg +0 -0
- package/docs/photo-1.webp +0 -0
- package/docs/photo-2.jpg +0 -0
- package/docs/photo-2.webp +0 -0
- package/docs/texts/ai-history.txt +9 -0
- package/docs/texts/coffee-origins.txt +9 -0
- package/docs/texts/renewable-energy.txt +9 -0
- package/docs/tjs-lang.svg +31 -0
- package/docs/tosijs-agent.svg +31 -0
- package/editors/README.md +325 -0
- package/editors/ace/ajs-mode.js +328 -0
- package/editors/ace/ajs-mode.ts +269 -0
- package/editors/ajs-syntax.ts +212 -0
- package/editors/build-grammars.ts +510 -0
- package/editors/codemirror/ajs-language.js +287 -0
- package/editors/codemirror/ajs-language.ts +1447 -0
- package/editors/codemirror/autocomplete.test.ts +531 -0
- package/editors/codemirror/component.ts +404 -0
- package/editors/monaco/ajs-monarch.js +243 -0
- package/editors/monaco/ajs-monarch.ts +225 -0
- package/editors/tjs-syntax.ts +115 -0
- package/editors/vscode/language-configuration.json +37 -0
- package/editors/vscode/package.json +65 -0
- package/editors/vscode/syntaxes/ajs-injection.tmLanguage.json +107 -0
- package/editors/vscode/syntaxes/ajs.tmLanguage.json +252 -0
- package/editors/vscode/syntaxes/tjs.tmLanguage.json +333 -0
- package/package.json +83 -0
- package/src/cli/commands/check.ts +41 -0
- package/src/cli/commands/convert.ts +133 -0
- package/src/cli/commands/emit.ts +260 -0
- package/src/cli/commands/run.ts +68 -0
- package/src/cli/commands/test.ts +194 -0
- package/src/cli/commands/types.ts +20 -0
- package/src/cli/create-app.ts +236 -0
- package/src/cli/playground.ts +250 -0
- package/src/cli/tjs.ts +166 -0
- package/src/cli/tjsx.ts +160 -0
- package/tjs-lang.svg +31 -0
|
@@ -0,0 +1,949 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* playground.ts - Interactive AsyncJS playground component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { elements, Component, PartsMap } from 'tosijs'
|
|
6
|
+
|
|
7
|
+
import { icons } from 'tosijs-ui'
|
|
8
|
+
|
|
9
|
+
import { EditorView, basicSetup } from 'codemirror'
|
|
10
|
+
import { EditorState, Compartment } from '@codemirror/state'
|
|
11
|
+
import { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language'
|
|
12
|
+
import { oneDark } from '@codemirror/theme-one-dark'
|
|
13
|
+
import { ajsEditorExtension } from '../../editors/codemirror/ajs-language'
|
|
14
|
+
|
|
15
|
+
import { examples, type Example } from './examples'
|
|
16
|
+
import {
|
|
17
|
+
AgentVM,
|
|
18
|
+
transpile,
|
|
19
|
+
type TranspileResult,
|
|
20
|
+
coreAtoms,
|
|
21
|
+
batteryAtoms,
|
|
22
|
+
} from '../../src'
|
|
23
|
+
import { getStoreCapabilityDefault } from '../../src/batteries'
|
|
24
|
+
import {
|
|
25
|
+
buildLLMCapability,
|
|
26
|
+
buildLLMBattery,
|
|
27
|
+
getSettings,
|
|
28
|
+
type LLMProvider,
|
|
29
|
+
} from './capabilities'
|
|
30
|
+
|
|
31
|
+
// Default LM Studio URL
|
|
32
|
+
const DEFAULT_LM_STUDIO_URL = 'http://localhost:1234/v1'
|
|
33
|
+
|
|
34
|
+
// Initialize default LM Studio URL on HTTP if not already set
|
|
35
|
+
function initLLMDefaults() {
|
|
36
|
+
const existingUrl = localStorage.getItem('customLlmUrl')
|
|
37
|
+
const hasExistingUrl = existingUrl && existingUrl.trim() !== ''
|
|
38
|
+
|
|
39
|
+
// On HTTP, default to LM Studio URL if nothing is configured
|
|
40
|
+
if (!hasExistingUrl && window.location.protocol === 'http:') {
|
|
41
|
+
localStorage.setItem('customLlmUrl', DEFAULT_LM_STUDIO_URL)
|
|
42
|
+
console.log('🤖 Defaulting to LM Studio endpoint:', DEFAULT_LM_STUDIO_URL)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Set defaults on module load
|
|
47
|
+
initLLMDefaults()
|
|
48
|
+
|
|
49
|
+
const { div, button, span, select, option, optgroup, input } = elements
|
|
50
|
+
|
|
51
|
+
// localStorage key for custom examples
|
|
52
|
+
const STORAGE_KEY = 'agent-playground-examples'
|
|
53
|
+
|
|
54
|
+
// Default code for new examples
|
|
55
|
+
const NEW_EXAMPLE_CODE = `// Write your AsyncJS code here
|
|
56
|
+
function myFunction({ name = 'World' }) {
|
|
57
|
+
let message = template({ tmpl: 'Hello, {{name}}!', vars: { name } })
|
|
58
|
+
return { message }
|
|
59
|
+
}`
|
|
60
|
+
|
|
61
|
+
// Load custom examples from localStorage
|
|
62
|
+
function loadCustomExamples(): Example[] {
|
|
63
|
+
try {
|
|
64
|
+
const stored = localStorage.getItem(STORAGE_KEY)
|
|
65
|
+
return stored ? JSON.parse(stored) : []
|
|
66
|
+
} catch {
|
|
67
|
+
return []
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Save custom examples to localStorage
|
|
72
|
+
function saveCustomExamples(customExamples: Example[]): void {
|
|
73
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(customExamples))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Parts interface for typed access
|
|
77
|
+
interface PlaygroundParts extends PartsMap {
|
|
78
|
+
editorContainer: HTMLElement
|
|
79
|
+
resultContainer: HTMLElement
|
|
80
|
+
statusBar: HTMLElement
|
|
81
|
+
tabResult: HTMLButtonElement
|
|
82
|
+
tabAst: HTMLButtonElement
|
|
83
|
+
tabTrace: HTMLButtonElement
|
|
84
|
+
copyBtn: HTMLButtonElement
|
|
85
|
+
runBtn: HTMLButtonElement
|
|
86
|
+
newBtn: HTMLButtonElement
|
|
87
|
+
saveBtn: HTMLButtonElement
|
|
88
|
+
deleteBtn: HTMLButtonElement
|
|
89
|
+
exampleSelect: HTMLSelectElement
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class Playground extends Component<PlaygroundParts> {
|
|
93
|
+
private editor: EditorView | null = null
|
|
94
|
+
private themeCompartment = new Compartment()
|
|
95
|
+
private darkModeObserver: MutationObserver | null = null
|
|
96
|
+
private vm = new AgentVM({ ...coreAtoms, ...batteryAtoms })
|
|
97
|
+
private currentTab = 'result'
|
|
98
|
+
private lastResult: any = null
|
|
99
|
+
private lastAst: TranspileResult | null = null
|
|
100
|
+
private lastError: string | null = null
|
|
101
|
+
private isRunning = false
|
|
102
|
+
private customExamples: Example[] = loadCustomExamples()
|
|
103
|
+
private currentExampleIndex: number = -1 // -1 = new/unsaved
|
|
104
|
+
|
|
105
|
+
// Use Shadow DOM styles (static styleSpec)
|
|
106
|
+
// CSS variables for theming
|
|
107
|
+
static styleSpec = {
|
|
108
|
+
':host': {
|
|
109
|
+
display: 'block',
|
|
110
|
+
height: '100%',
|
|
111
|
+
position: 'relative',
|
|
112
|
+
// Light mode defaults
|
|
113
|
+
'--pg-bg': '#f3f4f6',
|
|
114
|
+
'--pg-border': '#e5e7eb',
|
|
115
|
+
'--pg-text': '#1f2937',
|
|
116
|
+
'--pg-brand': '#3d4a6b',
|
|
117
|
+
'--pg-success': '#16a34a',
|
|
118
|
+
'--pg-error': '#dc2626',
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
// Dark mode - detect from parent
|
|
122
|
+
':host-context(.darkmode)': {
|
|
123
|
+
'--pg-bg': '#1f2937',
|
|
124
|
+
'--pg-border': '#374151',
|
|
125
|
+
'--pg-text': '#f3f4f6',
|
|
126
|
+
'--pg-brand': '#818cf8',
|
|
127
|
+
'--pg-success': '#4ade80',
|
|
128
|
+
'--pg-error': '#f87171',
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
'.playground': {
|
|
132
|
+
display: 'flex',
|
|
133
|
+
flexDirection: 'column',
|
|
134
|
+
height: '100%',
|
|
135
|
+
gap: '10px',
|
|
136
|
+
color: 'var(--pg-text)',
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
'.playground-toolbar': {
|
|
140
|
+
display: 'flex',
|
|
141
|
+
alignItems: 'center',
|
|
142
|
+
gap: '10px',
|
|
143
|
+
padding: '10px',
|
|
144
|
+
background: 'var(--pg-bg)',
|
|
145
|
+
borderRadius: '6px',
|
|
146
|
+
flexWrap: 'wrap',
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
'.run-btn': {
|
|
150
|
+
display: 'flex',
|
|
151
|
+
alignItems: 'center',
|
|
152
|
+
gap: '4px',
|
|
153
|
+
padding: '6px 12px',
|
|
154
|
+
background: 'var(--pg-brand)',
|
|
155
|
+
color: 'white',
|
|
156
|
+
border: 'none',
|
|
157
|
+
borderRadius: '6px',
|
|
158
|
+
cursor: 'pointer',
|
|
159
|
+
fontWeight: '500',
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
'.iconic': {
|
|
163
|
+
background: 'none',
|
|
164
|
+
border: 'none',
|
|
165
|
+
cursor: 'pointer',
|
|
166
|
+
padding: '5px',
|
|
167
|
+
borderRadius: '6px',
|
|
168
|
+
color: 'var(--pg-text)',
|
|
169
|
+
display: 'flex',
|
|
170
|
+
alignItems: 'center',
|
|
171
|
+
justifyContent: 'center',
|
|
172
|
+
transition: 'background 0.15s',
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
'.iconic:hover': {
|
|
176
|
+
background: 'rgba(99, 102, 241, 0.15)',
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
'.playground-main': {
|
|
180
|
+
display: 'flex',
|
|
181
|
+
flex: '1 1 auto',
|
|
182
|
+
gap: '10px',
|
|
183
|
+
minHeight: 0,
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
'.playground-editor': {
|
|
187
|
+
flex: '1 1 50%',
|
|
188
|
+
minWidth: '300px',
|
|
189
|
+
minHeight: '300px',
|
|
190
|
+
border: '1px solid var(--pg-border)',
|
|
191
|
+
borderRadius: '6px',
|
|
192
|
+
overflow: 'hidden',
|
|
193
|
+
position: 'relative',
|
|
194
|
+
background: 'var(--pg-bg)',
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
// Critical CodeMirror styles
|
|
198
|
+
'.playground-editor .cm-editor': {
|
|
199
|
+
height: '100%',
|
|
200
|
+
position: 'absolute',
|
|
201
|
+
top: 0,
|
|
202
|
+
left: 0,
|
|
203
|
+
right: 0,
|
|
204
|
+
bottom: 0,
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
'.playground-editor .cm-scroller': {
|
|
208
|
+
outline: 'none',
|
|
209
|
+
fontFamily: "Menlo, Monaco, Consolas, 'Courier New', monospace",
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
'.playground-output': {
|
|
213
|
+
flex: '1 1 50%',
|
|
214
|
+
display: 'flex',
|
|
215
|
+
flexDirection: 'column',
|
|
216
|
+
minWidth: '300px',
|
|
217
|
+
border: '1px solid var(--pg-border)',
|
|
218
|
+
borderRadius: '6px',
|
|
219
|
+
overflow: 'hidden',
|
|
220
|
+
background: 'var(--pg-bg)',
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
'.playground-tabs': {
|
|
224
|
+
display: 'flex',
|
|
225
|
+
background: 'var(--pg-bg)',
|
|
226
|
+
borderBottom: '1px solid var(--pg-border)',
|
|
227
|
+
alignItems: 'center',
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
'.playground-tabs .elastic': {
|
|
231
|
+
flex: '1 1 auto',
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
'.copy-btn': {
|
|
235
|
+
padding: '4px 8px',
|
|
236
|
+
marginRight: '4px',
|
|
237
|
+
border: 'none',
|
|
238
|
+
background: 'transparent',
|
|
239
|
+
cursor: 'pointer',
|
|
240
|
+
color: 'var(--pg-text)',
|
|
241
|
+
opacity: 0.6,
|
|
242
|
+
transition: 'opacity 0.15s',
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
'.copy-btn:hover': {
|
|
246
|
+
opacity: 1,
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
'.copy-btn.copied': {
|
|
250
|
+
color: 'var(--pg-success)',
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
'.playground-tab': {
|
|
254
|
+
padding: '5px 10px',
|
|
255
|
+
border: 'none',
|
|
256
|
+
background: 'transparent',
|
|
257
|
+
cursor: 'pointer',
|
|
258
|
+
color: 'var(--pg-text)',
|
|
259
|
+
opacity: 0.7,
|
|
260
|
+
transition: 'opacity 0.15s, background 0.15s',
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
'.playground-tab:hover': {
|
|
264
|
+
opacity: 1,
|
|
265
|
+
background: 'rgba(99, 102, 241, 0.1)',
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
'.playground-tab.active': {
|
|
269
|
+
opacity: 1,
|
|
270
|
+
borderBottom: '2px solid var(--pg-brand)',
|
|
271
|
+
marginBottom: '-1px',
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
'.playground-result': {
|
|
275
|
+
flex: '1 1 auto',
|
|
276
|
+
overflow: 'auto',
|
|
277
|
+
padding: '10px',
|
|
278
|
+
fontFamily: "'SF Mono', Monaco, 'Cascadia Code', Consolas, monospace",
|
|
279
|
+
fontSize: '13px',
|
|
280
|
+
whiteSpace: 'pre-wrap',
|
|
281
|
+
wordBreak: 'break-word',
|
|
282
|
+
color: 'var(--pg-text)',
|
|
283
|
+
background: 'var(--pg-bg)',
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
'.playground-result.error': {
|
|
287
|
+
color: 'var(--pg-error)',
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
'.playground-result.success': {
|
|
291
|
+
color: 'var(--pg-success)',
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
'.loading-spinner': {
|
|
295
|
+
display: 'inline-block',
|
|
296
|
+
width: '14px',
|
|
297
|
+
height: '14px',
|
|
298
|
+
border: '2px solid var(--pg-border)',
|
|
299
|
+
borderTopColor: 'var(--pg-brand)',
|
|
300
|
+
borderRadius: '50%',
|
|
301
|
+
animation: 'spin 0.8s linear infinite',
|
|
302
|
+
verticalAlign: 'middle',
|
|
303
|
+
marginRight: '8px',
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
'@keyframes spin': {
|
|
307
|
+
to: { transform: 'rotate(360deg)' },
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
'.playground-status': {
|
|
311
|
+
padding: '5px 10px',
|
|
312
|
+
background: 'var(--pg-bg)',
|
|
313
|
+
borderTop: '1px solid var(--pg-border)',
|
|
314
|
+
fontSize: '12px',
|
|
315
|
+
opacity: 0.7,
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
// Select dropdown styling
|
|
319
|
+
select: {
|
|
320
|
+
padding: '4px 8px',
|
|
321
|
+
borderRadius: '4px',
|
|
322
|
+
border: '1px solid var(--pg-border)',
|
|
323
|
+
background: 'var(--pg-bg)',
|
|
324
|
+
color: 'var(--pg-text)',
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
'@media (max-width: 768px)': {
|
|
328
|
+
'.playground-main': {
|
|
329
|
+
flexDirection: 'column',
|
|
330
|
+
},
|
|
331
|
+
'.playground-editor, .playground-output': {
|
|
332
|
+
flex: '1 1 auto',
|
|
333
|
+
minHeight: '250px',
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
content = () => [
|
|
339
|
+
div(
|
|
340
|
+
{ class: 'playground' },
|
|
341
|
+
// Toolbar
|
|
342
|
+
div(
|
|
343
|
+
{ class: 'playground-toolbar' },
|
|
344
|
+
button(
|
|
345
|
+
{
|
|
346
|
+
part: 'runBtn',
|
|
347
|
+
class: 'run-btn',
|
|
348
|
+
},
|
|
349
|
+
icons.play({ size: 16 }),
|
|
350
|
+
'Run'
|
|
351
|
+
),
|
|
352
|
+
|
|
353
|
+
button(
|
|
354
|
+
{
|
|
355
|
+
part: 'newBtn',
|
|
356
|
+
class: 'iconic',
|
|
357
|
+
title: 'New',
|
|
358
|
+
},
|
|
359
|
+
icons.plus()
|
|
360
|
+
),
|
|
361
|
+
|
|
362
|
+
button(
|
|
363
|
+
{
|
|
364
|
+
part: 'saveBtn',
|
|
365
|
+
class: 'iconic',
|
|
366
|
+
title: 'Save Example',
|
|
367
|
+
},
|
|
368
|
+
icons.save()
|
|
369
|
+
),
|
|
370
|
+
|
|
371
|
+
button(
|
|
372
|
+
{
|
|
373
|
+
part: 'deleteBtn',
|
|
374
|
+
class: 'iconic',
|
|
375
|
+
title: 'Delete Custom Example',
|
|
376
|
+
style: { display: 'none' },
|
|
377
|
+
},
|
|
378
|
+
icons.trash()
|
|
379
|
+
),
|
|
380
|
+
|
|
381
|
+
span({ style: { flex: '1' } }),
|
|
382
|
+
|
|
383
|
+
select(
|
|
384
|
+
{
|
|
385
|
+
part: 'exampleSelect',
|
|
386
|
+
style: { padding: '4px 8px', borderRadius: '4px' },
|
|
387
|
+
},
|
|
388
|
+
option({ value: 'new' }, '-- New --')
|
|
389
|
+
)
|
|
390
|
+
),
|
|
391
|
+
|
|
392
|
+
// Main area
|
|
393
|
+
div(
|
|
394
|
+
{ class: 'playground-main' },
|
|
395
|
+
div({ part: 'editorContainer', class: 'playground-editor' }),
|
|
396
|
+
|
|
397
|
+
div(
|
|
398
|
+
{ class: 'playground-output' },
|
|
399
|
+
div(
|
|
400
|
+
{ class: 'playground-tabs' },
|
|
401
|
+
button(
|
|
402
|
+
{ part: 'tabResult', class: 'playground-tab active' },
|
|
403
|
+
'Result'
|
|
404
|
+
),
|
|
405
|
+
button({ part: 'tabAst', class: 'playground-tab' }, 'AST'),
|
|
406
|
+
button({ part: 'tabTrace', class: 'playground-tab' }, 'Trace'),
|
|
407
|
+
span({ class: 'elastic' }),
|
|
408
|
+
button(
|
|
409
|
+
{
|
|
410
|
+
part: 'copyBtn',
|
|
411
|
+
class: 'copy-btn',
|
|
412
|
+
title: 'Copy to clipboard',
|
|
413
|
+
},
|
|
414
|
+
icons.copy({ size: 16 })
|
|
415
|
+
)
|
|
416
|
+
),
|
|
417
|
+
div(
|
|
418
|
+
{ part: 'resultContainer', class: 'playground-result' },
|
|
419
|
+
'// Run code to see results'
|
|
420
|
+
),
|
|
421
|
+
div({ part: 'statusBar', class: 'playground-status' }, 'Ready')
|
|
422
|
+
)
|
|
423
|
+
)
|
|
424
|
+
),
|
|
425
|
+
]
|
|
426
|
+
|
|
427
|
+
connectedCallback() {
|
|
428
|
+
super.connectedCallback()
|
|
429
|
+
|
|
430
|
+
// Bind event handlers manually for Shadow DOM
|
|
431
|
+
this.parts.runBtn.addEventListener('click', this.runCode)
|
|
432
|
+
this.parts.newBtn.addEventListener('click', this.newExample)
|
|
433
|
+
this.parts.saveBtn.addEventListener('click', this.saveExample)
|
|
434
|
+
this.parts.deleteBtn.addEventListener('click', this.deleteExample)
|
|
435
|
+
this.parts.exampleSelect.addEventListener('change', this.loadExample)
|
|
436
|
+
this.parts.tabResult.addEventListener('click', () =>
|
|
437
|
+
this.switchTab('result')
|
|
438
|
+
)
|
|
439
|
+
this.parts.tabAst.addEventListener('click', () => this.switchTab('ast'))
|
|
440
|
+
this.parts.tabTrace.addEventListener('click', () => this.switchTab('trace'))
|
|
441
|
+
this.parts.copyBtn.addEventListener('click', this.copyOutput)
|
|
442
|
+
|
|
443
|
+
// Listen for hash changes
|
|
444
|
+
window.addEventListener('hashchange', this.handleHashChange)
|
|
445
|
+
|
|
446
|
+
// Populate the example select BEFORE initEditor (so select value can be set)
|
|
447
|
+
this.rebuildExampleSelect()
|
|
448
|
+
|
|
449
|
+
// Initialize CodeMirror after hydration
|
|
450
|
+
this.initEditor()
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
disconnectedCallback() {
|
|
454
|
+
window.removeEventListener('hashchange', this.handleHashChange)
|
|
455
|
+
this.darkModeObserver?.disconnect()
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Get all examples (built-in + custom)
|
|
459
|
+
getAllExamples(): Example[] {
|
|
460
|
+
return [...examples, ...this.customExamples]
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Rebuild the example select dropdown
|
|
464
|
+
rebuildExampleSelect() {
|
|
465
|
+
const sel = this.parts.exampleSelect
|
|
466
|
+
sel.innerHTML = ''
|
|
467
|
+
|
|
468
|
+
// New option
|
|
469
|
+
sel.appendChild(option({ value: 'new' }, '-- New --'))
|
|
470
|
+
|
|
471
|
+
// Built-in examples
|
|
472
|
+
const builtInGroup = optgroup({ label: 'Built-in Examples' })
|
|
473
|
+
examples.forEach((ex, i) => {
|
|
474
|
+
builtInGroup.appendChild(
|
|
475
|
+
option(
|
|
476
|
+
{ value: `builtin:${i}` },
|
|
477
|
+
ex.requiresApi ? `${ex.name} 🔑` : ex.name
|
|
478
|
+
)
|
|
479
|
+
)
|
|
480
|
+
})
|
|
481
|
+
sel.appendChild(builtInGroup)
|
|
482
|
+
|
|
483
|
+
// Custom examples (if any)
|
|
484
|
+
if (this.customExamples.length > 0) {
|
|
485
|
+
const customGroup = optgroup({ label: 'My Examples' })
|
|
486
|
+
this.customExamples.forEach((ex, i) => {
|
|
487
|
+
customGroup.appendChild(option({ value: `custom:${i}` }, ex.name))
|
|
488
|
+
})
|
|
489
|
+
sel.appendChild(customGroup)
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Get example index from hash (e.g., #example=2 or #example=Hello%20World)
|
|
494
|
+
getExampleFromHash(): { type: 'new' | 'builtin' | 'custom'; index: number } {
|
|
495
|
+
const hash = window.location.hash.slice(1)
|
|
496
|
+
const params = new URLSearchParams(hash)
|
|
497
|
+
const value = params.get('example')
|
|
498
|
+
if (value === null || value === 'new') return { type: 'new', index: -1 }
|
|
499
|
+
|
|
500
|
+
// Try matching by name in built-in examples
|
|
501
|
+
const decodedName = decodeURIComponent(value).toLowerCase()
|
|
502
|
+
const builtinIdx = examples.findIndex(
|
|
503
|
+
(ex) => ex.name.toLowerCase() === decodedName
|
|
504
|
+
)
|
|
505
|
+
if (builtinIdx >= 0) return { type: 'builtin', index: builtinIdx }
|
|
506
|
+
|
|
507
|
+
// Try matching in custom examples
|
|
508
|
+
const customIdx = this.customExamples.findIndex(
|
|
509
|
+
(ex) => ex.name.toLowerCase() === decodedName
|
|
510
|
+
)
|
|
511
|
+
if (customIdx >= 0) return { type: 'custom', index: customIdx }
|
|
512
|
+
|
|
513
|
+
return { type: 'builtin', index: 0 }
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Update hash when example changes
|
|
517
|
+
setHashForExample(type: 'new' | 'builtin' | 'custom', idx: number) {
|
|
518
|
+
if (type === 'new') {
|
|
519
|
+
history.replaceState(null, '', '#example=new')
|
|
520
|
+
return
|
|
521
|
+
}
|
|
522
|
+
const allExamples = type === 'builtin' ? examples : this.customExamples
|
|
523
|
+
const example = allExamples[idx]
|
|
524
|
+
if (example) {
|
|
525
|
+
const hash = `example=${encodeURIComponent(example.name)}`
|
|
526
|
+
history.replaceState(null, '', `#${hash}`)
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
handleHashChange = () => {
|
|
531
|
+
const { type, index } = this.getExampleFromHash()
|
|
532
|
+
this.loadExampleByTypeAndIndex(type, index)
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private isDarkMode(): boolean {
|
|
536
|
+
return document.body.classList.contains('darkmode')
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private getThemeExtension() {
|
|
540
|
+
return this.isDarkMode() ? oneDark : []
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
private updateEditorTheme() {
|
|
544
|
+
if (!this.editor) return
|
|
545
|
+
this.editor.dispatch({
|
|
546
|
+
effects: this.themeCompartment.reconfigure(this.getThemeExtension()),
|
|
547
|
+
})
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
initEditor() {
|
|
551
|
+
const container = this.parts.editorContainer
|
|
552
|
+
if (!container) return
|
|
553
|
+
|
|
554
|
+
const extensions = [
|
|
555
|
+
basicSetup,
|
|
556
|
+
syntaxHighlighting(defaultHighlightStyle),
|
|
557
|
+
ajsEditorExtension(),
|
|
558
|
+
this.themeCompartment.of(this.getThemeExtension()),
|
|
559
|
+
]
|
|
560
|
+
|
|
561
|
+
// Get initial example from hash or default to first built-in
|
|
562
|
+
const { type, index } = this.getExampleFromHash()
|
|
563
|
+
let startDoc = NEW_EXAMPLE_CODE
|
|
564
|
+
if (type === 'builtin' && index >= 0) {
|
|
565
|
+
startDoc = examples[index]?.code || startDoc
|
|
566
|
+
} else if (type === 'custom' && index >= 0) {
|
|
567
|
+
startDoc = this.customExamples[index]?.code || startDoc
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
this.editor = new EditorView({
|
|
571
|
+
state: EditorState.create({
|
|
572
|
+
doc: startDoc,
|
|
573
|
+
extensions,
|
|
574
|
+
}),
|
|
575
|
+
parent: container,
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
// Watch for dark mode changes on body
|
|
579
|
+
this.darkModeObserver = new MutationObserver((mutations) => {
|
|
580
|
+
for (const mutation of mutations) {
|
|
581
|
+
if (mutation.attributeName === 'class') {
|
|
582
|
+
this.updateEditorTheme()
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
})
|
|
586
|
+
this.darkModeObserver.observe(document.body, { attributes: true })
|
|
587
|
+
|
|
588
|
+
// Update current example tracking
|
|
589
|
+
this.currentExampleIndex = index
|
|
590
|
+
this.updateDeleteButtonVisibility(type, index)
|
|
591
|
+
|
|
592
|
+
// Update select to match the loaded example
|
|
593
|
+
if (type === 'new') {
|
|
594
|
+
this.parts.exampleSelect.value = 'new'
|
|
595
|
+
} else if (type === 'builtin') {
|
|
596
|
+
this.parts.exampleSelect.value = `builtin:${index}`
|
|
597
|
+
} else if (type === 'custom') {
|
|
598
|
+
this.parts.exampleSelect.value = `custom:${index}`
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Set hash if not already set
|
|
602
|
+
if (!window.location.hash.includes('example=')) {
|
|
603
|
+
this.setHashForExample(type, index)
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
loadExampleByTypeAndIndex(type: 'new' | 'builtin' | 'custom', idx: number) {
|
|
608
|
+
if (!this.editor) return
|
|
609
|
+
|
|
610
|
+
let code = NEW_EXAMPLE_CODE
|
|
611
|
+
if (type === 'builtin' && idx >= 0 && idx < examples.length) {
|
|
612
|
+
code = examples[idx].code
|
|
613
|
+
} else if (
|
|
614
|
+
type === 'custom' &&
|
|
615
|
+
idx >= 0 &&
|
|
616
|
+
idx < this.customExamples.length
|
|
617
|
+
) {
|
|
618
|
+
code = this.customExamples[idx].code
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
this.editor.dispatch({
|
|
622
|
+
changes: {
|
|
623
|
+
from: 0,
|
|
624
|
+
to: this.editor.state.doc.length,
|
|
625
|
+
insert: code,
|
|
626
|
+
},
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
this.currentExampleIndex = idx
|
|
630
|
+
this.updateDeleteButtonVisibility(type, idx)
|
|
631
|
+
this.setHashForExample(type, idx)
|
|
632
|
+
|
|
633
|
+
// Update select to match the loaded example
|
|
634
|
+
if (type === 'new') {
|
|
635
|
+
this.parts.exampleSelect.value = 'new'
|
|
636
|
+
} else if (type === 'builtin') {
|
|
637
|
+
this.parts.exampleSelect.value = `builtin:${idx}`
|
|
638
|
+
} else if (type === 'custom') {
|
|
639
|
+
this.parts.exampleSelect.value = `custom:${idx}`
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Clear results
|
|
643
|
+
this.parts.resultContainer.textContent = '// Run code to see results'
|
|
644
|
+
this.parts.statusBar.textContent = 'Ready'
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
updateDeleteButtonVisibility(
|
|
648
|
+
type: 'new' | 'builtin' | 'custom',
|
|
649
|
+
idx: number
|
|
650
|
+
) {
|
|
651
|
+
// Only show delete button for custom examples
|
|
652
|
+
const showDelete = type === 'custom' && idx >= 0
|
|
653
|
+
this.parts.deleteBtn.style.display = showDelete ? '' : 'none'
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
loadExample = (e: Event) => {
|
|
657
|
+
const value = (e.target as HTMLSelectElement).value
|
|
658
|
+
if (value === 'new') {
|
|
659
|
+
this.loadExampleByTypeAndIndex('new', -1)
|
|
660
|
+
return
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const [type, idxStr] = value.split(':')
|
|
664
|
+
const idx = parseInt(idxStr, 10)
|
|
665
|
+
if (type === 'builtin' || type === 'custom') {
|
|
666
|
+
this.loadExampleByTypeAndIndex(type, idx)
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
newExample = () => {
|
|
671
|
+
if (this.editor) {
|
|
672
|
+
this.editor.dispatch({
|
|
673
|
+
changes: {
|
|
674
|
+
from: 0,
|
|
675
|
+
to: this.editor.state.doc.length,
|
|
676
|
+
insert: NEW_EXAMPLE_CODE,
|
|
677
|
+
},
|
|
678
|
+
})
|
|
679
|
+
}
|
|
680
|
+
this.currentExampleIndex = -1
|
|
681
|
+
this.parts.exampleSelect.value = 'new'
|
|
682
|
+
this.updateDeleteButtonVisibility('new', -1)
|
|
683
|
+
this.setHashForExample('new', -1)
|
|
684
|
+
this.parts.resultContainer.textContent = '// Run code to see results'
|
|
685
|
+
this.parts.statusBar.textContent = 'Ready'
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
saveExample = () => {
|
|
689
|
+
if (!this.editor) return
|
|
690
|
+
|
|
691
|
+
const code = this.editor.state.doc.toString()
|
|
692
|
+
if (!code.trim()) {
|
|
693
|
+
alert('Cannot save empty example')
|
|
694
|
+
return
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Try to extract function name from code
|
|
698
|
+
const funcMatch = code.match(/function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/)
|
|
699
|
+
const defaultName = funcMatch ? funcMatch[1] : 'My Example'
|
|
700
|
+
|
|
701
|
+
const name = prompt('Enter a name for this example:', defaultName)
|
|
702
|
+
if (!name) return
|
|
703
|
+
|
|
704
|
+
// Check for duplicate names
|
|
705
|
+
const existingIdx = this.customExamples.findIndex(
|
|
706
|
+
(ex) => ex.name.toLowerCase() === name.toLowerCase()
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
if (existingIdx >= 0) {
|
|
710
|
+
if (
|
|
711
|
+
!confirm(`An example named "${name}" already exists. Overwrite it?`)
|
|
712
|
+
) {
|
|
713
|
+
return
|
|
714
|
+
}
|
|
715
|
+
this.customExamples[existingIdx] = { name, description: '', code }
|
|
716
|
+
} else {
|
|
717
|
+
this.customExamples.push({ name, description: '', code })
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
saveCustomExamples(this.customExamples)
|
|
721
|
+
this.rebuildExampleSelect()
|
|
722
|
+
|
|
723
|
+
// Select the saved example
|
|
724
|
+
const newIdx =
|
|
725
|
+
existingIdx >= 0 ? existingIdx : this.customExamples.length - 1
|
|
726
|
+
this.currentExampleIndex = newIdx
|
|
727
|
+
this.parts.exampleSelect.value = `custom:${newIdx}`
|
|
728
|
+
this.updateDeleteButtonVisibility('custom', newIdx)
|
|
729
|
+
this.setHashForExample('custom', newIdx)
|
|
730
|
+
|
|
731
|
+
this.parts.statusBar.textContent = `Saved "${name}"`
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
deleteExample = () => {
|
|
735
|
+
const value = this.parts.exampleSelect.value
|
|
736
|
+
if (!value.startsWith('custom:')) return
|
|
737
|
+
|
|
738
|
+
const idx = parseInt(value.split(':')[1], 10)
|
|
739
|
+
if (idx < 0 || idx >= this.customExamples.length) return
|
|
740
|
+
|
|
741
|
+
const name = this.customExamples[idx].name
|
|
742
|
+
if (!confirm(`Delete "${name}"?`)) return
|
|
743
|
+
|
|
744
|
+
this.customExamples.splice(idx, 1)
|
|
745
|
+
saveCustomExamples(this.customExamples)
|
|
746
|
+
this.rebuildExampleSelect()
|
|
747
|
+
|
|
748
|
+
// Go to new
|
|
749
|
+
this.newExample()
|
|
750
|
+
this.parts.statusBar.textContent = `Deleted "${name}"`
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
copyOutput = async () => {
|
|
754
|
+
const text = this.parts.resultContainer.textContent || ''
|
|
755
|
+
try {
|
|
756
|
+
await navigator.clipboard.writeText(text)
|
|
757
|
+
this.parts.copyBtn.classList.add('copied')
|
|
758
|
+
setTimeout(() => {
|
|
759
|
+
this.parts.copyBtn.classList.remove('copied')
|
|
760
|
+
}, 1500)
|
|
761
|
+
} catch (e) {
|
|
762
|
+
console.error('Failed to copy:', e)
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
switchTab(tab: string) {
|
|
767
|
+
this.currentTab = tab
|
|
768
|
+
this.parts.tabResult.classList.toggle('active', tab === 'result')
|
|
769
|
+
this.parts.tabAst.classList.toggle('active', tab === 'ast')
|
|
770
|
+
this.parts.tabTrace.classList.toggle('active', tab === 'trace')
|
|
771
|
+
this.updateOutput()
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
updateOutput() {
|
|
775
|
+
const container = this.parts.resultContainer
|
|
776
|
+
container.className = 'playground-result'
|
|
777
|
+
|
|
778
|
+
if (this.lastError) {
|
|
779
|
+
container.className += ' error'
|
|
780
|
+
container.textContent = this.lastError
|
|
781
|
+
return
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
switch (this.currentTab) {
|
|
785
|
+
case 'ast':
|
|
786
|
+
container.textContent = this.lastAst
|
|
787
|
+
? JSON.stringify(this.lastAst.ast, null, 2)
|
|
788
|
+
: '// Run code to see AST'
|
|
789
|
+
break
|
|
790
|
+
|
|
791
|
+
case 'trace':
|
|
792
|
+
container.textContent = this.lastResult?.trace
|
|
793
|
+
? this.lastResult.trace
|
|
794
|
+
.map((t: any) => `${t.op}: ${JSON.stringify(t.result)}`)
|
|
795
|
+
.join('\n')
|
|
796
|
+
: '// Run code with tracing enabled to see execution trace'
|
|
797
|
+
break
|
|
798
|
+
|
|
799
|
+
case 'result':
|
|
800
|
+
default:
|
|
801
|
+
if (this.lastResult) {
|
|
802
|
+
container.className += this.lastResult.error ? ' error' : ' success'
|
|
803
|
+
const output = this.lastResult.error || this.lastResult.result
|
|
804
|
+
container.textContent =
|
|
805
|
+
typeof output === 'string'
|
|
806
|
+
? output
|
|
807
|
+
: JSON.stringify(output, null, 2)
|
|
808
|
+
} else {
|
|
809
|
+
container.textContent = '// Run code to see results'
|
|
810
|
+
}
|
|
811
|
+
break
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
runCode = async () => {
|
|
816
|
+
console.log('runCode called', {
|
|
817
|
+
isRunning: this.isRunning,
|
|
818
|
+
editor: this.editor,
|
|
819
|
+
})
|
|
820
|
+
if (this.isRunning || !this.editor) return
|
|
821
|
+
|
|
822
|
+
this.isRunning = true
|
|
823
|
+
this.lastError = null
|
|
824
|
+
this.lastAst = null
|
|
825
|
+
this.lastResult = null
|
|
826
|
+
|
|
827
|
+
const code = this.editor.state.doc.toString()
|
|
828
|
+
const startTime = performance.now()
|
|
829
|
+
|
|
830
|
+
// Show loading state
|
|
831
|
+
this.parts.resultContainer.className = 'playground-result'
|
|
832
|
+
this.parts.resultContainer.innerHTML =
|
|
833
|
+
'<span class="loading-spinner"></span> Running...'
|
|
834
|
+
this.parts.statusBar.textContent = 'Transpiling...'
|
|
835
|
+
|
|
836
|
+
// Update elapsed time while running
|
|
837
|
+
const updateTimer = setInterval(() => {
|
|
838
|
+
const elapsed = ((performance.now() - startTime) / 1000).toFixed(1)
|
|
839
|
+
this.parts.statusBar.textContent = `Running... ${elapsed}s`
|
|
840
|
+
}, 100)
|
|
841
|
+
|
|
842
|
+
try {
|
|
843
|
+
const transpileResult = transpile(code)
|
|
844
|
+
this.lastAst = transpileResult
|
|
845
|
+
|
|
846
|
+
this.parts.statusBar.textContent = 'Running...'
|
|
847
|
+
|
|
848
|
+
// Build args from signature defaults
|
|
849
|
+
const args: Record<string, any> = {}
|
|
850
|
+
if (transpileResult.signature?.parameters) {
|
|
851
|
+
for (const [key, param] of Object.entries(
|
|
852
|
+
transpileResult.signature.parameters
|
|
853
|
+
)) {
|
|
854
|
+
if ('default' in param) {
|
|
855
|
+
args[key] = param.default
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// Build capabilities from settings
|
|
861
|
+
const settings = getSettings()
|
|
862
|
+
const llmCapability = buildLLMCapability(settings)
|
|
863
|
+
const llmBattery = buildLLMBattery(settings)
|
|
864
|
+
|
|
865
|
+
const noLLMError = () => {
|
|
866
|
+
const isHttps = window.location.protocol === 'https:'
|
|
867
|
+
if (isHttps) {
|
|
868
|
+
throw new Error(
|
|
869
|
+
'No LLM configured. Go to Settings (⋮) > API Keys to add an OpenAI or Anthropic API key. Note: Local LLM endpoints require HTTP.'
|
|
870
|
+
)
|
|
871
|
+
} else {
|
|
872
|
+
throw new Error(
|
|
873
|
+
'No LLM configured. Go to Settings (⋮) > API Keys to add an OpenAI key, Anthropic key, or LM Studio endpoint (default: http://localhost:1234/v1).'
|
|
874
|
+
)
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const result = await this.vm.run(transpileResult.ast, args, {
|
|
879
|
+
trace: true,
|
|
880
|
+
fuel: 10000,
|
|
881
|
+
capabilities: {
|
|
882
|
+
fetch: async (url: string, options?: any) => {
|
|
883
|
+
const response = await fetch(url, options)
|
|
884
|
+
|
|
885
|
+
// Handle dataUrl response type for vision/image fetching
|
|
886
|
+
if (options?.responseType === 'dataUrl') {
|
|
887
|
+
const buffer = await response.arrayBuffer()
|
|
888
|
+
const bytes = new Uint8Array(buffer)
|
|
889
|
+
let binary = ''
|
|
890
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
891
|
+
binary += String.fromCharCode(bytes[i])
|
|
892
|
+
}
|
|
893
|
+
const base64 = btoa(binary)
|
|
894
|
+
const ct =
|
|
895
|
+
response.headers.get('content-type') ||
|
|
896
|
+
'application/octet-stream'
|
|
897
|
+
return `data:${ct};base64,${base64}`
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
const contentType = response.headers.get('content-type')
|
|
901
|
+
if (contentType && contentType.includes('application/json')) {
|
|
902
|
+
return response.json()
|
|
903
|
+
}
|
|
904
|
+
return response.text()
|
|
905
|
+
},
|
|
906
|
+
store: getStoreCapabilityDefault(),
|
|
907
|
+
llm: {
|
|
908
|
+
predict: async (prompt: string, options?: any) => {
|
|
909
|
+
if (!llmCapability) noLLMError()
|
|
910
|
+
return llmCapability!.predict(prompt, options)
|
|
911
|
+
},
|
|
912
|
+
},
|
|
913
|
+
llmBattery: llmBattery || {
|
|
914
|
+
predict: () => noLLMError(),
|
|
915
|
+
embed: () => noLLMError(),
|
|
916
|
+
},
|
|
917
|
+
code: {
|
|
918
|
+
transpile: (source: string) => transpile(source).ast,
|
|
919
|
+
},
|
|
920
|
+
},
|
|
921
|
+
})
|
|
922
|
+
|
|
923
|
+
this.lastResult = result
|
|
924
|
+
clearInterval(updateTimer)
|
|
925
|
+
const elapsed = ((performance.now() - startTime) / 1000).toFixed(2)
|
|
926
|
+
this.parts.statusBar.textContent = `Done in ${elapsed}s (${result.fuelUsed.toFixed(
|
|
927
|
+
1
|
|
928
|
+
)} fuel)`
|
|
929
|
+
} catch (e: any) {
|
|
930
|
+
clearInterval(updateTimer)
|
|
931
|
+
const elapsed = ((performance.now() - startTime) / 1000).toFixed(2)
|
|
932
|
+
this.lastError = e.message || String(e)
|
|
933
|
+
this.parts.statusBar.textContent = `Error after ${elapsed}s`
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
this.updateOutput()
|
|
937
|
+
this.isRunning = false
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
render() {
|
|
941
|
+
super.render()
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Register component with Shadow DOM (uses static styleSpec)
|
|
946
|
+
console.log('Registering agent-playground component')
|
|
947
|
+
export const playground = Playground.elementCreator({
|
|
948
|
+
tag: 'agent-playground',
|
|
949
|
+
})
|