create-janux 0.2.0 → 0.2.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/bin.ts +9 -3
- package/package.json +3 -2
- package/template/README.md +8 -13
- package/template/bun.lock +208 -0
- package/template/package.json +13 -5
- package/template/public/favicon.svg +19 -0
- package/template/src/client.ts +3 -5
- package/template/src/components/AgentPanel.tsx +150 -0
- package/template/src/components/Counter.test.ts +27 -0
- package/template/src/components/Counter.tsx +40 -0
- package/template/src/routes/index.tsx +16 -18
- package/template/src/styles.css +173 -228
- package/template/tsconfig.json +1 -0
- package/template/.env.example +0 -9
- package/template/src/agent.ts +0 -8
- package/template/src/components/Copilot.tsx +0 -103
- package/template/src/components/TaskBoard.test.ts +0 -38
- package/template/src/components/TaskBoard.tsx +0 -125
- package/template/src/components/ThemeToggle.tsx +0 -23
- package/template/src/server/tasks.api.ts +0 -31
- package/template/src/stores.ts +0 -17
|
@@ -1,29 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { ThemeToggle } from '../components/ThemeToggle';
|
|
4
|
-
import { Copilot } from '../components/Copilot';
|
|
1
|
+
import { Counter } from '../components/Counter';
|
|
2
|
+
import { AgentPanel } from '../components/AgentPanel';
|
|
5
3
|
|
|
6
4
|
export const meta = {
|
|
7
|
-
title: '
|
|
8
|
-
description: '
|
|
5
|
+
title: 'Welcome to Janux',
|
|
6
|
+
description: 'One component, two faces: a UI for you, typed tools for agents.',
|
|
9
7
|
};
|
|
10
8
|
|
|
11
|
-
export default
|
|
12
|
-
const saved: any = await loadTasks({});
|
|
13
|
-
|
|
9
|
+
export default function Home() {
|
|
14
10
|
return (
|
|
15
11
|
<div class="app">
|
|
16
|
-
<header class="
|
|
17
|
-
<span class="brand"
|
|
18
|
-
<
|
|
12
|
+
<header class="bar">
|
|
13
|
+
<span class="brand">⚡ Janux</span>
|
|
14
|
+
<span class="bar-hint">The counter is your UI · the right panel is what an agent sees</span>
|
|
19
15
|
</header>
|
|
20
|
-
<main>
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
<main class="split">
|
|
17
|
+
<section class="preview">
|
|
18
|
+
<Counter />
|
|
19
|
+
<p class="curl">
|
|
20
|
+
Same surface over HTTP: <code>curl localhost:3000/_janux/manifest</code>
|
|
21
|
+
</p>
|
|
22
|
+
</section>
|
|
23
|
+
<AgentPanel eager />
|
|
25
24
|
</main>
|
|
26
|
-
<Copilot persist />
|
|
27
25
|
</div>
|
|
28
26
|
);
|
|
29
27
|
}
|
package/template/src/styles.css
CHANGED
|
@@ -1,24 +1,9 @@
|
|
|
1
1
|
:root {
|
|
2
|
-
--
|
|
3
|
-
--card: #1b1938;
|
|
4
|
-
--card-2: #242150;
|
|
5
|
-
--text: #e2e8f0;
|
|
6
|
-
--muted: #94a3b8;
|
|
7
|
-
--violet: #a78bfa;
|
|
8
|
-
--cyan: #22d3ee;
|
|
9
|
-
--border: #312e81;
|
|
10
|
-
--danger: #f87171;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
:root[data-theme='light'] {
|
|
14
|
-
--bg: #f8fafc;
|
|
15
|
-
--card: #ffffff;
|
|
16
|
-
--card-2: #eef2ff;
|
|
17
|
-
--text: #0f172a;
|
|
18
|
-
--muted: #64748b;
|
|
2
|
+
--ink: #0f172a;
|
|
19
3
|
--violet: #7c3aed;
|
|
20
|
-
--cyan: #0891b2;
|
|
21
4
|
--border: #e2e8f0;
|
|
5
|
+
--muted: #64748b;
|
|
6
|
+
--mono: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
22
7
|
}
|
|
23
8
|
|
|
24
9
|
* {
|
|
@@ -28,305 +13,265 @@
|
|
|
28
13
|
body {
|
|
29
14
|
margin: 0;
|
|
30
15
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif;
|
|
31
|
-
background:
|
|
32
|
-
color: var(--
|
|
33
|
-
transition: background 0.25s, color 0.25s;
|
|
16
|
+
background: #fff;
|
|
17
|
+
color: var(--ink);
|
|
34
18
|
}
|
|
35
19
|
|
|
36
|
-
.
|
|
20
|
+
.bar {
|
|
37
21
|
display: flex;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
padding: 22px 16px 0;
|
|
22
|
+
align-items: baseline;
|
|
23
|
+
gap: 12px;
|
|
24
|
+
padding: 12px 16px;
|
|
25
|
+
border-bottom: 1px solid var(--border);
|
|
43
26
|
}
|
|
44
27
|
|
|
45
28
|
.brand {
|
|
46
29
|
font-weight: 800;
|
|
47
|
-
font-size:
|
|
48
|
-
background: linear-gradient(90deg, var(--violet), var(--cyan));
|
|
49
|
-
-webkit-background-clip: text;
|
|
50
|
-
background-clip: text;
|
|
51
|
-
color: transparent;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.theme-toggle {
|
|
55
|
-
padding: 7px 14px;
|
|
56
|
-
border: 1px solid var(--border);
|
|
57
|
-
border-radius: 999px;
|
|
58
|
-
background: var(--card);
|
|
59
|
-
color: var(--text);
|
|
60
|
-
cursor: pointer;
|
|
61
|
-
font-size: 13px;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
main {
|
|
65
|
-
max-width: 560px;
|
|
66
|
-
margin: 24px auto 120px;
|
|
67
|
-
padding: 0 16px;
|
|
30
|
+
font-size: 1rem;
|
|
68
31
|
}
|
|
69
32
|
|
|
70
|
-
.hint {
|
|
71
|
-
text-align: center;
|
|
33
|
+
.bar-hint {
|
|
72
34
|
color: var(--muted);
|
|
73
35
|
font-size: 13px;
|
|
74
36
|
}
|
|
75
37
|
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
font-size: 12px;
|
|
38
|
+
.split {
|
|
39
|
+
display: grid;
|
|
40
|
+
grid-template-columns: minmax(0, 1fr) 340px;
|
|
41
|
+
height: calc(100vh - 46px);
|
|
81
42
|
}
|
|
82
43
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
44
|
+
.preview {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
align-items: center;
|
|
48
|
+
justify-content: center;
|
|
49
|
+
gap: 28px;
|
|
50
|
+
border-right: 1px solid var(--border);
|
|
90
51
|
}
|
|
91
52
|
|
|
92
|
-
|
|
53
|
+
/* ── Counter ────────────────────────────────────────────────────── */
|
|
54
|
+
.counter {
|
|
93
55
|
display: flex;
|
|
94
|
-
|
|
95
|
-
align-items:
|
|
96
|
-
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
align-items: center;
|
|
58
|
+
gap: 24px;
|
|
97
59
|
}
|
|
98
60
|
|
|
99
|
-
.
|
|
61
|
+
.counter h1 {
|
|
100
62
|
margin: 0;
|
|
101
|
-
font-size:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
.board .count {
|
|
106
|
-
color: var(--muted);
|
|
107
|
-
font-size: 13px;
|
|
63
|
+
font-size: 3.75rem;
|
|
64
|
+
font-weight: 800;
|
|
65
|
+
color: #1e1b4b;
|
|
108
66
|
}
|
|
109
67
|
|
|
110
|
-
.
|
|
68
|
+
.counter .row {
|
|
111
69
|
display: flex;
|
|
112
|
-
gap:
|
|
70
|
+
gap: 12px;
|
|
113
71
|
}
|
|
114
72
|
|
|
115
|
-
.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
border:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
color:
|
|
122
|
-
|
|
123
|
-
|
|
73
|
+
.counter button {
|
|
74
|
+
padding: 10px 28px;
|
|
75
|
+
border: none;
|
|
76
|
+
border-radius: 12px;
|
|
77
|
+
font-size: 1.25rem;
|
|
78
|
+
font-weight: 700;
|
|
79
|
+
color: #fff;
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.15);
|
|
124
82
|
}
|
|
125
83
|
|
|
126
|
-
.
|
|
127
|
-
|
|
84
|
+
.counter .dec {
|
|
85
|
+
background: #64748b;
|
|
128
86
|
}
|
|
129
87
|
|
|
130
|
-
.
|
|
131
|
-
|
|
132
|
-
border-radius: 10px;
|
|
133
|
-
cursor: pointer;
|
|
134
|
-
font-weight: 600;
|
|
88
|
+
.counter .dec:hover {
|
|
89
|
+
background: #475569;
|
|
135
90
|
}
|
|
136
91
|
|
|
137
|
-
.
|
|
138
|
-
padding: 0 18px;
|
|
92
|
+
.counter .inc {
|
|
139
93
|
background: linear-gradient(90deg, #7c3aed, #06b6d4);
|
|
140
|
-
color: #fff;
|
|
141
94
|
}
|
|
142
95
|
|
|
143
|
-
.
|
|
144
|
-
|
|
145
|
-
gap: 6px;
|
|
146
|
-
margin: 14px 0 6px;
|
|
96
|
+
.counter .inc:hover {
|
|
97
|
+
opacity: 0.9;
|
|
147
98
|
}
|
|
148
99
|
|
|
149
|
-
.
|
|
150
|
-
padding: 5px 14px;
|
|
151
|
-
border-radius: 999px;
|
|
152
|
-
background: transparent;
|
|
100
|
+
.curl {
|
|
153
101
|
color: var(--muted);
|
|
154
|
-
border: 1px solid transparent;
|
|
155
102
|
font-size: 13px;
|
|
156
|
-
text-transform: capitalize;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.filters button.on {
|
|
160
|
-
background: var(--card-2);
|
|
161
|
-
color: var(--text);
|
|
162
|
-
border-color: var(--border);
|
|
163
103
|
}
|
|
164
104
|
|
|
165
|
-
.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
display: flex;
|
|
173
|
-
align-items: center;
|
|
174
|
-
gap: 12px;
|
|
175
|
-
padding: 10px 4px;
|
|
176
|
-
border-bottom: 1px solid var(--border);
|
|
105
|
+
.curl code {
|
|
106
|
+
padding: 2px 8px;
|
|
107
|
+
border-radius: 6px;
|
|
108
|
+
background: #ede9fe;
|
|
109
|
+
color: #5b21b6;
|
|
110
|
+
font-size: 12px;
|
|
111
|
+
font-family: var(--mono);
|
|
177
112
|
}
|
|
178
113
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
114
|
+
/* ── Agent pane ─────────────────────────────────────────────────── */
|
|
115
|
+
.agent-pane {
|
|
116
|
+
overflow-y: auto;
|
|
117
|
+
padding: 14px;
|
|
118
|
+
background: #f8fafc;
|
|
182
119
|
}
|
|
183
120
|
|
|
184
|
-
.
|
|
185
|
-
|
|
186
|
-
|
|
121
|
+
.agent-pane h2 {
|
|
122
|
+
margin: 0 0 10px;
|
|
123
|
+
font-size: 0.95rem;
|
|
187
124
|
}
|
|
188
125
|
|
|
189
|
-
.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
border
|
|
193
|
-
border:
|
|
194
|
-
background:
|
|
195
|
-
|
|
196
|
-
font-size: 14px;
|
|
197
|
-
line-height: 1;
|
|
126
|
+
.agent-pane .tool-row {
|
|
127
|
+
margin-bottom: 8px;
|
|
128
|
+
padding: 10px 12px;
|
|
129
|
+
border: 1px solid var(--border);
|
|
130
|
+
border-radius: 10px;
|
|
131
|
+
background: #fff;
|
|
132
|
+
font-size: 13px;
|
|
198
133
|
}
|
|
199
134
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
135
|
+
.agent-pane .tool-row code {
|
|
136
|
+
font-size: 12px;
|
|
137
|
+
font-family: var(--mono);
|
|
203
138
|
}
|
|
204
139
|
|
|
205
|
-
.
|
|
206
|
-
|
|
140
|
+
.agent-pane .tool-row small {
|
|
141
|
+
display: block;
|
|
142
|
+
margin-top: 2px;
|
|
207
143
|
color: var(--muted);
|
|
208
|
-
font-size: 14px;
|
|
209
|
-
padding: 4px 8px;
|
|
210
144
|
}
|
|
211
145
|
|
|
212
|
-
.
|
|
213
|
-
|
|
146
|
+
.agent-pane .tool-row .example {
|
|
147
|
+
display: inline-block;
|
|
148
|
+
margin-top: 6px;
|
|
149
|
+
padding: 2px 8px;
|
|
150
|
+
border-radius: 6px;
|
|
151
|
+
background: #f1f5f9;
|
|
152
|
+
color: var(--muted);
|
|
214
153
|
}
|
|
215
154
|
|
|
216
|
-
.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
155
|
+
.agent-pane .guard {
|
|
156
|
+
margin-left: 6px;
|
|
157
|
+
padding: 1px 8px;
|
|
158
|
+
border-radius: 999px;
|
|
159
|
+
font-size: 11px;
|
|
160
|
+
font-weight: 700;
|
|
220
161
|
}
|
|
221
162
|
|
|
222
|
-
.
|
|
223
|
-
background:
|
|
224
|
-
color:
|
|
225
|
-
font-size: 13px;
|
|
226
|
-
padding: 6px 10px;
|
|
163
|
+
.agent-pane .guard.auto {
|
|
164
|
+
background: #ecfdf5;
|
|
165
|
+
color: #047857;
|
|
227
166
|
}
|
|
228
167
|
|
|
229
|
-
.
|
|
230
|
-
|
|
168
|
+
.agent-pane .guard.confirm {
|
|
169
|
+
background: #fffbeb;
|
|
170
|
+
color: #92400e;
|
|
231
171
|
}
|
|
232
172
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
bottom: 22px;
|
|
238
|
-
z-index: 50;
|
|
239
|
-
padding: 12px 20px;
|
|
173
|
+
.agent-pane .tool-row button {
|
|
174
|
+
display: block;
|
|
175
|
+
margin-top: 8px;
|
|
176
|
+
padding: 5px 12px;
|
|
240
177
|
border: none;
|
|
241
|
-
border-radius:
|
|
242
|
-
background:
|
|
178
|
+
border-radius: 8px;
|
|
179
|
+
background: var(--ink);
|
|
243
180
|
color: #fff;
|
|
244
|
-
font-
|
|
181
|
+
font-size: 12px;
|
|
245
182
|
cursor: pointer;
|
|
246
|
-
box-shadow: 0 10px 30px -8px rgba(124, 58, 237, 0.6);
|
|
247
183
|
}
|
|
248
184
|
|
|
249
|
-
.
|
|
250
|
-
|
|
251
|
-
right: 22px;
|
|
252
|
-
bottom: 78px;
|
|
253
|
-
z-index: 50;
|
|
254
|
-
display: flex;
|
|
255
|
-
flex-direction: column;
|
|
256
|
-
width: 340px;
|
|
257
|
-
max-height: 480px;
|
|
258
|
-
border: 1px solid var(--border);
|
|
259
|
-
border-radius: 16px;
|
|
260
|
-
background: var(--card);
|
|
261
|
-
box-shadow: 0 24px 60px -12px rgba(0, 0, 0, 0.5);
|
|
262
|
-
overflow: hidden;
|
|
185
|
+
.agent-pane .resource-block {
|
|
186
|
+
margin-top: 14px;
|
|
263
187
|
}
|
|
264
188
|
|
|
265
|
-
.
|
|
266
|
-
flex: 1;
|
|
189
|
+
.agent-pane .resource {
|
|
267
190
|
margin: 0;
|
|
268
|
-
padding:
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
191
|
+
padding: 12px;
|
|
192
|
+
border-radius: 10px;
|
|
193
|
+
max-height: 220px;
|
|
194
|
+
overflow: auto;
|
|
195
|
+
background: #16143a;
|
|
196
|
+
color: #a5f3fc;
|
|
197
|
+
font-size: 12px;
|
|
198
|
+
font-family: var(--mono);
|
|
272
199
|
}
|
|
273
200
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
201
|
+
/* ── Proposal (guard: confirm) ──────────────────────────────────── */
|
|
202
|
+
.proposal-card {
|
|
203
|
+
margin-top: 12px;
|
|
204
|
+
padding: 14px;
|
|
205
|
+
border: 2px solid #dc2626;
|
|
277
206
|
border-radius: 12px;
|
|
207
|
+
background: #fef2f2;
|
|
208
|
+
color: #991b1b;
|
|
209
|
+
font-size: 13px;
|
|
210
|
+
animation: proposal-pulse 1.2s ease-in-out 2;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.proposal-card p {
|
|
214
|
+
margin: 0 0 6px;
|
|
215
|
+
line-height: 1.5;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.proposal-card .proposal-title {
|
|
219
|
+
font-weight: 800;
|
|
278
220
|
font-size: 13.5px;
|
|
279
|
-
|
|
221
|
+
color: #b91c1c;
|
|
280
222
|
}
|
|
281
223
|
|
|
282
|
-
.
|
|
283
|
-
|
|
284
|
-
background: #6d28d9;
|
|
285
|
-
color: #fff;
|
|
224
|
+
.proposal-card .proposal-why {
|
|
225
|
+
color: #b45309;
|
|
286
226
|
}
|
|
287
227
|
|
|
288
|
-
.
|
|
289
|
-
background:
|
|
228
|
+
.proposal-card code {
|
|
229
|
+
background: #fee2e2;
|
|
230
|
+
color: #991b1b;
|
|
231
|
+
font-family: var(--mono);
|
|
290
232
|
}
|
|
291
233
|
|
|
292
|
-
.
|
|
234
|
+
.proposal-actions {
|
|
293
235
|
display: flex;
|
|
294
236
|
gap: 8px;
|
|
295
|
-
|
|
296
|
-
border-top: 1px solid var(--border);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
.copilot-panel input {
|
|
300
|
-
flex: 1;
|
|
301
|
-
padding: 9px 12px;
|
|
302
|
-
border: 1px solid var(--border);
|
|
303
|
-
border-radius: 9px;
|
|
304
|
-
background: var(--bg);
|
|
305
|
-
color: var(--text);
|
|
306
|
-
font-size: 13.5px;
|
|
307
|
-
outline: none;
|
|
237
|
+
margin-top: 10px;
|
|
308
238
|
}
|
|
309
239
|
|
|
310
|
-
.
|
|
240
|
+
.proposal-actions .approve {
|
|
241
|
+
padding: 8px 18px;
|
|
311
242
|
border: none;
|
|
312
|
-
border-radius:
|
|
313
|
-
|
|
314
|
-
background: var(--violet);
|
|
243
|
+
border-radius: 8px;
|
|
244
|
+
background: #dc2626;
|
|
315
245
|
color: #fff;
|
|
316
|
-
font-weight:
|
|
246
|
+
font-weight: 700;
|
|
317
247
|
cursor: pointer;
|
|
318
248
|
}
|
|
319
249
|
|
|
320
|
-
.proposal {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
border:
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
font-
|
|
250
|
+
.proposal-actions .reject {
|
|
251
|
+
padding: 8px 18px;
|
|
252
|
+
border: 1px solid #dc2626;
|
|
253
|
+
border-radius: 8px;
|
|
254
|
+
background: #fff;
|
|
255
|
+
color: #b91c1c;
|
|
256
|
+
font-weight: 700;
|
|
257
|
+
cursor: pointer;
|
|
327
258
|
}
|
|
328
259
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
260
|
+
@keyframes proposal-pulse {
|
|
261
|
+
50% {
|
|
262
|
+
box-shadow: 0 0 0 5px rgba(220, 38, 38, 0.25);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
@media (max-width: 800px) {
|
|
267
|
+
.split {
|
|
268
|
+
grid-template-columns: 1fr;
|
|
269
|
+
height: auto;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.preview {
|
|
273
|
+
min-height: 60vh;
|
|
274
|
+
border-right: none;
|
|
275
|
+
border-bottom: 1px solid var(--border);
|
|
276
|
+
}
|
|
332
277
|
}
|
package/template/tsconfig.json
CHANGED
package/template/.env.example
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# Copilot model — pick ONE option (RFC §8.1 resolution order)
|
|
2
|
-
|
|
3
|
-
# Option 1: explicit model
|
|
4
|
-
# JANUX_MODEL=anthropic/claude-fable-5
|
|
5
|
-
|
|
6
|
-
# Option 2: provider key only — default model inferred
|
|
7
|
-
# ANTHROPIC_API_KEY=sk-ant-...
|
|
8
|
-
# OPENAI_API_KEY=sk-...
|
|
9
|
-
# GOOGLE_GENERATIVE_AI_API_KEY=...
|
package/template/src/agent.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { defineAgent } from '@janux/agent';
|
|
2
|
-
|
|
3
|
-
export default defineAgent({
|
|
4
|
-
instructions:
|
|
5
|
-
'You are this task app’s copilot. Use the tasks.* tools to add, toggle, filter and clear ' +
|
|
6
|
-
'tasks, api.tasks.taskStats for stats, and theme.toggle for appearance. clearDone needs ' +
|
|
7
|
-
'human approval — propose it, never insist.',
|
|
8
|
-
});
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { component, intent, schema, str, bool, enums, list } from 'janux';
|
|
2
|
-
|
|
3
|
-
let wire: any[] = [];
|
|
4
|
-
|
|
5
|
-
async function postAgent(messages: unknown[], path: string): Promise<any> {
|
|
6
|
-
const response = await fetch('/_janux/agent', {
|
|
7
|
-
method: 'POST',
|
|
8
|
-
headers: { 'content-type': 'application/json' },
|
|
9
|
-
body: JSON.stringify({ messages, path }),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
return response.json();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async function converse(state: any, path: string): Promise<void> {
|
|
16
|
-
let reply = await postAgent(wire, path);
|
|
17
|
-
|
|
18
|
-
while (reply.type === 'ui_calls') {
|
|
19
|
-
wire = reply.messages;
|
|
20
|
-
for (const call of reply.calls) {
|
|
21
|
-
const result = await (window as any).janux
|
|
22
|
-
.call(call.name, call.input)
|
|
23
|
-
.catch((error: unknown) => ({ error: String(error) }));
|
|
24
|
-
|
|
25
|
-
if (result?.status === 'proposal') state.proposal = { id: result.id, tool: call.name };
|
|
26
|
-
wire.push({ role: 'tool', toolCallId: call.id, content: JSON.stringify(result ?? null) });
|
|
27
|
-
}
|
|
28
|
-
reply = await postAgent(wire, path);
|
|
29
|
-
}
|
|
30
|
-
wire = reply.messages ?? wire;
|
|
31
|
-
state.messages.push({ role: 'assistant', text: reply.type === 'setup' ? reply.message : reply.text });
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export const Copilot = component({
|
|
35
|
-
name: 'copilot',
|
|
36
|
-
description: 'Built-in copilot operating this app through the agent bridge.',
|
|
37
|
-
|
|
38
|
-
state: schema({
|
|
39
|
-
open: bool(),
|
|
40
|
-
busy: bool(),
|
|
41
|
-
messages: list({ role: enums(['user', 'assistant']), text: str() }),
|
|
42
|
-
proposal: schema({ id: str(), tool: str() }).nullable(),
|
|
43
|
-
}),
|
|
44
|
-
|
|
45
|
-
intents: {
|
|
46
|
-
toggle: intent({ description: 'Open/close the copilot', run: ({ state }: any) => (state.open = !state.open) }),
|
|
47
|
-
send: intent({
|
|
48
|
-
description: 'Send a message to the copilot',
|
|
49
|
-
input: schema({ text: str().min(1) }),
|
|
50
|
-
run: async ({ state, input }: any) => {
|
|
51
|
-
state.messages.push({ role: 'user', text: input.text });
|
|
52
|
-
state.busy = true;
|
|
53
|
-
wire.push({ role: 'user', content: input.text });
|
|
54
|
-
await converse(state, window.location.pathname).finally(() => (state.busy = false));
|
|
55
|
-
},
|
|
56
|
-
}),
|
|
57
|
-
approve: intent({
|
|
58
|
-
guard: 'forbidden',
|
|
59
|
-
run: async ({ state }: any) => {
|
|
60
|
-
await (window as any).janux.approve(state.proposal.id);
|
|
61
|
-
state.messages.push({ role: 'assistant', text: `Approved ${state.proposal.tool} ✔` });
|
|
62
|
-
state.proposal = null;
|
|
63
|
-
},
|
|
64
|
-
}),
|
|
65
|
-
reject: intent({
|
|
66
|
-
guard: 'forbidden',
|
|
67
|
-
run: ({ state }: any) => {
|
|
68
|
-
(window as any).janux.reject(state.proposal?.id);
|
|
69
|
-
state.proposal = null;
|
|
70
|
-
},
|
|
71
|
-
}),
|
|
72
|
-
},
|
|
73
|
-
|
|
74
|
-
view: ({ state, intents }: any) => (
|
|
75
|
-
<aside class="copilot">
|
|
76
|
-
<button class="copilot-toggle" on={intents.toggle}>
|
|
77
|
-
{state.open ? '×' : '✦ Copilot'}
|
|
78
|
-
</button>
|
|
79
|
-
{state.open ? (
|
|
80
|
-
<div class="copilot-panel">
|
|
81
|
-
<ol class="chat">
|
|
82
|
-
{state.messages.map((message: any, index: number) => (
|
|
83
|
-
<li key={String(index)} class={message.role}>
|
|
84
|
-
{message.text}
|
|
85
|
-
</li>
|
|
86
|
-
))}
|
|
87
|
-
</ol>
|
|
88
|
-
{state.proposal ? (
|
|
89
|
-
<div class="proposal">
|
|
90
|
-
<p>Run “{state.proposal.tool}”?</p>
|
|
91
|
-
<button on={intents.approve}>Approve</button>
|
|
92
|
-
<button on={intents.reject}>Reject</button>
|
|
93
|
-
</div>
|
|
94
|
-
) : null}
|
|
95
|
-
<form intent={intents.send}>
|
|
96
|
-
<input name="text" placeholder={state.busy ? 'Thinking…' : 'Try: add a task to buy milk'} />
|
|
97
|
-
<button type="submit">Send</button>
|
|
98
|
-
</form>
|
|
99
|
-
</div>
|
|
100
|
-
) : null}
|
|
101
|
-
</aside>
|
|
102
|
-
),
|
|
103
|
-
});
|