@yushaw/sanqian-chat 0.1.1 → 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.
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Safe CSS - No Tailwind preflight (for third-party integration)
3
+ *
4
+ * DO NOT EDIT - auto-generated from coreCss.ts
5
+ * Run: npm run build:css
6
+ */
7
+
8
+ import { CSS_VARIABLES, CSS_UTILITIES } from './coreCss';
9
+
10
+ export const SAFE_CSS = CSS_VARIABLES + '\n' + CSS_UTILITIES;
@@ -0,0 +1,683 @@
1
+ /**
2
+ * @yushaw/sanqian-chat CSS Variables
3
+ *
4
+ * Design tokens for chat UI theming.
5
+ * Based on Sanqian Notes design system for consistency.
6
+ *
7
+ * Usage in Tailwind:
8
+ * - bg-chat-bg, text-chat-text, border-chat-border, etc.
9
+ */
10
+
11
+ /* ========================================
12
+ Base Styles - Font & Rendering
13
+ ======================================== */
14
+ *, *::before, *::after {
15
+ box-sizing: border-box;
16
+ }
17
+
18
+ html, body, #root {
19
+ height: 100%;
20
+ margin: 0;
21
+ padding: 0;
22
+ font-family: system-ui, -apple-system, "PingFang SC", "Microsoft YaHei UI", "Microsoft YaHei", sans-serif;
23
+ -webkit-font-smoothing: antialiased;
24
+ -moz-osx-font-smoothing: grayscale;
25
+ text-rendering: optimizeLegibility;
26
+ font-feature-settings: "kern" 1, "liga" 1;
27
+ }
28
+
29
+ /* Text selection */
30
+ ::selection {
31
+ background: var(--chat-selection);
32
+ }
33
+
34
+ /* ========================================
35
+ Code Font
36
+ ======================================== */
37
+ code {
38
+ font-family: 'Iosevka Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
39
+ }
40
+
41
+ :root {
42
+ /* ========================================
43
+ Background Colors
44
+ ======================================== */
45
+ --chat-bg: #f5f5f7;
46
+ --chat-surface: #fbfbfd;
47
+ --chat-card: #ffffff;
48
+
49
+ /* ========================================
50
+ Border Colors
51
+ ======================================== */
52
+ --chat-border: #e5e5ea;
53
+ --chat-divider: #e5e5ea;
54
+ --chat-border-focus: rgba(0, 0, 0, 0.12);
55
+ --chat-hover: rgba(0, 0, 0, 0.05);
56
+
57
+ /* ========================================
58
+ Text Colors
59
+ ======================================== */
60
+ --chat-text: #1d1d1f;
61
+ --chat-muted: #6e6e73;
62
+
63
+ /* ========================================
64
+ Accent Colors
65
+ ======================================== */
66
+ --chat-accent: #e85d75;
67
+ --chat-accent-soft: #fad0da;
68
+ --chat-selection: #fbe7ec;
69
+
70
+ /* ========================================
71
+ Status Colors
72
+ ======================================== */
73
+ --chat-success: #34c759;
74
+ --chat-error: #ff3b30;
75
+ --chat-warning: #ff9500;
76
+ --chat-info: #007aff;
77
+
78
+ /* ========================================
79
+ Shadows
80
+ ======================================== */
81
+ --chat-shadow-soft: 0 4px 16px rgba(15, 23, 42, 0.05), 0 1px 4px rgba(15, 23, 42, 0.03);
82
+ --chat-shadow-elevated: 0 10px 26px rgba(15, 23, 42, 0.08), 0 4px 12px rgba(15, 23, 42, 0.06);
83
+
84
+ /* ========================================
85
+ Scrollbar
86
+ ======================================== */
87
+ --chat-scrollbar: rgba(0, 0, 0, 0.18);
88
+
89
+ /* ========================================
90
+ Message Bubbles
91
+ ======================================== */
92
+ --chat-bubble-user: #3b82f6;
93
+ --chat-bubble-user-text: #ffffff;
94
+ --chat-bubble-assistant: var(--chat-surface);
95
+ --chat-bubble-assistant-text: var(--chat-text);
96
+
97
+ /* ========================================
98
+ Code Blocks
99
+ ======================================== */
100
+ --chat-code-bg: rgba(0, 0, 0, 0.03);
101
+ --chat-code-border: var(--chat-border);
102
+
103
+ /* ========================================
104
+ Sanqian Aliases (for shared prose styles)
105
+ ======================================== */
106
+ --color-bg: var(--chat-bg);
107
+ --color-card: var(--chat-card);
108
+ --color-card-solid: var(--chat-card);
109
+ --color-surface: var(--chat-surface);
110
+ --color-border: var(--chat-border);
111
+ --color-divider: var(--chat-divider);
112
+ --color-selection: var(--chat-selection);
113
+ --color-accent: var(--chat-accent);
114
+ --color-accent-soft: var(--chat-accent-soft);
115
+ --color-text: var(--chat-text);
116
+ --color-muted: var(--chat-muted);
117
+ --shadow-soft: var(--chat-shadow-soft);
118
+ --shadow-elevated: var(--chat-shadow-elevated);
119
+ }
120
+
121
+ /* ========================================
122
+ Dark Mode
123
+ ======================================== */
124
+ .dark,
125
+ :root.dark {
126
+ --chat-bg: #1a1a1a;
127
+ --chat-surface: #2d2d2d;
128
+ --chat-card: #242424;
129
+
130
+ --chat-border: #333333;
131
+ --chat-divider: #333333;
132
+ --chat-border-focus: rgba(255, 255, 255, 0.12);
133
+ --chat-hover: rgba(255, 255, 255, 0.08);
134
+
135
+ --chat-text: #ffffff;
136
+ --chat-muted: #aeaeb2;
137
+
138
+ --chat-accent: #e85d75;
139
+ --chat-accent-soft: #4a2a32;
140
+ --chat-selection: #3a2028;
141
+
142
+ --chat-shadow-soft: 0 4px 16px rgba(0, 0, 0, 0.2), 0 1px 4px rgba(0, 0, 0, 0.1);
143
+ --chat-shadow-elevated: 0 10px 26px rgba(0, 0, 0, 0.3), 0 4px 12px rgba(0, 0, 0, 0.2);
144
+
145
+ --chat-scrollbar: rgba(255, 255, 255, 0.18);
146
+
147
+ --chat-bubble-user: #3b82f6;
148
+ --chat-bubble-user-text: #ffffff;
149
+ --chat-bubble-assistant: var(--chat-surface);
150
+ --chat-bubble-assistant-text: var(--chat-text);
151
+
152
+ --chat-code-bg: rgba(255, 255, 255, 0.05);
153
+ --chat-code-border: var(--chat-border);
154
+ }
155
+
156
+ /* ========================================
157
+ Scrollbar Styling
158
+ ======================================== */
159
+ .chat-scrollbar {
160
+ scrollbar-width: thin;
161
+ scrollbar-color: transparent transparent;
162
+ }
163
+
164
+ .chat-scrollbar:hover {
165
+ scrollbar-color: var(--chat-scrollbar) transparent;
166
+ }
167
+
168
+ .chat-scrollbar::-webkit-scrollbar {
169
+ width: 6px;
170
+ height: 6px;
171
+ }
172
+
173
+ .chat-scrollbar::-webkit-scrollbar-track {
174
+ background: transparent;
175
+ }
176
+
177
+ .chat-scrollbar::-webkit-scrollbar-thumb {
178
+ background: transparent;
179
+ border-radius: 3px;
180
+ }
181
+
182
+ .chat-scrollbar:hover::-webkit-scrollbar-thumb {
183
+ background: var(--chat-scrollbar);
184
+ }
185
+
186
+ /* ========================================
187
+ Animations
188
+ ======================================== */
189
+ @keyframes chat-cursor-blink {
190
+ 0%, 49% { opacity: 1; }
191
+ 50%, 100% { opacity: 0; }
192
+ }
193
+
194
+ @keyframes chat-cursor-breathing {
195
+ 0%, 100% { opacity: 1; }
196
+ 50% { opacity: 0.4; }
197
+ }
198
+
199
+ .chat-cursor-blink {
200
+ animation: chat-cursor-blink 1s step-end infinite;
201
+ }
202
+
203
+ .chat-cursor-breathing {
204
+ animation: chat-cursor-breathing 1s ease-in-out infinite;
205
+ }
206
+
207
+ @keyframes chat-fade-in {
208
+ from { opacity: 0; transform: translateY(4px); }
209
+ to { opacity: 1; transform: translateY(0); }
210
+ }
211
+
212
+ .chat-fade-in {
213
+ animation: chat-fade-in 0.2s ease-out;
214
+ }
215
+
216
+ /* ========================================
217
+ Prose Overrides
218
+ ======================================== */
219
+ .prose {
220
+ --tw-prose-body: currentColor !important;
221
+ --tw-prose-headings: currentColor !important;
222
+ --tw-prose-lead: currentColor !important;
223
+ --tw-prose-links: var(--chat-accent) !important;
224
+ --tw-prose-bold: currentColor !important;
225
+ --tw-prose-counters: var(--chat-muted) !important;
226
+ --tw-prose-bullets: var(--chat-muted) !important;
227
+ --tw-prose-hr: var(--chat-divider) !important;
228
+ --tw-prose-quotes: var(--chat-muted) !important;
229
+ --tw-prose-quote-borders: var(--chat-accent) !important;
230
+ --tw-prose-captions: var(--chat-muted) !important;
231
+ --tw-prose-code: currentColor !important;
232
+ --tw-prose-pre-code: currentColor !important;
233
+ --tw-prose-pre-bg: var(--chat-code-bg) !important;
234
+ --tw-prose-th-borders: var(--chat-divider) !important;
235
+ --tw-prose-td-borders: var(--chat-divider) !important;
236
+ }
237
+
238
+ /* Override streamdown's default space-y-4 for compact layout */
239
+ .prose .space-y-4 > * + * {
240
+ margin-top: 0.25rem !important;
241
+ }
242
+
243
+ .prose .space-y-4 {
244
+ --tw-space-y-reverse: 0 !important;
245
+ }
246
+
247
+ /* Force list bullets to display */
248
+ .prose ul {
249
+ list-style-type: disc !important;
250
+ padding-left: 1.25rem !important;
251
+ }
252
+
253
+ .prose ol {
254
+ list-style-type: decimal !important;
255
+ padding-left: 1.25rem !important;
256
+ }
257
+
258
+ .prose li {
259
+ display: list-item !important;
260
+ margin-top: 0 !important;
261
+ margin-bottom: 0 !important;
262
+ padding-top: 0 !important;
263
+ padding-bottom: 0 !important;
264
+ }
265
+
266
+ /* ========================================
267
+ Markdown Layout Parity (Sanqian)
268
+ ======================================== */
269
+ .prose :is(p, li, h1, h2, h3, h4, h5, h6):last-child {
270
+ color: inherit;
271
+ }
272
+
273
+ .prose p:has(img:nth-child(2)) {
274
+ display: flex;
275
+ flex-wrap: wrap;
276
+ gap: 0.5rem;
277
+ }
278
+
279
+ .prose p:has(img:nth-child(2)) img {
280
+ max-width: calc(50% - 0.25rem);
281
+ height: auto;
282
+ object-fit: cover;
283
+ cursor: pointer;
284
+ }
285
+
286
+ .prose p:has(img:nth-child(3)) img {
287
+ max-width: calc(33.333% - 0.333rem);
288
+ }
289
+
290
+ /* ========================================
291
+ Streamdown Code Block (Sanqian)
292
+ ======================================== */
293
+ [data-streamdown="code-block"] {
294
+ position: relative;
295
+ border: none !important;
296
+ border-radius: 6px !important;
297
+ overflow: visible !important;
298
+ background: transparent !important;
299
+ margin: 6px 0 !important;
300
+ }
301
+
302
+ [data-streamdown="code-block-header"] {
303
+ position: absolute !important;
304
+ top: 14px !important;
305
+ right: 8px !important;
306
+ z-index: 10;
307
+ padding: 0 !important;
308
+ background: transparent !important;
309
+ border: none !important;
310
+ opacity: 0;
311
+ transition: opacity 150ms ease;
312
+ }
313
+
314
+ [data-streamdown="code-block"]:hover [data-streamdown="code-block-header"] {
315
+ opacity: 1;
316
+ }
317
+
318
+ [data-streamdown="code-block-header"] > span:first-child {
319
+ font-size: 11px;
320
+ color: var(--color-muted);
321
+ text-transform: lowercase;
322
+ margin-right: 8px;
323
+ }
324
+
325
+ [data-streamdown="code-block-header"] > div {
326
+ display: flex;
327
+ gap: 4px;
328
+ }
329
+
330
+ [data-streamdown="code-block-header"] button {
331
+ padding: 4px !important;
332
+ border-radius: 4px !important;
333
+ background: var(--color-surface) !important;
334
+ opacity: 0.7;
335
+ }
336
+
337
+ [data-streamdown="code-block-header"] button:hover {
338
+ opacity: 1;
339
+ background: var(--color-border) !important;
340
+ }
341
+
342
+ [data-streamdown="code-block-header"] svg {
343
+ color: var(--color-muted) !important;
344
+ width: 12px !important;
345
+ height: 12px !important;
346
+ }
347
+
348
+ [data-streamdown="code-block-header"] button:hover svg {
349
+ color: var(--color-text) !important;
350
+ }
351
+
352
+ [data-streamdown="code-block-body"] {
353
+ padding: 8px 12px !important;
354
+ background: rgba(0, 0, 0, 0.02) !important;
355
+ border-radius: 6px !important;
356
+ font-size: 13px !important;
357
+ line-height: 1.5 !important;
358
+ white-space: pre !important;
359
+ overflow-x: auto !important;
360
+ }
361
+
362
+ [data-streamdown="code-block-body"] code {
363
+ white-space: pre !important;
364
+ display: block !important;
365
+ }
366
+
367
+ .dark [data-streamdown="code-block-body"],
368
+ :root.dark [data-streamdown="code-block-body"] {
369
+ background: rgba(255, 255, 255, 0.03) !important;
370
+ }
371
+
372
+ [data-streamdown="code-block-body"] code span::before {
373
+ display: none !important;
374
+ }
375
+
376
+ [data-streamdown="code-block-body"],
377
+ [data-streamdown="code-block-body"] code {
378
+ color: var(--color-text) !important;
379
+ opacity: 0.85;
380
+ }
381
+
382
+ [data-streamdown="code-block-body"] code span[style] {
383
+ background-color: transparent !important;
384
+ }
385
+
386
+ .dark [data-streamdown="code-block-body"] code span[style],
387
+ :root.dark [data-streamdown="code-block-body"] code span[style] {
388
+ color: var(--shiki-dark) !important;
389
+ }
390
+
391
+ /* ========================================
392
+ Streamdown Table (Sanqian)
393
+ ======================================== */
394
+ [data-streamdown="table-wrapper"].my-4 {
395
+ position: relative !important;
396
+ margin: 0 !important;
397
+ gap: 0 !important;
398
+ }
399
+
400
+ [data-streamdown="table-wrapper"].my-4 > .flex.items-center.justify-end.gap-1 {
401
+ position: absolute !important;
402
+ top: -8px !important;
403
+ right: 4px !important;
404
+ z-index: 10;
405
+ opacity: 0 !important;
406
+ transition: opacity 150ms ease;
407
+ margin: 0 !important;
408
+ }
409
+
410
+ [data-streamdown="table-wrapper"].my-4:hover > .flex.items-center.justify-end.gap-1 {
411
+ opacity: 1 !important;
412
+ }
413
+
414
+ [data-streamdown="table-wrapper"] .absolute.top-full {
415
+ border: 1px solid var(--color-divider) !important;
416
+ background: var(--color-card) !important;
417
+ backdrop-filter: blur(8px);
418
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
419
+ }
420
+
421
+ [data-streamdown="table-wrapper"] .absolute.top-full button {
422
+ color: var(--color-text) !important;
423
+ opacity: 0.8;
424
+ }
425
+
426
+ [data-streamdown="table-wrapper"] .absolute.top-full button:hover {
427
+ background: var(--color-surface) !important;
428
+ opacity: 1;
429
+ }
430
+
431
+ /* Code block styling - reset inline code styles for pre > code */
432
+ .prose pre {
433
+ display: block !important;
434
+ overflow-x: auto !important;
435
+ white-space: pre !important;
436
+ }
437
+
438
+ .prose pre code {
439
+ padding: 0 !important;
440
+ background: transparent !important;
441
+ border-radius: 0 !important;
442
+ font-size: 0.75rem !important;
443
+ white-space: pre !important;
444
+ display: block !important;
445
+ line-height: 1.5 !important;
446
+ }
447
+
448
+ /* ========================================
449
+ Chat Input Styling (Sanqian style)
450
+ Layout: [Input box (flex-1)] [Send button]
451
+ ======================================== */
452
+
453
+ /* Form container - flex row */
454
+ .chat-input-form {
455
+ display: flex;
456
+ align-items: center;
457
+ gap: 0.5rem;
458
+ }
459
+
460
+ /* Input box container - takes remaining space */
461
+ .chat-input-box {
462
+ flex: 1;
463
+ position: relative;
464
+ display: grid;
465
+ grid-template-columns: 1fr;
466
+ }
467
+
468
+ /* Hidden replica for auto-height */
469
+ .chat-input-replica {
470
+ grid-area: 1 / 1 / 2 / 2;
471
+ visibility: hidden;
472
+ white-space: pre-wrap;
473
+ word-break: break-word;
474
+ font-size: 0.875rem;
475
+ line-height: 1.5;
476
+ padding-top: 0.625em;
477
+ padding-bottom: 0.625em;
478
+ min-height: 2.75em;
479
+ max-height: 200px;
480
+ pointer-events: none;
481
+ border: 1px solid transparent;
482
+ border-radius: 1rem;
483
+ }
484
+
485
+ /* Actual textarea - matches Sanqian's .smart-textarea-editor .ProseMirror */
486
+ .chat-input-textarea {
487
+ grid-area: 1 / 1 / 2 / 2;
488
+ width: 100%;
489
+ min-height: 2.75em;
490
+ max-height: 200px;
491
+ padding-top: 0.625em;
492
+ padding-bottom: 0.625em;
493
+ border-radius: 1rem;
494
+ border: 1px solid var(--chat-border);
495
+ background: var(--chat-card);
496
+ font-size: 0.875rem;
497
+ line-height: 1.5;
498
+ box-shadow: var(--chat-shadow-elevated);
499
+ color: var(--chat-text);
500
+ overflow-y: auto;
501
+ outline: none;
502
+ resize: none;
503
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
504
+ }
505
+
506
+ .chat-input-textarea::placeholder {
507
+ color: var(--chat-muted);
508
+ }
509
+
510
+ .chat-input-textarea:focus {
511
+ border-color: var(--chat-border-focus);
512
+ }
513
+
514
+ .chat-input-textarea:disabled {
515
+ opacity: 0.5;
516
+ cursor: not-allowed;
517
+ }
518
+
519
+ /* Left slot - inside input box (matches Sanqian's left-1 bottom-[0.25em]) */
520
+ .chat-input-left-slot {
521
+ position: absolute;
522
+ left: 0.25rem;
523
+ bottom: 0.25em;
524
+ z-index: 10;
525
+ }
526
+
527
+ /* Right slot - inside input box (matches Sanqian's right-1 bottom-[0.25em]) */
528
+ .chat-input-right-slot {
529
+ position: absolute;
530
+ right: 0.25rem;
531
+ bottom: 0.25em;
532
+ z-index: 10;
533
+ }
534
+
535
+ /* Send button - OUTSIDE input box */
536
+ .chat-input-send-btn {
537
+ flex-shrink: 0;
538
+ width: 2.25rem;
539
+ height: 2.25rem;
540
+ border-radius: 50%;
541
+ border: none;
542
+ background: var(--chat-accent);
543
+ color: white;
544
+ display: flex;
545
+ align-items: center;
546
+ justify-content: center;
547
+ cursor: pointer;
548
+ transition: all 0.15s ease;
549
+ box-shadow: var(--chat-shadow-soft);
550
+ }
551
+
552
+ .chat-input-send-btn:hover:not(:disabled) {
553
+ transform: scale(1.05);
554
+ box-shadow: var(--chat-shadow-elevated);
555
+ }
556
+
557
+ .chat-input-send-btn:active:not(:disabled) {
558
+ transform: scale(0.95);
559
+ }
560
+
561
+ .chat-input-send-btn:disabled {
562
+ opacity: 0.4;
563
+ cursor: not-allowed;
564
+ transform: none;
565
+ }
566
+
567
+ .chat-input-send-btn svg {
568
+ width: 1rem;
569
+ height: 1rem;
570
+ }
571
+
572
+ /* Header visuals */
573
+ .chat-header-icon-btn {
574
+ display: inline-flex;
575
+ align-items: center;
576
+ justify-content: center;
577
+ width: 1.75rem;
578
+ height: 1.75rem;
579
+ border-radius: 0.5rem;
580
+ color: var(--chat-muted);
581
+ background: transparent;
582
+ transition: color 0.15s ease, background-color 0.15s ease;
583
+ }
584
+
585
+ .chat-header-icon-btn:hover {
586
+ color: var(--chat-text);
587
+ background: var(--chat-hover);
588
+ }
589
+
590
+ .chat-header-icon {
591
+ width: 1rem;
592
+ height: 1rem;
593
+ }
594
+
595
+ .chat-header-logo {
596
+ width: 1rem;
597
+ height: 1rem;
598
+ border-radius: 0.25rem;
599
+ object-fit: contain;
600
+ display: block;
601
+ }
602
+
603
+ .dark .chat-header-logo,
604
+ :root.dark .chat-header-logo {
605
+ filter: invert(1);
606
+ }
607
+
608
+ .chat-logo-wrapper {
609
+ display: inline-flex;
610
+ align-items: center;
611
+ justify-content: center;
612
+ }
613
+
614
+ .chat-logo-wrapper > * {
615
+ width: 100%;
616
+ height: 100%;
617
+ display: block;
618
+ }
619
+
620
+ .chat-logo-default {
621
+ opacity: 0.9;
622
+ }
623
+
624
+ .dark .chat-logo-default,
625
+ :root.dark .chat-logo-default {
626
+ filter: invert(1);
627
+ }
628
+
629
+ .chat-empty-logo {
630
+ width: 5rem;
631
+ height: 5rem;
632
+ object-fit: contain;
633
+ display: block;
634
+ }
635
+
636
+ .chat-divider-border {
637
+ border-color: var(--chat-divider);
638
+ }
639
+
640
+ /* Send wrapper + stop tooltip (aligned with Sanqian stop affordance) */
641
+
642
+ .chat-input-send-wrapper {
643
+ position: relative;
644
+ display: flex;
645
+ align-items: center;
646
+ }
647
+
648
+ .chat-input-stop-tooltip {
649
+ position: absolute;
650
+ bottom: 100%;
651
+ left: 50%;
652
+ transform: translateX(-50%);
653
+ margin-bottom: 0.5rem;
654
+ padding: 0.25rem 0.5rem;
655
+ border-radius: 0.5rem;
656
+ border: 1px solid var(--chat-divider);
657
+ background: var(--chat-card);
658
+ color: var(--chat-text);
659
+ font-size: 0.75rem;
660
+ line-height: 1.2;
661
+ text-align: center;
662
+ white-space: nowrap;
663
+ opacity: 0;
664
+ visibility: hidden;
665
+ pointer-events: none;
666
+ box-shadow: var(--chat-shadow-soft);
667
+ transition: opacity 0.15s ease, visibility 0.15s ease;
668
+ }
669
+
670
+ .chat-input-stop-hotkey {
671
+ color: var(--chat-muted);
672
+ font-size: 0.7rem;
673
+ line-height: 1.1;
674
+ }
675
+
676
+ .chat-input-send-wrapper:hover .chat-input-stop-tooltip {
677
+ opacity: 1;
678
+ visibility: visible;
679
+ }
680
+
681
+ @tailwind base;
682
+ @tailwind components;
683
+ @tailwind utilities;