create-theokit 1.0.4 → 1.0.6
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
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
/* TheoKit —
|
|
1
|
+
/* TheoKit — design system */
|
|
2
2
|
|
|
3
3
|
:root {
|
|
4
4
|
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
5
5
|
--font-mono: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas, monospace;
|
|
6
|
-
|
|
7
|
-
--bg
|
|
6
|
+
|
|
7
|
+
--bg: #fafafa;
|
|
8
|
+
--bg-content: #ffffff;
|
|
8
9
|
--card: #ffffff;
|
|
9
10
|
--border: #e5e5e5;
|
|
10
11
|
--text: #171717;
|
|
@@ -12,6 +13,10 @@
|
|
|
12
13
|
--text-muted: #999999;
|
|
13
14
|
--accent: #6366f1;
|
|
14
15
|
--accent-hover: #4f46e5;
|
|
16
|
+
--btn-primary-bg: #171717;
|
|
17
|
+
--btn-primary-hover: #383838;
|
|
18
|
+
--btn-secondary-hover: #f2f2f2;
|
|
19
|
+
--btn-secondary-border: #ebebeb;
|
|
15
20
|
--green: #22c55e;
|
|
16
21
|
--red: #ef4444;
|
|
17
22
|
--yellow: #eab308;
|
|
@@ -20,7 +25,7 @@
|
|
|
20
25
|
@media (prefers-color-scheme: dark) {
|
|
21
26
|
:root {
|
|
22
27
|
--bg: #0a0a0a;
|
|
23
|
-
--bg-
|
|
28
|
+
--bg-content: #0a0a0a;
|
|
24
29
|
--card: #141414;
|
|
25
30
|
--border: #2a2a2a;
|
|
26
31
|
--text: #ededed;
|
|
@@ -28,11 +33,14 @@
|
|
|
28
33
|
--text-muted: #666666;
|
|
29
34
|
--accent: #818cf8;
|
|
30
35
|
--accent-hover: #6366f1;
|
|
36
|
+
--btn-primary-bg: #ededed;
|
|
37
|
+
--btn-primary-hover: #cccccc;
|
|
38
|
+
--btn-secondary-hover: #1a1a1a;
|
|
39
|
+
--btn-secondary-border: #2a2a2a;
|
|
31
40
|
--green: #22c55e;
|
|
32
41
|
--red: #ef4444;
|
|
33
42
|
--yellow: #eab308;
|
|
34
43
|
}
|
|
35
|
-
|
|
36
44
|
html { color-scheme: dark; }
|
|
37
45
|
}
|
|
38
46
|
|
|
@@ -50,31 +58,146 @@ body {
|
|
|
50
58
|
min-height: 100vh;
|
|
51
59
|
display: flex;
|
|
52
60
|
flex-direction: column;
|
|
61
|
+
align-items: center;
|
|
53
62
|
-webkit-font-smoothing: antialiased;
|
|
54
63
|
-moz-osx-font-smoothing: grayscale;
|
|
55
64
|
}
|
|
56
65
|
|
|
66
|
+
a { color: inherit; text-decoration: none; }
|
|
57
67
|
code, pre { font-family: var(--font-mono); }
|
|
58
68
|
|
|
59
|
-
|
|
69
|
+
/* ─── Page container ────────────────────────────────── */
|
|
60
70
|
|
|
61
|
-
|
|
71
|
+
.page {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex: 1;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
align-items: center;
|
|
76
|
+
width: 100%;
|
|
77
|
+
}
|
|
62
78
|
|
|
63
|
-
.
|
|
64
|
-
|
|
65
|
-
margin: 0 auto;
|
|
66
|
-
padding: 24px;
|
|
79
|
+
.main {
|
|
80
|
+
display: flex;
|
|
67
81
|
flex: 1;
|
|
82
|
+
flex-direction: column;
|
|
68
83
|
width: 100%;
|
|
84
|
+
max-width: 900px;
|
|
85
|
+
background: var(--bg-content);
|
|
86
|
+
padding: 80px 48px 48px;
|
|
69
87
|
}
|
|
70
88
|
|
|
71
|
-
|
|
89
|
+
@media (max-width: 768px) {
|
|
90
|
+
.main { padding: 48px 20px 32px; }
|
|
91
|
+
}
|
|
72
92
|
|
|
73
|
-
|
|
93
|
+
/* ─── Hero ──────────────────────────────────────────── */
|
|
94
|
+
|
|
95
|
+
.hero {
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-direction: column;
|
|
98
|
+
align-items: center;
|
|
99
|
+
text-align: center;
|
|
100
|
+
gap: 16px;
|
|
101
|
+
margin-bottom: 48px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.hero-logo {
|
|
105
|
+
border-radius: 16px;
|
|
106
|
+
margin-bottom: 8px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.hero h1 {
|
|
110
|
+
font-size: 40px;
|
|
111
|
+
font-weight: 700;
|
|
112
|
+
letter-spacing: -2.4px;
|
|
113
|
+
line-height: 48px;
|
|
114
|
+
text-wrap: balance;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.hero .tagline {
|
|
118
|
+
font-size: 18px;
|
|
119
|
+
line-height: 28px;
|
|
120
|
+
color: var(--text-secondary);
|
|
121
|
+
max-width: 440px;
|
|
122
|
+
text-wrap: balance;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.hero .hint {
|
|
126
|
+
font-size: 14px;
|
|
127
|
+
color: var(--text-muted);
|
|
128
|
+
margin-top: 8px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.hero .hint code {
|
|
74
132
|
background: var(--card);
|
|
75
133
|
border: 1px solid var(--border);
|
|
76
|
-
|
|
77
|
-
|
|
134
|
+
padding: 2px 8px;
|
|
135
|
+
border-radius: 6px;
|
|
136
|
+
font-size: 13px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@media (max-width: 768px) {
|
|
140
|
+
.hero h1 { font-size: 32px; line-height: 40px; letter-spacing: -1.92px; }
|
|
141
|
+
.hero .tagline { font-size: 16px; }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* ─── CTA Buttons ───────────────────────────────────── */
|
|
145
|
+
|
|
146
|
+
.ctas {
|
|
147
|
+
display: flex;
|
|
148
|
+
gap: 12px;
|
|
149
|
+
margin-top: 8px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.btn {
|
|
153
|
+
display: inline-flex;
|
|
154
|
+
justify-content: center;
|
|
155
|
+
align-items: center;
|
|
156
|
+
height: 40px;
|
|
157
|
+
padding: 0 20px;
|
|
158
|
+
border-radius: 128px;
|
|
159
|
+
border: 1px solid transparent;
|
|
160
|
+
font-size: 14px;
|
|
161
|
+
font-weight: 500;
|
|
162
|
+
cursor: pointer;
|
|
163
|
+
transition: background 0.2s, border-color 0.2s;
|
|
164
|
+
text-decoration: none;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.btn.primary {
|
|
168
|
+
background: var(--btn-primary-bg);
|
|
169
|
+
color: var(--bg);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.btn.secondary {
|
|
173
|
+
border-color: var(--btn-secondary-border);
|
|
174
|
+
color: var(--text);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@media (hover: hover) and (pointer: fine) {
|
|
178
|
+
.btn.primary:hover { background: var(--btn-primary-hover); }
|
|
179
|
+
.btn.secondary:hover { background: var(--btn-secondary-hover); border-color: transparent; }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* ─── Role selector ─────────────────────────────────── */
|
|
183
|
+
|
|
184
|
+
.role-bar {
|
|
185
|
+
display: flex;
|
|
186
|
+
justify-content: center;
|
|
187
|
+
align-items: center;
|
|
188
|
+
gap: 8px;
|
|
189
|
+
margin-bottom: 32px;
|
|
190
|
+
font-size: 14px;
|
|
191
|
+
color: var(--text-muted);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.role-bar select {
|
|
195
|
+
padding: 6px 12px;
|
|
196
|
+
background: var(--card);
|
|
197
|
+
border: 1px solid var(--border);
|
|
198
|
+
border-radius: 6px;
|
|
199
|
+
color: var(--text);
|
|
200
|
+
font-size: 13px;
|
|
78
201
|
}
|
|
79
202
|
|
|
80
203
|
/* ─── Grid ──────────────────────────────────────────── */
|
|
@@ -83,24 +206,35 @@ a { color: inherit; text-decoration: none; }
|
|
|
83
206
|
display: grid;
|
|
84
207
|
grid-template-columns: 1fr 1fr;
|
|
85
208
|
gap: 24px;
|
|
209
|
+
width: 100%;
|
|
86
210
|
}
|
|
87
211
|
|
|
88
212
|
@media (max-width: 768px) {
|
|
89
213
|
.grid { grid-template-columns: 1fr; }
|
|
90
214
|
}
|
|
91
215
|
|
|
92
|
-
/* ───
|
|
216
|
+
/* ─── Cards ─────────────────────────────────────────── */
|
|
93
217
|
|
|
94
|
-
|
|
95
|
-
|
|
218
|
+
.card {
|
|
219
|
+
background: var(--card);
|
|
220
|
+
border: 1px solid var(--border);
|
|
221
|
+
border-radius: 12px;
|
|
222
|
+
padding: 24px;
|
|
223
|
+
}
|
|
96
224
|
|
|
97
|
-
.
|
|
98
|
-
|
|
225
|
+
.card h2 {
|
|
226
|
+
font-size: 15px;
|
|
227
|
+
font-weight: 600;
|
|
228
|
+
margin-bottom: 16px;
|
|
229
|
+
display: flex;
|
|
230
|
+
align-items: center;
|
|
231
|
+
gap: 8px;
|
|
232
|
+
}
|
|
99
233
|
|
|
100
234
|
/* ─── Badges ────────────────────────────────────────── */
|
|
101
235
|
|
|
102
236
|
.badge {
|
|
103
|
-
font-size:
|
|
237
|
+
font-size: 11px;
|
|
104
238
|
padding: 2px 10px;
|
|
105
239
|
border-radius: 99px;
|
|
106
240
|
font-weight: 500;
|
|
@@ -115,87 +249,88 @@ h2 { font-size: 1rem; font-weight: 600; margin-bottom: 16px; display: flex; alig
|
|
|
115
249
|
|
|
116
250
|
/* ─── Table ─────────────────────────────────────────── */
|
|
117
251
|
|
|
118
|
-
table { width: 100%; border-collapse: collapse; font-size:
|
|
119
|
-
th {
|
|
120
|
-
|
|
121
|
-
|
|
252
|
+
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
253
|
+
th {
|
|
254
|
+
text-align: left; padding: 8px 6px;
|
|
255
|
+
color: var(--text-muted); border-bottom: 1px solid var(--border);
|
|
256
|
+
font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
|
|
257
|
+
}
|
|
258
|
+
td { padding: 8px 6px; border-bottom: 1px solid var(--border); }
|
|
259
|
+
tr.done td { opacity: 0.4; text-decoration: line-through; }
|
|
122
260
|
|
|
123
|
-
.prio { font-size:
|
|
261
|
+
.prio { font-size: 11px; padding: 2px 8px; border-radius: 99px; font-weight: 500; }
|
|
124
262
|
.prio-high { background: color-mix(in srgb, var(--red) 12%, transparent); color: var(--red); }
|
|
125
|
-
.prio-
|
|
263
|
+
.prio-medium { background: color-mix(in srgb, var(--yellow) 12%, transparent); color: var(--yellow); }
|
|
126
264
|
.prio-low { background: color-mix(in srgb, var(--green) 12%, transparent); color: var(--green); }
|
|
127
265
|
|
|
128
266
|
/* ─── Forms ─────────────────────────────────────────── */
|
|
129
267
|
|
|
130
268
|
.create-bar { display: flex; gap: 8px; margin-top: 16px; }
|
|
131
269
|
.create-bar input {
|
|
132
|
-
flex: 1; padding:
|
|
133
|
-
background: var(--bg
|
|
134
|
-
border-radius: 8px; color: var(--text); font-size:
|
|
270
|
+
flex: 1; padding: 8px 12px;
|
|
271
|
+
background: var(--bg); border: 1px solid var(--border);
|
|
272
|
+
border-radius: 8px; color: var(--text); font-size: 13px;
|
|
135
273
|
outline: none; transition: border-color 0.2s;
|
|
136
274
|
}
|
|
137
275
|
.create-bar input:focus { border-color: var(--accent); }
|
|
138
276
|
.create-bar select {
|
|
139
|
-
padding: 10px
|
|
277
|
+
padding: 8px 10px; background: var(--bg);
|
|
140
278
|
border: 1px solid var(--border); border-radius: 8px;
|
|
141
|
-
color: var(--text); font-size:
|
|
279
|
+
color: var(--text); font-size: 13px;
|
|
142
280
|
}
|
|
143
281
|
.create-bar button, .chat-bar button {
|
|
144
|
-
padding:
|
|
282
|
+
padding: 8px 16px; background: var(--accent); color: white;
|
|
145
283
|
border: none; border-radius: 8px; cursor: pointer;
|
|
146
|
-
font-weight: 600; font-size:
|
|
284
|
+
font-weight: 600; font-size: 13px; transition: background 0.2s;
|
|
147
285
|
}
|
|
148
|
-
|
|
149
286
|
@media (hover: hover) and (pointer: fine) {
|
|
150
287
|
.create-bar button:hover, .chat-bar button:hover { background: var(--accent-hover); }
|
|
151
288
|
}
|
|
152
|
-
|
|
153
289
|
.create-bar button:disabled, .chat-bar button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
154
|
-
.error { color: var(--red); font-size:
|
|
155
|
-
|
|
156
|
-
/* ─── Role selector ─────────────────────────────────── */
|
|
157
|
-
|
|
158
|
-
.role-bar { margin-top: 14px; display: flex; align-items: center; gap: 8px; }
|
|
159
|
-
.role-bar label { font-size: 0.8rem; color: var(--text-muted); }
|
|
160
|
-
.role-bar select {
|
|
161
|
-
padding: 6px 12px; background: var(--bg-secondary);
|
|
162
|
-
border: 1px solid var(--border); border-radius: 6px;
|
|
163
|
-
color: var(--text); font-size: 0.8rem;
|
|
164
|
-
}
|
|
290
|
+
.error { color: var(--red); font-size: 13px; margin-top: 6px; }
|
|
165
291
|
|
|
166
292
|
/* ─── Chat ──────────────────────────────────────────── */
|
|
167
293
|
|
|
168
294
|
.chat-box {
|
|
169
|
-
height:
|
|
170
|
-
background: var(--bg
|
|
295
|
+
height: 320px; overflow-y: auto; padding: 14px;
|
|
296
|
+
background: var(--bg); border: 1px solid var(--border);
|
|
171
297
|
border-radius: 10px; margin-bottom: 12px;
|
|
172
|
-
font-size:
|
|
298
|
+
font-size: 13px; line-height: 1.6;
|
|
173
299
|
}
|
|
174
300
|
|
|
175
|
-
.msg { margin-bottom:
|
|
301
|
+
.msg { margin-bottom: 8px; padding: 8px 12px; border-radius: 8px; }
|
|
176
302
|
.msg.user { background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent); }
|
|
177
303
|
.msg.agent { background: var(--card); border: 1px solid var(--border); }
|
|
178
|
-
.msg.tool { background: color-mix(in srgb, var(--yellow) 8%, transparent); color: var(--yellow); font-size:
|
|
179
|
-
.msg.system { color: var(--text-muted); font-size:
|
|
180
|
-
.msg.error { color: var(--red); font-size:
|
|
304
|
+
.msg.tool { background: color-mix(in srgb, var(--yellow) 8%, transparent); color: var(--yellow); font-size: 12px; font-family: var(--font-mono); }
|
|
305
|
+
.msg.system { color: var(--text-muted); font-size: 12px; font-style: italic; }
|
|
306
|
+
.msg.error { color: var(--red); font-size: 13px; }
|
|
181
307
|
|
|
182
308
|
.chat-bar { display: flex; gap: 8px; }
|
|
183
309
|
.chat-bar input {
|
|
184
|
-
flex: 1; padding:
|
|
185
|
-
background: var(--bg
|
|
186
|
-
border-radius: 10px; color: var(--text); font-size:
|
|
310
|
+
flex: 1; padding: 10px 14px;
|
|
311
|
+
background: var(--bg); border: 1px solid var(--border);
|
|
312
|
+
border-radius: 10px; color: var(--text); font-size: 14px;
|
|
187
313
|
outline: none; transition: border-color 0.2s;
|
|
188
314
|
}
|
|
189
315
|
.chat-bar input:focus { border-color: var(--accent); }
|
|
190
316
|
|
|
191
|
-
|
|
317
|
+
/* ─── Footer ────────────────────────────────────────── */
|
|
318
|
+
|
|
319
|
+
.footer {
|
|
320
|
+
text-align: center;
|
|
321
|
+
padding: 40px 24px 32px;
|
|
322
|
+
font-size: 13px;
|
|
323
|
+
color: var(--text-muted);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.footer a { color: var(--text-secondary); transition: color 0.2s; }
|
|
327
|
+
.footer a:hover { color: var(--accent); }
|
|
192
328
|
|
|
193
329
|
/* ─── Loading ───────────────────────────────────────── */
|
|
194
330
|
|
|
195
331
|
.loading-spinner {
|
|
196
|
-
width:
|
|
197
|
-
|
|
198
|
-
border: 3px solid var(--border);
|
|
332
|
+
width: 24px; height: 24px;
|
|
333
|
+
border: 2px solid var(--border);
|
|
199
334
|
border-top-color: var(--accent);
|
|
200
335
|
border-radius: 50%;
|
|
201
336
|
animation: spin 0.8s linear infinite;
|
|
@@ -204,93 +339,3 @@ tr.done td { opacity: 0.45; text-decoration: line-through; }
|
|
|
204
339
|
@keyframes spin {
|
|
205
340
|
to { transform: rotate(360deg); }
|
|
206
341
|
}
|
|
207
|
-
|
|
208
|
-
/* ─── Hero ──────────────────────────────────────────── */
|
|
209
|
-
|
|
210
|
-
.hero {
|
|
211
|
-
text-align: center;
|
|
212
|
-
padding: 48px 24px 32px;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
.hero img {
|
|
216
|
-
margin-bottom: 16px;
|
|
217
|
-
border-radius: 16px;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
.hero h1 {
|
|
221
|
-
font-size: 2.5rem;
|
|
222
|
-
font-weight: 800;
|
|
223
|
-
letter-spacing: -0.03em;
|
|
224
|
-
margin-bottom: 4px;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.hero .tagline {
|
|
228
|
-
color: var(--text-secondary);
|
|
229
|
-
font-size: 1.1rem;
|
|
230
|
-
margin-bottom: 24px;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
.hero-links {
|
|
234
|
-
display: flex;
|
|
235
|
-
justify-content: center;
|
|
236
|
-
gap: 12px;
|
|
237
|
-
margin-bottom: 24px;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
.btn {
|
|
241
|
-
display: inline-flex;
|
|
242
|
-
align-items: center;
|
|
243
|
-
padding: 10px 24px;
|
|
244
|
-
border-radius: 99px;
|
|
245
|
-
font-weight: 600;
|
|
246
|
-
font-size: 0.9rem;
|
|
247
|
-
text-decoration: none;
|
|
248
|
-
transition: background 0.2s, border-color 0.2s;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.btn.primary {
|
|
252
|
-
background: var(--accent);
|
|
253
|
-
color: white;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
.btn.secondary {
|
|
257
|
-
background: transparent;
|
|
258
|
-
border: 1px solid var(--border);
|
|
259
|
-
color: var(--text);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
@media (hover: hover) and (pointer: fine) {
|
|
263
|
-
.btn.primary:hover { background: var(--accent-hover); }
|
|
264
|
-
.btn.secondary:hover { background: var(--bg-secondary); }
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
.hero .hint {
|
|
268
|
-
color: var(--text-muted);
|
|
269
|
-
font-size: 0.8rem;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
.hero .hint code {
|
|
273
|
-
font-family: var(--font-mono);
|
|
274
|
-
background: var(--bg-secondary);
|
|
275
|
-
padding: 2px 6px;
|
|
276
|
-
border-radius: 4px;
|
|
277
|
-
font-size: 0.75rem;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/* ─── Footer ────────────────────────────────────────── */
|
|
281
|
-
|
|
282
|
-
.footer {
|
|
283
|
-
text-align: center;
|
|
284
|
-
padding: 32px 24px;
|
|
285
|
-
color: var(--text-muted);
|
|
286
|
-
font-size: 0.8rem;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
.footer a {
|
|
290
|
-
color: var(--accent);
|
|
291
|
-
text-decoration: none;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
.footer a:hover {
|
|
295
|
-
text-decoration: underline;
|
|
296
|
-
}
|
|
@@ -2,199 +2,260 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback, useRef, type FormEvent } from 'react'
|
|
4
4
|
|
|
5
|
-
// ── Types ──
|
|
6
|
-
|
|
7
5
|
interface Task {
|
|
8
6
|
id: number
|
|
9
7
|
title: string
|
|
10
8
|
priority: 'high' | 'medium' | 'low'
|
|
11
9
|
done: boolean
|
|
12
10
|
}
|
|
13
|
-
|
|
14
11
|
type Role = '' | 'user' | 'admin'
|
|
15
|
-
|
|
16
12
|
interface ChatMsg {
|
|
17
13
|
role: 'user' | 'agent' | 'tool' | 'system' | 'error'
|
|
18
14
|
text: string
|
|
19
15
|
}
|
|
20
16
|
|
|
21
|
-
// ── Page ──
|
|
22
|
-
|
|
23
17
|
export default function Page() {
|
|
24
18
|
const [tasks, setTasks] = useState<Task[]>([])
|
|
25
19
|
const [role, setRole] = useState<Role>('user')
|
|
26
20
|
const [title, setTitle] = useState('')
|
|
27
21
|
const [priority, setPriority] = useState<Task['priority']>('medium')
|
|
28
22
|
const [formError, setFormError] = useState('')
|
|
29
|
-
const [chat, setChat] = useState<ChatMsg[]>([
|
|
23
|
+
const [chat, setChat] = useState<ChatMsg[]>([
|
|
24
|
+
{ role: 'system', text: 'Ask me to list, create, or complete tasks...' },
|
|
25
|
+
])
|
|
30
26
|
const [chatInput, setChatInput] = useState('')
|
|
31
27
|
const [chatBusy, setChatBusy] = useState(false)
|
|
32
28
|
const chatRef = useRef<HTMLDivElement>(null)
|
|
33
29
|
|
|
34
|
-
const
|
|
30
|
+
const hdrs = useCallback((): Record<string, string> => {
|
|
35
31
|
const h: Record<string, string> = { 'Content-Type': 'application/json' }
|
|
36
32
|
if (role) h['x-role'] = role
|
|
37
33
|
return h
|
|
38
34
|
}, [role])
|
|
39
35
|
|
|
40
|
-
// Fetch tasks on mount + when role changes
|
|
41
36
|
const loadTasks = useCallback(async () => {
|
|
42
37
|
const res = await fetch('/api/tasks')
|
|
43
38
|
if (res.ok) setTasks(await res.json())
|
|
44
39
|
}, [])
|
|
45
40
|
|
|
46
|
-
useEffect(() => {
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
loadTasks()
|
|
43
|
+
}, [loadTasks])
|
|
47
44
|
|
|
48
|
-
// Create task
|
|
49
45
|
const createTask = async (e: FormEvent) => {
|
|
50
46
|
e.preventDefault()
|
|
51
47
|
setFormError('')
|
|
52
48
|
if (!title.trim()) return
|
|
53
|
-
const res = await fetch('/api/tasks', {
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
const res = await fetch('/api/tasks', {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: hdrs(),
|
|
52
|
+
body: JSON.stringify({ title, priority }),
|
|
53
|
+
})
|
|
54
|
+
if (res.status === 403) {
|
|
55
|
+
setFormError('403 — Need User role')
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
const b = await res.json()
|
|
60
|
+
setFormError(b.error?.issues?.[0]?.message ?? `Error ${res.status}`)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
56
63
|
setTitle('')
|
|
57
64
|
loadTasks()
|
|
58
65
|
}
|
|
59
66
|
|
|
60
|
-
// AI Chat
|
|
61
67
|
const sendChat = async () => {
|
|
62
68
|
const msg = chatInput.trim()
|
|
63
69
|
if (!msg || chatBusy) return
|
|
64
70
|
setChatInput('')
|
|
65
|
-
setChat(c => [...c, { role: 'user', text: msg }])
|
|
71
|
+
setChat((c) => [...c, { role: 'user', text: msg }])
|
|
66
72
|
setChatBusy(true)
|
|
67
|
-
|
|
68
73
|
try {
|
|
69
74
|
const res = await fetch('/api/agents/assistant/chat', {
|
|
70
|
-
method: 'POST',
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: hdrs(),
|
|
71
77
|
body: JSON.stringify({ message: msg, sessionId: 'session-' + Date.now() }),
|
|
72
78
|
})
|
|
73
|
-
if (res.status === 403) {
|
|
74
|
-
|
|
79
|
+
if (res.status === 403) {
|
|
80
|
+
setChat((c) => [...c, { role: 'error', text: '403 — Need User role' }])
|
|
81
|
+
return
|
|
82
|
+
}
|
|
75
83
|
const reader = res.body?.getReader()
|
|
76
84
|
if (!reader) return
|
|
77
85
|
const decoder = new TextDecoder()
|
|
78
|
-
let buf = '',
|
|
79
|
-
|
|
86
|
+
let buf = '',
|
|
87
|
+
agentText = ''
|
|
80
88
|
while (true) {
|
|
81
89
|
const { done, value } = await reader.read()
|
|
82
90
|
if (done) break
|
|
83
91
|
buf += decoder.decode(value, { stream: true })
|
|
84
|
-
const lines = buf.split('\n')
|
|
92
|
+
const lines = buf.split('\n')
|
|
93
|
+
buf = lines.pop() ?? ''
|
|
85
94
|
for (const line of lines) {
|
|
86
95
|
if (!line.startsWith('data: ')) continue
|
|
87
96
|
try {
|
|
88
97
|
const ev = JSON.parse(line.slice(6))
|
|
89
98
|
if (ev.type === 'text_delta') agentText += ev.content
|
|
90
|
-
else if (ev.type === 'tool_call')
|
|
91
|
-
|
|
92
|
-
else if (ev.type === '
|
|
93
|
-
|
|
99
|
+
else if (ev.type === 'tool_call')
|
|
100
|
+
setChat((c) => [...c, { role: 'tool', text: `🔧 ${ev.toolName}` }])
|
|
101
|
+
else if (ev.type === 'tool_result')
|
|
102
|
+
setChat((c) => [...c, { role: 'tool', text: `✅ ${(ev.output ?? '').slice(0, 80)}` }])
|
|
103
|
+
else if (ev.type === 'error')
|
|
104
|
+
setChat((c) => [...c, { role: 'error', text: ev.message }])
|
|
105
|
+
} catch {
|
|
106
|
+
/* partial */
|
|
107
|
+
}
|
|
94
108
|
}
|
|
95
109
|
}
|
|
96
|
-
if (agentText) setChat(c => [...c, { role: 'agent', text: agentText }])
|
|
110
|
+
if (agentText) setChat((c) => [...c, { role: 'agent', text: agentText }])
|
|
97
111
|
loadTasks()
|
|
98
112
|
} catch (err) {
|
|
99
|
-
setChat(c => [
|
|
113
|
+
setChat((c) => [
|
|
114
|
+
...c,
|
|
115
|
+
{ role: 'error', text: `Error: ${err instanceof Error ? err.message : String(err)}` },
|
|
116
|
+
])
|
|
100
117
|
} finally {
|
|
101
118
|
setChatBusy(false)
|
|
102
119
|
}
|
|
103
120
|
}
|
|
104
121
|
|
|
105
|
-
useEffect(() => {
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
chatRef.current?.scrollTo(0, chatRef.current.scrollHeight)
|
|
124
|
+
}, [chat])
|
|
106
125
|
|
|
107
126
|
return (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
127
|
+
<div className="page">
|
|
128
|
+
<div className="main">
|
|
129
|
+
{/* Hero */}
|
|
130
|
+
<header className="hero">
|
|
131
|
+
<img src="/logo.png" alt="TheoKit" width={72} height={72} className="hero-logo" />
|
|
132
|
+
<h1>TheoKit</h1>
|
|
133
|
+
<p className="tagline">Build the app your agent lives in.</p>
|
|
134
|
+
<nav className="ctas">
|
|
135
|
+
<a
|
|
136
|
+
href="https://usetheo.dev"
|
|
137
|
+
target="_blank"
|
|
138
|
+
rel="noopener noreferrer"
|
|
139
|
+
className="btn primary"
|
|
140
|
+
>
|
|
141
|
+
Get Started
|
|
142
|
+
</a>
|
|
143
|
+
<a
|
|
144
|
+
href="https://github.com/usetheodev/theokit"
|
|
145
|
+
target="_blank"
|
|
146
|
+
rel="noopener noreferrer"
|
|
147
|
+
className="btn secondary"
|
|
148
|
+
>
|
|
149
|
+
Documentation
|
|
150
|
+
</a>
|
|
151
|
+
</nav>
|
|
152
|
+
<p className="hint">
|
|
153
|
+
Edit <code>app/page.tsx</code> to get started.
|
|
154
|
+
</p>
|
|
155
|
+
</header>
|
|
126
156
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
157
|
+
{/* Role */}
|
|
158
|
+
<div className="role-bar">
|
|
159
|
+
<label htmlFor="role">Role:</label>
|
|
160
|
+
<select id="role" value={role} onChange={(e) => setRole(e.target.value as Role)}>
|
|
161
|
+
<option value="">None (public)</option>
|
|
162
|
+
<option value="user">User</option>
|
|
163
|
+
<option value="admin">Admin</option>
|
|
164
|
+
</select>
|
|
165
|
+
</div>
|
|
136
166
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
<
|
|
148
|
-
<
|
|
149
|
-
<td>{t.done ? 'Done' : 'To do'}</td>
|
|
167
|
+
{/* Content */}
|
|
168
|
+
<div className="grid">
|
|
169
|
+
<section className="card">
|
|
170
|
+
<h2>
|
|
171
|
+
Tasks <span className="badge">@Controller</span>
|
|
172
|
+
</h2>
|
|
173
|
+
<table>
|
|
174
|
+
<thead>
|
|
175
|
+
<tr>
|
|
176
|
+
<th>Task</th>
|
|
177
|
+
<th>Priority</th>
|
|
178
|
+
<th>Status</th>
|
|
150
179
|
</tr>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
180
|
+
</thead>
|
|
181
|
+
<tbody>
|
|
182
|
+
{tasks.map((t) => (
|
|
183
|
+
<tr key={t.id} className={t.done ? 'done' : ''}>
|
|
184
|
+
<td>
|
|
185
|
+
{t.done ? '✅ ' : '○ '}
|
|
186
|
+
{t.title}
|
|
187
|
+
</td>
|
|
188
|
+
<td>
|
|
189
|
+
<span className={`prio prio-${t.priority}`}>{t.priority}</span>
|
|
190
|
+
</td>
|
|
191
|
+
<td>{t.done ? 'Done' : 'To do'}</td>
|
|
192
|
+
</tr>
|
|
193
|
+
))}
|
|
194
|
+
</tbody>
|
|
195
|
+
</table>
|
|
196
|
+
<form onSubmit={createTask} className="create-bar">
|
|
197
|
+
<input
|
|
198
|
+
value={title}
|
|
199
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
200
|
+
placeholder="New task..."
|
|
201
|
+
required
|
|
202
|
+
minLength={3}
|
|
203
|
+
/>
|
|
204
|
+
<select
|
|
205
|
+
value={priority}
|
|
206
|
+
onChange={(e) => setPriority(e.target.value as Task['priority'])}
|
|
207
|
+
>
|
|
208
|
+
<option value="medium">Medium</option>
|
|
209
|
+
<option value="high">High</option>
|
|
210
|
+
<option value="low">Low</option>
|
|
211
|
+
</select>
|
|
212
|
+
<button type="submit">Add</button>
|
|
213
|
+
</form>
|
|
214
|
+
{formError && <p className="error">{formError}</p>}
|
|
215
|
+
</section>
|
|
165
216
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
{chat
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
217
|
+
<section className="card">
|
|
218
|
+
<h2>
|
|
219
|
+
AI Assistant <span className="badge badge-ai">@Agent + SSE</span>
|
|
220
|
+
</h2>
|
|
221
|
+
<div ref={chatRef} className="chat-box">
|
|
222
|
+
{chat.map((m, i) => (
|
|
223
|
+
<div key={i} className={`msg ${m.role}`}>
|
|
224
|
+
{m.role === 'user' ? `You: ${m.text}` : m.text}
|
|
225
|
+
</div>
|
|
226
|
+
))}
|
|
227
|
+
</div>
|
|
228
|
+
<div className="chat-bar">
|
|
229
|
+
<input
|
|
230
|
+
value={chatInput}
|
|
231
|
+
onChange={(e) => setChatInput(e.target.value)}
|
|
232
|
+
onKeyDown={(e) => e.key === 'Enter' && sendChat()}
|
|
233
|
+
placeholder="Message the AI assistant..."
|
|
234
|
+
disabled={chatBusy}
|
|
235
|
+
/>
|
|
236
|
+
<button type="button" onClick={sendChat} disabled={chatBusy}>
|
|
237
|
+
Send
|
|
238
|
+
</button>
|
|
239
|
+
</div>
|
|
240
|
+
</section>
|
|
241
|
+
</div>
|
|
186
242
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
<p>
|
|
243
|
+
{/* Footer */}
|
|
244
|
+
<footer className="footer">
|
|
190
245
|
Powered by{' '}
|
|
191
|
-
<a href="https://usetheo.dev" target="_blank" rel="noopener noreferrer">
|
|
246
|
+
<a href="https://usetheo.dev" target="_blank" rel="noopener noreferrer">
|
|
247
|
+
TheoKit
|
|
248
|
+
</a>
|
|
192
249
|
{' · '}
|
|
193
|
-
<a href="https://github.com/usetheodev/theokit" target="_blank" rel="noopener noreferrer">
|
|
250
|
+
<a href="https://github.com/usetheodev/theokit" target="_blank" rel="noopener noreferrer">
|
|
251
|
+
GitHub
|
|
252
|
+
</a>
|
|
194
253
|
{' · '}
|
|
195
|
-
<a href="https://discord.usetheo.dev" target="_blank" rel="noopener noreferrer">
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
254
|
+
<a href="https://discord.usetheo.dev" target="_blank" rel="noopener noreferrer">
|
|
255
|
+
Discord
|
|
256
|
+
</a>
|
|
257
|
+
</footer>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
199
260
|
)
|
|
200
261
|
}
|