@narumitw/pi-webui 0.28.0 → 0.29.2

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.
@@ -0,0 +1,128 @@
1
+ import { ChevronDownIcon, Cross2Icon, EyeOpenIcon, TrashIcon } from "@radix-ui/react-icons";
2
+ import { Button, Container, Flex, Heading, IconButton, Separator, Text } from "@radix-ui/themes";
3
+ import { AlertDialog, Collapsible, Dialog, Tooltip } from "radix-ui";
4
+ import { webClient } from "./client.js";
5
+
6
+ export function ClearAttachmentsDialog({ count, onOpenChange, open }) {
7
+ return (
8
+ <AlertDialog.Root onOpenChange={onOpenChange} open={open}>
9
+ <AlertDialog.Overlay className="dialog-overlay" />
10
+ <AlertDialog.Content className="dialog-content">
11
+ <AlertDialog.Title asChild>
12
+ <Heading as="h2" size="4">
13
+ Clear attachments?
14
+ </Heading>
15
+ </AlertDialog.Title>
16
+ <AlertDialog.Description asChild>
17
+ <Text as="p" color="gray" size="2">
18
+ Remove all {count} unsent image attachments? Message text will be kept.
19
+ </Text>
20
+ </AlertDialog.Description>
21
+ <Flex gap="3" justify="end">
22
+ <AlertDialog.Cancel asChild>
23
+ <Button color="gray" variant="soft">
24
+ Cancel
25
+ </Button>
26
+ </AlertDialog.Cancel>
27
+ <AlertDialog.Action asChild>
28
+ <Button color="red" onClick={() => void webClient.clearAttachments()} variant="solid">
29
+ <TrashIcon /> Clear attachments
30
+ </Button>
31
+ </AlertDialog.Action>
32
+ </Flex>
33
+ </AlertDialog.Content>
34
+ </AlertDialog.Root>
35
+ );
36
+ }
37
+
38
+ export function ForgetImageDialog({ id, onOpenChange }) {
39
+ return (
40
+ <AlertDialog.Root onOpenChange={onOpenChange} open={Boolean(id)}>
41
+ <AlertDialog.Overlay className="dialog-overlay" />
42
+ <AlertDialog.Content className="dialog-content">
43
+ <AlertDialog.Title asChild>
44
+ <Heading as="h2" size="4">
45
+ Forget retained image?
46
+ </Heading>
47
+ </AlertDialog.Title>
48
+ <AlertDialog.Description asChild>
49
+ <Text as="p" color="gray" size="2">
50
+ This removes the session-only copy. It does not retract provider content.
51
+ </Text>
52
+ </AlertDialog.Description>
53
+ <Flex gap="3" justify="end">
54
+ <AlertDialog.Cancel asChild>
55
+ <Button color="gray" variant="soft">
56
+ Cancel
57
+ </Button>
58
+ </AlertDialog.Cancel>
59
+ <AlertDialog.Action asChild>
60
+ <Button color="red" onClick={() => void webClient.forgetSentImage(id)}>
61
+ <TrashIcon /> Forget
62
+ </Button>
63
+ </AlertDialog.Action>
64
+ </Flex>
65
+ </AlertDialog.Content>
66
+ </AlertDialog.Root>
67
+ );
68
+ }
69
+
70
+ export function ImagePreviewDialog({ image, onOpenChange, revision }) {
71
+ return (
72
+ <Dialog.Root onOpenChange={onOpenChange} open={Boolean(image)}>
73
+ <Dialog.Overlay className="dialog-overlay preview-overlay" />
74
+ <Dialog.Content className="preview-content">
75
+ <Flex align="center" className="preview-header" justify="between">
76
+ <Dialog.Title asChild>
77
+ <Heading className="preview-title" size="3">
78
+ {image?.name ?? "Image preview"}
79
+ </Heading>
80
+ </Dialog.Title>
81
+ <Tooltip.Root>
82
+ <Tooltip.Trigger asChild>
83
+ <Dialog.Close asChild>
84
+ <IconButton aria-label="Close image preview" color="gray" variant="soft">
85
+ <Cross2Icon />
86
+ </IconButton>
87
+ </Dialog.Close>
88
+ </Tooltip.Trigger>
89
+ <Tooltip.Content sideOffset={6}>Close preview</Tooltip.Content>
90
+ </Tooltip.Root>
91
+ </Flex>
92
+ {image && (
93
+ <img
94
+ alt={image.name}
95
+ className="preview-image"
96
+ src={`/api/attachments/${encodeURIComponent(image.id)}/preview?v=${revision}`}
97
+ />
98
+ )}
99
+ </Dialog.Content>
100
+ </Dialog.Root>
101
+ );
102
+ }
103
+
104
+ export function PageFooter() {
105
+ return (
106
+ <footer className="page-footer">
107
+ <Container size="3" px={{ initial: "3", sm: "5" }}>
108
+ <div className="page-footer-content">
109
+ <Separator size="4" />
110
+ <Collapsible.Root>
111
+ <Collapsible.Trigger asChild>
112
+ <Button color="gray" variant="ghost">
113
+ <EyeOpenIcon /> Privacy and limitations{" "}
114
+ <ChevronDownIcon className="disclosure-icon" />
115
+ </Button>
116
+ </Collapsible.Trigger>
117
+ <Collapsible.Content>
118
+ <Text as="p" className="page-footer-copy" color="gray" size="2">
119
+ This loopback page reflects semantic Pi messages, not terminal pixels. Data stays in
120
+ this live Pi process and browser tab. Thinking is collapsed by default.
121
+ </Text>
122
+ </Collapsible.Content>
123
+ </Collapsible.Root>
124
+ </div>
125
+ </Container>
126
+ </footer>
127
+ );
128
+ }
@@ -0,0 +1,576 @@
1
+ :root {
2
+ color-scheme: light dark;
3
+ }
4
+
5
+ * {
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ html {
10
+ min-width: 280px;
11
+ scroll-behavior: smooth;
12
+ }
13
+
14
+ body {
15
+ margin: 0;
16
+ min-height: 100vh;
17
+ background: var(--color-background);
18
+ }
19
+
20
+ .radix-themes {
21
+ min-height: 100vh;
22
+ --pi-shadow: 0 12px 36px color-mix(in srgb, var(--gray-12) 12%, transparent);
23
+ }
24
+
25
+ button,
26
+ label,
27
+ [data-radix-collection-item] {
28
+ touch-action: manipulation;
29
+ }
30
+
31
+ .rt-BaseButton,
32
+ .rt-IconButton {
33
+ min-height: 44px;
34
+ }
35
+
36
+ .rt-IconButton {
37
+ min-width: 44px;
38
+ }
39
+
40
+ .skip-link {
41
+ position: fixed;
42
+ z-index: 100;
43
+ top: var(--space-2);
44
+ left: var(--space-2);
45
+ padding: var(--space-2) var(--space-3);
46
+ border-radius: var(--radius-3);
47
+ color: white;
48
+ background: var(--jade-9);
49
+ transform: translateY(-180%);
50
+ }
51
+
52
+ .skip-link:focus {
53
+ transform: none;
54
+ }
55
+
56
+ .session-header {
57
+ border-bottom: 1px solid var(--gray-a6);
58
+ }
59
+
60
+ .session-header-content {
61
+ display: flex;
62
+ align-items: flex-end;
63
+ justify-content: space-between;
64
+ gap: var(--space-5);
65
+ padding-block: var(--space-5) var(--space-4);
66
+ }
67
+
68
+ .session-identity {
69
+ min-width: 0;
70
+ }
71
+
72
+ .eyebrow {
73
+ margin: 0 0 var(--space-1);
74
+ letter-spacing: 0.09em;
75
+ text-transform: uppercase;
76
+ }
77
+
78
+ #project-name {
79
+ max-width: 100%;
80
+ overflow-wrap: anywhere;
81
+ }
82
+
83
+ #session-name {
84
+ margin: var(--space-1) 0 0;
85
+ overflow-wrap: anywhere;
86
+ }
87
+
88
+ .session-controls {
89
+ flex: none;
90
+ }
91
+
92
+ #connection-status {
93
+ gap: var(--space-1);
94
+ white-space: nowrap;
95
+ }
96
+
97
+ .session-popover {
98
+ z-index: 50;
99
+ width: min(28rem, 85vw);
100
+ padding: var(--space-4);
101
+ border: 1px solid var(--gray-a6);
102
+ border-radius: var(--radius-4);
103
+ background: var(--color-panel-solid);
104
+ box-shadow: var(--pi-shadow);
105
+ }
106
+
107
+ .session-popover #cwd {
108
+ display: block;
109
+ margin-top: var(--space-2);
110
+ white-space: normal;
111
+ overflow-wrap: anywhere;
112
+ }
113
+
114
+ .popover-arrow {
115
+ fill: var(--color-panel-solid);
116
+ }
117
+
118
+ main {
119
+ padding-block: var(--space-4) var(--space-6);
120
+ }
121
+
122
+ .conversation {
123
+ min-height: min(55vh, 640px);
124
+ }
125
+
126
+ #transcript {
127
+ display: grid;
128
+ gap: var(--space-5);
129
+ margin: 0;
130
+ padding: 0;
131
+ list-style: none;
132
+ }
133
+
134
+ .empty-state {
135
+ min-height: 38vh;
136
+ gap: var(--space-2);
137
+ text-align: center;
138
+ }
139
+
140
+ .empty-icon {
141
+ width: 32px;
142
+ height: 32px;
143
+ margin-bottom: var(--space-2);
144
+ color: var(--gray-9);
145
+ }
146
+
147
+ .message {
148
+ width: min(100%, 78ch);
149
+ overflow-wrap: anywhere;
150
+ }
151
+
152
+ .message.assistant {
153
+ padding-inline: var(--space-1);
154
+ }
155
+
156
+ .message.user {
157
+ width: min(90%, 66ch);
158
+ margin-left: auto;
159
+ padding: var(--space-3) var(--space-4);
160
+ border: 1px solid var(--jade-a6);
161
+ border-radius: var(--radius-4);
162
+ background: var(--jade-a3);
163
+ }
164
+
165
+ .message.toolResult,
166
+ .message.other {
167
+ padding: var(--space-3) var(--space-4);
168
+ border-left: 3px solid var(--gray-a7);
169
+ background: var(--gray-a2);
170
+ }
171
+
172
+ .message-heading {
173
+ margin-bottom: var(--space-2);
174
+ letter-spacing: 0.025em;
175
+ }
176
+
177
+ .disclosure-icon {
178
+ transition: transform 150ms ease;
179
+ }
180
+
181
+ .message-disclosure > [data-state="open"] .disclosure-icon,
182
+ .thinking > [data-state="open"] .disclosure-icon,
183
+ .tool [data-state="open"] .disclosure-icon,
184
+ .page-footer [data-state="open"] .disclosure-icon {
185
+ transform: rotate(180deg);
186
+ }
187
+
188
+ .message-body > :first-child,
189
+ .message-markdown > :first-child {
190
+ margin-top: 0;
191
+ }
192
+
193
+ .message-body > :last-child,
194
+ .message-markdown > :last-child {
195
+ margin-bottom: 0;
196
+ }
197
+
198
+ .message-text {
199
+ margin: var(--space-3) 0;
200
+ white-space: pre-wrap;
201
+ }
202
+
203
+ .markdown-heading {
204
+ margin: var(--space-5) 0 var(--space-2);
205
+ line-height: 1.3;
206
+ }
207
+
208
+ .markdown-list {
209
+ margin: var(--space-3) 0;
210
+ padding-left: var(--space-6);
211
+ }
212
+
213
+ .markdown-list li + li {
214
+ margin-top: var(--space-1);
215
+ }
216
+
217
+ .message-markdown blockquote {
218
+ margin: var(--space-3) 0;
219
+ padding-left: var(--space-4);
220
+ border-left: 3px solid var(--gray-a7);
221
+ color: var(--gray-11);
222
+ }
223
+
224
+ pre {
225
+ max-width: 100%;
226
+ margin: 0;
227
+ padding: var(--space-3);
228
+ overflow: auto;
229
+ border-radius: var(--radius-3);
230
+ background: var(--gray-a3);
231
+ font: 0.82rem / 1.45 var(--code-font-family);
232
+ white-space: pre-wrap;
233
+ overflow-wrap: anywhere;
234
+ }
235
+
236
+ .thinking,
237
+ .tool,
238
+ .message-body > [data-radix-collapsible-root] {
239
+ margin-top: var(--space-3);
240
+ }
241
+
242
+ .tool-trigger {
243
+ display: grid;
244
+ width: 100%;
245
+ height: auto;
246
+ min-height: 44px;
247
+ grid-template-columns: auto minmax(0, auto) minmax(0, 1fr) auto;
248
+ justify-content: start;
249
+ text-align: left;
250
+ }
251
+
252
+ .tool-command {
253
+ display: block;
254
+ overflow: hidden;
255
+ color: var(--gray-11);
256
+ text-overflow: ellipsis;
257
+ white-space: nowrap;
258
+ }
259
+
260
+ .tool-content {
261
+ display: grid;
262
+ gap: var(--space-2);
263
+ padding: 0 var(--space-3) var(--space-3);
264
+ }
265
+
266
+ .tool.failed > .rt-Card {
267
+ border-color: var(--red-9);
268
+ }
269
+
270
+ .image-chip {
271
+ width: fit-content;
272
+ max-width: 100%;
273
+ margin-top: var(--space-3);
274
+ padding: var(--space-2) var(--space-3);
275
+ border: 1px solid var(--gray-a6);
276
+ border-radius: var(--radius-4);
277
+ }
278
+
279
+ .blocking-state {
280
+ margin-block: var(--space-4);
281
+ }
282
+
283
+ .jump-latest {
284
+ position: sticky;
285
+ z-index: 8;
286
+ bottom: calc(var(--composer-height, 10rem) + var(--space-4));
287
+ display: flex;
288
+ margin: var(--space-3) auto;
289
+ box-shadow: var(--pi-shadow);
290
+ }
291
+
292
+ .composer {
293
+ position: sticky;
294
+ z-index: 7;
295
+ bottom: var(--space-3);
296
+ margin-top: var(--space-5);
297
+ background: color-mix(in srgb, var(--color-panel-solid) 94%, transparent);
298
+ box-shadow: var(--pi-shadow);
299
+ backdrop-filter: blur(12px);
300
+ transition:
301
+ border-color 150ms,
302
+ background 150ms,
303
+ transform 150ms;
304
+ }
305
+
306
+ .composer.drag-active {
307
+ border-color: var(--jade-9);
308
+ background: color-mix(in srgb, var(--jade-4) 45%, var(--color-panel-solid));
309
+ transform: scale(1.004);
310
+ }
311
+
312
+ #message-input {
313
+ min-height: 48px;
314
+ max-height: 32vh;
315
+ overflow-y: auto;
316
+ }
317
+
318
+ .attachment-summary,
319
+ #composer-status {
320
+ margin: 0;
321
+ }
322
+
323
+ #image-previews {
324
+ margin: 0;
325
+ padding: 0;
326
+ list-style: none;
327
+ }
328
+
329
+ .image-preview-item {
330
+ display: grid;
331
+ width: fit-content;
332
+ max-width: 100%;
333
+ min-width: 0;
334
+ flex: 0 1 auto;
335
+ grid-template-columns: 68px fit-content(14rem) auto;
336
+ align-items: center;
337
+ gap: var(--space-2);
338
+ transition:
339
+ border-color 120ms ease,
340
+ box-shadow 120ms ease,
341
+ transform 120ms ease;
342
+ }
343
+
344
+ .image-preview-item[draggable="true"] {
345
+ cursor: grab;
346
+ }
347
+
348
+ .image-preview-item[draggable="true"]:active {
349
+ cursor: grabbing;
350
+ }
351
+
352
+ .image-preview-item.dragging {
353
+ opacity: 0.5;
354
+ transform: scale(0.985);
355
+ }
356
+
357
+ .image-preview-item.drag-before {
358
+ border-color: var(--jade-9);
359
+ box-shadow: inset 3px 0 var(--jade-9);
360
+ }
361
+
362
+ .image-preview-item.drag-after {
363
+ border-color: var(--jade-9);
364
+ box-shadow: inset -3px 0 var(--jade-9);
365
+ }
366
+
367
+ .attachment-preview {
368
+ width: 68px;
369
+ height: 68px;
370
+ padding: 0;
371
+ cursor: zoom-in;
372
+ }
373
+
374
+ .attachment-preview img {
375
+ width: 100%;
376
+ height: 100%;
377
+ max-width: 100%;
378
+ border-radius: var(--radius-2);
379
+ object-fit: contain;
380
+ }
381
+
382
+ .attachment-details {
383
+ display: grid;
384
+ min-width: 0;
385
+ gap: var(--space-1);
386
+ }
387
+
388
+ .attachment-details > * {
389
+ overflow: hidden;
390
+ text-overflow: ellipsis;
391
+ white-space: nowrap;
392
+ }
393
+
394
+ .image-actions {
395
+ justify-self: end;
396
+ }
397
+
398
+ .dialog-overlay {
399
+ position: fixed;
400
+ z-index: 80;
401
+ inset: 0;
402
+ background: color-mix(in srgb, var(--gray-12) 72%, transparent);
403
+ animation: overlay-in 140ms ease-out;
404
+ }
405
+
406
+ .dialog-content {
407
+ position: fixed;
408
+ z-index: 81;
409
+ top: 50%;
410
+ left: 50%;
411
+ display: grid;
412
+ width: min(92vw, 30rem);
413
+ gap: var(--space-4);
414
+ padding: var(--space-5);
415
+ border: 1px solid var(--gray-a6);
416
+ border-radius: var(--radius-5);
417
+ color: var(--gray-12);
418
+ background: var(--color-panel-solid);
419
+ box-shadow: var(--pi-shadow);
420
+ transform: translate(-50%, -50%);
421
+ animation: content-in 160ms ease-out;
422
+ }
423
+
424
+ .dialog-content p {
425
+ margin: 0;
426
+ }
427
+
428
+ .preview-overlay {
429
+ background: color-mix(in srgb, var(--gray-12) 88%, transparent);
430
+ }
431
+
432
+ .preview-content {
433
+ position: fixed;
434
+ z-index: 81;
435
+ inset: 3dvh 2vw;
436
+ display: grid;
437
+ grid-template-rows: auto minmax(0, 1fr);
438
+ overflow: hidden;
439
+ border: 1px solid var(--gray-a6);
440
+ border-radius: var(--radius-5);
441
+ background: var(--color-panel-solid);
442
+ box-shadow: var(--pi-shadow);
443
+ animation: preview-in 160ms ease-out;
444
+ }
445
+
446
+ .preview-header {
447
+ gap: var(--space-4);
448
+ padding: var(--space-3) var(--space-4);
449
+ border-bottom: 1px solid var(--gray-a6);
450
+ }
451
+
452
+ .preview-title {
453
+ overflow: hidden;
454
+ text-overflow: ellipsis;
455
+ white-space: nowrap;
456
+ }
457
+
458
+ .preview-image {
459
+ width: 100%;
460
+ height: 100%;
461
+ min-height: 0;
462
+ padding: var(--space-4);
463
+ background: var(--gray-2);
464
+ object-fit: contain;
465
+ }
466
+
467
+ .page-footer {
468
+ padding-block: 0 var(--space-6);
469
+ }
470
+
471
+ .page-footer-content > .rt-Separator {
472
+ margin-bottom: var(--space-3);
473
+ }
474
+
475
+ .page-footer-copy {
476
+ max-width: 72ch;
477
+ margin: 0;
478
+ padding: 0 var(--space-3) var(--space-3);
479
+ }
480
+
481
+ .visually-hidden {
482
+ position: absolute;
483
+ width: 1px;
484
+ height: 1px;
485
+ padding: 0;
486
+ overflow: hidden;
487
+ clip: rect(0, 0, 0, 0);
488
+ white-space: nowrap;
489
+ border: 0;
490
+ }
491
+
492
+ @keyframes overlay-in {
493
+ from {
494
+ opacity: 0;
495
+ }
496
+ }
497
+
498
+ @keyframes content-in {
499
+ from {
500
+ opacity: 0;
501
+ transform: translate(-50%, -48%) scale(0.98);
502
+ }
503
+ }
504
+
505
+ @keyframes preview-in {
506
+ from {
507
+ opacity: 0;
508
+ transform: scale(0.99);
509
+ }
510
+ }
511
+
512
+ @media (max-width: 640px) {
513
+ .session-header-content {
514
+ align-items: flex-start;
515
+ padding-top: var(--space-4);
516
+ }
517
+
518
+ .session-controls {
519
+ align-items: flex-end;
520
+ flex-direction: column;
521
+ }
522
+
523
+ .message.user {
524
+ width: 95%;
525
+ }
526
+
527
+ .tool-trigger {
528
+ grid-template-columns: auto minmax(0, 1fr) auto;
529
+ }
530
+
531
+ .tool-command {
532
+ display: none;
533
+ }
534
+
535
+ .composer {
536
+ bottom: var(--space-1);
537
+ }
538
+
539
+ .composer-bottom {
540
+ align-items: stretch;
541
+ flex-direction: column;
542
+ }
543
+
544
+ .composer-support,
545
+ .send-actions {
546
+ justify-content: space-between;
547
+ }
548
+
549
+ .image-hint {
550
+ display: none;
551
+ }
552
+
553
+ .image-preview-item {
554
+ width: 100%;
555
+ grid-template-columns: 68px minmax(0, 1fr) auto;
556
+ }
557
+
558
+ .preview-content {
559
+ inset: 1dvh 1vw;
560
+ }
561
+ }
562
+
563
+ @media (prefers-reduced-motion: reduce) {
564
+ html {
565
+ scroll-behavior: auto;
566
+ }
567
+
568
+ .radix-themes *,
569
+ .radix-themes *::before,
570
+ .radix-themes *::after {
571
+ scroll-behavior: auto;
572
+ transition-duration: 0.01ms;
573
+ animation-duration: 0.01ms;
574
+ animation-iteration-count: 1;
575
+ }
576
+ }