Spaces:
Sleeping
Sleeping
redo ui
Browse files- .gitattributes +1 -0
- package-lock.json +294 -0
- package.json +1 -0
- public/teich.png +3 -0
- src/app/globals.css +253 -151
- src/components/chat.tsx +174 -147
- src/components/markdown.tsx +28 -29
- teich.png +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
package-lock.json
CHANGED
|
@@ -19,6 +19,7 @@
|
|
| 19 |
"react": "18.3.1",
|
| 20 |
"react-dom": "18.3.1",
|
| 21 |
"react-markdown": "9.0.3",
|
|
|
|
| 22 |
"tailwind-merge": "2.6.0"
|
| 23 |
},
|
| 24 |
"devDependencies": {
|
|
@@ -1756,6 +1757,18 @@
|
|
| 1756 |
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
| 1757 |
"license": "MIT"
|
| 1758 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1759 |
"node_modules/estree-util-is-identifier-name": {
|
| 1760 |
"version": "3.0.0",
|
| 1761 |
"resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
|
|
@@ -2460,6 +2473,16 @@
|
|
| 2460 |
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 2461 |
}
|
| 2462 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2463 |
"node_modules/math-intrinsics": {
|
| 2464 |
"version": "1.1.0",
|
| 2465 |
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
|
@@ -2469,6 +2492,22 @@
|
|
| 2469 |
"node": ">= 0.4"
|
| 2470 |
}
|
| 2471 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2472 |
"node_modules/mdast-util-from-markdown": {
|
| 2473 |
"version": "2.0.2",
|
| 2474 |
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
|
|
@@ -2493,6 +2532,107 @@
|
|
| 2493 |
"url": "https://opencollective.com/unified"
|
| 2494 |
}
|
| 2495 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2496 |
"node_modules/mdast-util-mdx-expression": {
|
| 2497 |
"version": "2.0.1",
|
| 2498 |
"resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
|
|
@@ -2722,6 +2862,127 @@
|
|
| 2722 |
"micromark-util-types": "^2.0.0"
|
| 2723 |
}
|
| 2724 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2725 |
"node_modules/micromark-factory-destination": {
|
| 2726 |
"version": "2.0.1",
|
| 2727 |
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
|
|
@@ -3738,6 +3999,24 @@
|
|
| 3738 |
"node": ">=8.10.0"
|
| 3739 |
}
|
| 3740 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3741 |
"node_modules/remark-parse": {
|
| 3742 |
"version": "11.0.0",
|
| 3743 |
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
|
@@ -3771,6 +4050,21 @@
|
|
| 3771 |
"url": "https://opencollective.com/unified"
|
| 3772 |
}
|
| 3773 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3774 |
"node_modules/require-from-string": {
|
| 3775 |
"version": "2.0.2",
|
| 3776 |
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
|
|
|
| 19 |
"react": "18.3.1",
|
| 20 |
"react-dom": "18.3.1",
|
| 21 |
"react-markdown": "9.0.3",
|
| 22 |
+
"remark-gfm": "^4.0.1",
|
| 23 |
"tailwind-merge": "2.6.0"
|
| 24 |
},
|
| 25 |
"devDependencies": {
|
|
|
|
| 1757 |
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
| 1758 |
"license": "MIT"
|
| 1759 |
},
|
| 1760 |
+
"node_modules/escape-string-regexp": {
|
| 1761 |
+
"version": "5.0.0",
|
| 1762 |
+
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
| 1763 |
+
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
|
| 1764 |
+
"license": "MIT",
|
| 1765 |
+
"engines": {
|
| 1766 |
+
"node": ">=12"
|
| 1767 |
+
},
|
| 1768 |
+
"funding": {
|
| 1769 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1770 |
+
}
|
| 1771 |
+
},
|
| 1772 |
"node_modules/estree-util-is-identifier-name": {
|
| 1773 |
"version": "3.0.0",
|
| 1774 |
"resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
|
|
|
|
| 2473 |
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 2474 |
}
|
| 2475 |
},
|
| 2476 |
+
"node_modules/markdown-table": {
|
| 2477 |
+
"version": "3.0.4",
|
| 2478 |
+
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
|
| 2479 |
+
"integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
|
| 2480 |
+
"license": "MIT",
|
| 2481 |
+
"funding": {
|
| 2482 |
+
"type": "github",
|
| 2483 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 2484 |
+
}
|
| 2485 |
+
},
|
| 2486 |
"node_modules/math-intrinsics": {
|
| 2487 |
"version": "1.1.0",
|
| 2488 |
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
|
|
|
| 2492 |
"node": ">= 0.4"
|
| 2493 |
}
|
| 2494 |
},
|
| 2495 |
+
"node_modules/mdast-util-find-and-replace": {
|
| 2496 |
+
"version": "3.0.2",
|
| 2497 |
+
"resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
|
| 2498 |
+
"integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
|
| 2499 |
+
"license": "MIT",
|
| 2500 |
+
"dependencies": {
|
| 2501 |
+
"@types/mdast": "^4.0.0",
|
| 2502 |
+
"escape-string-regexp": "^5.0.0",
|
| 2503 |
+
"unist-util-is": "^6.0.0",
|
| 2504 |
+
"unist-util-visit-parents": "^6.0.0"
|
| 2505 |
+
},
|
| 2506 |
+
"funding": {
|
| 2507 |
+
"type": "opencollective",
|
| 2508 |
+
"url": "https://opencollective.com/unified"
|
| 2509 |
+
}
|
| 2510 |
+
},
|
| 2511 |
"node_modules/mdast-util-from-markdown": {
|
| 2512 |
"version": "2.0.2",
|
| 2513 |
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
|
|
|
|
| 2532 |
"url": "https://opencollective.com/unified"
|
| 2533 |
}
|
| 2534 |
},
|
| 2535 |
+
"node_modules/mdast-util-gfm": {
|
| 2536 |
+
"version": "3.1.0",
|
| 2537 |
+
"resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
|
| 2538 |
+
"integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
|
| 2539 |
+
"license": "MIT",
|
| 2540 |
+
"dependencies": {
|
| 2541 |
+
"mdast-util-from-markdown": "^2.0.0",
|
| 2542 |
+
"mdast-util-gfm-autolink-literal": "^2.0.0",
|
| 2543 |
+
"mdast-util-gfm-footnote": "^2.0.0",
|
| 2544 |
+
"mdast-util-gfm-strikethrough": "^2.0.0",
|
| 2545 |
+
"mdast-util-gfm-table": "^2.0.0",
|
| 2546 |
+
"mdast-util-gfm-task-list-item": "^2.0.0",
|
| 2547 |
+
"mdast-util-to-markdown": "^2.0.0"
|
| 2548 |
+
},
|
| 2549 |
+
"funding": {
|
| 2550 |
+
"type": "opencollective",
|
| 2551 |
+
"url": "https://opencollective.com/unified"
|
| 2552 |
+
}
|
| 2553 |
+
},
|
| 2554 |
+
"node_modules/mdast-util-gfm-autolink-literal": {
|
| 2555 |
+
"version": "2.0.1",
|
| 2556 |
+
"resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
|
| 2557 |
+
"integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
|
| 2558 |
+
"license": "MIT",
|
| 2559 |
+
"dependencies": {
|
| 2560 |
+
"@types/mdast": "^4.0.0",
|
| 2561 |
+
"ccount": "^2.0.0",
|
| 2562 |
+
"devlop": "^1.0.0",
|
| 2563 |
+
"mdast-util-find-and-replace": "^3.0.0",
|
| 2564 |
+
"micromark-util-character": "^2.0.0"
|
| 2565 |
+
},
|
| 2566 |
+
"funding": {
|
| 2567 |
+
"type": "opencollective",
|
| 2568 |
+
"url": "https://opencollective.com/unified"
|
| 2569 |
+
}
|
| 2570 |
+
},
|
| 2571 |
+
"node_modules/mdast-util-gfm-footnote": {
|
| 2572 |
+
"version": "2.1.0",
|
| 2573 |
+
"resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
|
| 2574 |
+
"integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
|
| 2575 |
+
"license": "MIT",
|
| 2576 |
+
"dependencies": {
|
| 2577 |
+
"@types/mdast": "^4.0.0",
|
| 2578 |
+
"devlop": "^1.1.0",
|
| 2579 |
+
"mdast-util-from-markdown": "^2.0.0",
|
| 2580 |
+
"mdast-util-to-markdown": "^2.0.0",
|
| 2581 |
+
"micromark-util-normalize-identifier": "^2.0.0"
|
| 2582 |
+
},
|
| 2583 |
+
"funding": {
|
| 2584 |
+
"type": "opencollective",
|
| 2585 |
+
"url": "https://opencollective.com/unified"
|
| 2586 |
+
}
|
| 2587 |
+
},
|
| 2588 |
+
"node_modules/mdast-util-gfm-strikethrough": {
|
| 2589 |
+
"version": "2.0.0",
|
| 2590 |
+
"resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
|
| 2591 |
+
"integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
|
| 2592 |
+
"license": "MIT",
|
| 2593 |
+
"dependencies": {
|
| 2594 |
+
"@types/mdast": "^4.0.0",
|
| 2595 |
+
"mdast-util-from-markdown": "^2.0.0",
|
| 2596 |
+
"mdast-util-to-markdown": "^2.0.0"
|
| 2597 |
+
},
|
| 2598 |
+
"funding": {
|
| 2599 |
+
"type": "opencollective",
|
| 2600 |
+
"url": "https://opencollective.com/unified"
|
| 2601 |
+
}
|
| 2602 |
+
},
|
| 2603 |
+
"node_modules/mdast-util-gfm-table": {
|
| 2604 |
+
"version": "2.0.0",
|
| 2605 |
+
"resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
|
| 2606 |
+
"integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
|
| 2607 |
+
"license": "MIT",
|
| 2608 |
+
"dependencies": {
|
| 2609 |
+
"@types/mdast": "^4.0.0",
|
| 2610 |
+
"devlop": "^1.0.0",
|
| 2611 |
+
"markdown-table": "^3.0.0",
|
| 2612 |
+
"mdast-util-from-markdown": "^2.0.0",
|
| 2613 |
+
"mdast-util-to-markdown": "^2.0.0"
|
| 2614 |
+
},
|
| 2615 |
+
"funding": {
|
| 2616 |
+
"type": "opencollective",
|
| 2617 |
+
"url": "https://opencollective.com/unified"
|
| 2618 |
+
}
|
| 2619 |
+
},
|
| 2620 |
+
"node_modules/mdast-util-gfm-task-list-item": {
|
| 2621 |
+
"version": "2.0.0",
|
| 2622 |
+
"resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
|
| 2623 |
+
"integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
|
| 2624 |
+
"license": "MIT",
|
| 2625 |
+
"dependencies": {
|
| 2626 |
+
"@types/mdast": "^4.0.0",
|
| 2627 |
+
"devlop": "^1.0.0",
|
| 2628 |
+
"mdast-util-from-markdown": "^2.0.0",
|
| 2629 |
+
"mdast-util-to-markdown": "^2.0.0"
|
| 2630 |
+
},
|
| 2631 |
+
"funding": {
|
| 2632 |
+
"type": "opencollective",
|
| 2633 |
+
"url": "https://opencollective.com/unified"
|
| 2634 |
+
}
|
| 2635 |
+
},
|
| 2636 |
"node_modules/mdast-util-mdx-expression": {
|
| 2637 |
"version": "2.0.1",
|
| 2638 |
"resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
|
|
|
|
| 2862 |
"micromark-util-types": "^2.0.0"
|
| 2863 |
}
|
| 2864 |
},
|
| 2865 |
+
"node_modules/micromark-extension-gfm": {
|
| 2866 |
+
"version": "3.0.0",
|
| 2867 |
+
"resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
|
| 2868 |
+
"integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
|
| 2869 |
+
"license": "MIT",
|
| 2870 |
+
"dependencies": {
|
| 2871 |
+
"micromark-extension-gfm-autolink-literal": "^2.0.0",
|
| 2872 |
+
"micromark-extension-gfm-footnote": "^2.0.0",
|
| 2873 |
+
"micromark-extension-gfm-strikethrough": "^2.0.0",
|
| 2874 |
+
"micromark-extension-gfm-table": "^2.0.0",
|
| 2875 |
+
"micromark-extension-gfm-tagfilter": "^2.0.0",
|
| 2876 |
+
"micromark-extension-gfm-task-list-item": "^2.0.0",
|
| 2877 |
+
"micromark-util-combine-extensions": "^2.0.0",
|
| 2878 |
+
"micromark-util-types": "^2.0.0"
|
| 2879 |
+
},
|
| 2880 |
+
"funding": {
|
| 2881 |
+
"type": "opencollective",
|
| 2882 |
+
"url": "https://opencollective.com/unified"
|
| 2883 |
+
}
|
| 2884 |
+
},
|
| 2885 |
+
"node_modules/micromark-extension-gfm-autolink-literal": {
|
| 2886 |
+
"version": "2.1.0",
|
| 2887 |
+
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
|
| 2888 |
+
"integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
|
| 2889 |
+
"license": "MIT",
|
| 2890 |
+
"dependencies": {
|
| 2891 |
+
"micromark-util-character": "^2.0.0",
|
| 2892 |
+
"micromark-util-sanitize-uri": "^2.0.0",
|
| 2893 |
+
"micromark-util-symbol": "^2.0.0",
|
| 2894 |
+
"micromark-util-types": "^2.0.0"
|
| 2895 |
+
},
|
| 2896 |
+
"funding": {
|
| 2897 |
+
"type": "opencollective",
|
| 2898 |
+
"url": "https://opencollective.com/unified"
|
| 2899 |
+
}
|
| 2900 |
+
},
|
| 2901 |
+
"node_modules/micromark-extension-gfm-footnote": {
|
| 2902 |
+
"version": "2.1.0",
|
| 2903 |
+
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
|
| 2904 |
+
"integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
|
| 2905 |
+
"license": "MIT",
|
| 2906 |
+
"dependencies": {
|
| 2907 |
+
"devlop": "^1.0.0",
|
| 2908 |
+
"micromark-core-commonmark": "^2.0.0",
|
| 2909 |
+
"micromark-factory-space": "^2.0.0",
|
| 2910 |
+
"micromark-util-character": "^2.0.0",
|
| 2911 |
+
"micromark-util-normalize-identifier": "^2.0.0",
|
| 2912 |
+
"micromark-util-sanitize-uri": "^2.0.0",
|
| 2913 |
+
"micromark-util-symbol": "^2.0.0",
|
| 2914 |
+
"micromark-util-types": "^2.0.0"
|
| 2915 |
+
},
|
| 2916 |
+
"funding": {
|
| 2917 |
+
"type": "opencollective",
|
| 2918 |
+
"url": "https://opencollective.com/unified"
|
| 2919 |
+
}
|
| 2920 |
+
},
|
| 2921 |
+
"node_modules/micromark-extension-gfm-strikethrough": {
|
| 2922 |
+
"version": "2.1.0",
|
| 2923 |
+
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
|
| 2924 |
+
"integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
|
| 2925 |
+
"license": "MIT",
|
| 2926 |
+
"dependencies": {
|
| 2927 |
+
"devlop": "^1.0.0",
|
| 2928 |
+
"micromark-util-chunked": "^2.0.0",
|
| 2929 |
+
"micromark-util-classify-character": "^2.0.0",
|
| 2930 |
+
"micromark-util-resolve-all": "^2.0.0",
|
| 2931 |
+
"micromark-util-symbol": "^2.0.0",
|
| 2932 |
+
"micromark-util-types": "^2.0.0"
|
| 2933 |
+
},
|
| 2934 |
+
"funding": {
|
| 2935 |
+
"type": "opencollective",
|
| 2936 |
+
"url": "https://opencollective.com/unified"
|
| 2937 |
+
}
|
| 2938 |
+
},
|
| 2939 |
+
"node_modules/micromark-extension-gfm-table": {
|
| 2940 |
+
"version": "2.1.1",
|
| 2941 |
+
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
|
| 2942 |
+
"integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
|
| 2943 |
+
"license": "MIT",
|
| 2944 |
+
"dependencies": {
|
| 2945 |
+
"devlop": "^1.0.0",
|
| 2946 |
+
"micromark-factory-space": "^2.0.0",
|
| 2947 |
+
"micromark-util-character": "^2.0.0",
|
| 2948 |
+
"micromark-util-symbol": "^2.0.0",
|
| 2949 |
+
"micromark-util-types": "^2.0.0"
|
| 2950 |
+
},
|
| 2951 |
+
"funding": {
|
| 2952 |
+
"type": "opencollective",
|
| 2953 |
+
"url": "https://opencollective.com/unified"
|
| 2954 |
+
}
|
| 2955 |
+
},
|
| 2956 |
+
"node_modules/micromark-extension-gfm-tagfilter": {
|
| 2957 |
+
"version": "2.0.0",
|
| 2958 |
+
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
|
| 2959 |
+
"integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
|
| 2960 |
+
"license": "MIT",
|
| 2961 |
+
"dependencies": {
|
| 2962 |
+
"micromark-util-types": "^2.0.0"
|
| 2963 |
+
},
|
| 2964 |
+
"funding": {
|
| 2965 |
+
"type": "opencollective",
|
| 2966 |
+
"url": "https://opencollective.com/unified"
|
| 2967 |
+
}
|
| 2968 |
+
},
|
| 2969 |
+
"node_modules/micromark-extension-gfm-task-list-item": {
|
| 2970 |
+
"version": "2.1.0",
|
| 2971 |
+
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
|
| 2972 |
+
"integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
|
| 2973 |
+
"license": "MIT",
|
| 2974 |
+
"dependencies": {
|
| 2975 |
+
"devlop": "^1.0.0",
|
| 2976 |
+
"micromark-factory-space": "^2.0.0",
|
| 2977 |
+
"micromark-util-character": "^2.0.0",
|
| 2978 |
+
"micromark-util-symbol": "^2.0.0",
|
| 2979 |
+
"micromark-util-types": "^2.0.0"
|
| 2980 |
+
},
|
| 2981 |
+
"funding": {
|
| 2982 |
+
"type": "opencollective",
|
| 2983 |
+
"url": "https://opencollective.com/unified"
|
| 2984 |
+
}
|
| 2985 |
+
},
|
| 2986 |
"node_modules/micromark-factory-destination": {
|
| 2987 |
"version": "2.0.1",
|
| 2988 |
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
|
|
|
|
| 3999 |
"node": ">=8.10.0"
|
| 4000 |
}
|
| 4001 |
},
|
| 4002 |
+
"node_modules/remark-gfm": {
|
| 4003 |
+
"version": "4.0.1",
|
| 4004 |
+
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
| 4005 |
+
"integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
|
| 4006 |
+
"license": "MIT",
|
| 4007 |
+
"dependencies": {
|
| 4008 |
+
"@types/mdast": "^4.0.0",
|
| 4009 |
+
"mdast-util-gfm": "^3.0.0",
|
| 4010 |
+
"micromark-extension-gfm": "^3.0.0",
|
| 4011 |
+
"remark-parse": "^11.0.0",
|
| 4012 |
+
"remark-stringify": "^11.0.0",
|
| 4013 |
+
"unified": "^11.0.0"
|
| 4014 |
+
},
|
| 4015 |
+
"funding": {
|
| 4016 |
+
"type": "opencollective",
|
| 4017 |
+
"url": "https://opencollective.com/unified"
|
| 4018 |
+
}
|
| 4019 |
+
},
|
| 4020 |
"node_modules/remark-parse": {
|
| 4021 |
"version": "11.0.0",
|
| 4022 |
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
|
|
|
| 4050 |
"url": "https://opencollective.com/unified"
|
| 4051 |
}
|
| 4052 |
},
|
| 4053 |
+
"node_modules/remark-stringify": {
|
| 4054 |
+
"version": "11.0.0",
|
| 4055 |
+
"resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
|
| 4056 |
+
"integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
|
| 4057 |
+
"license": "MIT",
|
| 4058 |
+
"dependencies": {
|
| 4059 |
+
"@types/mdast": "^4.0.0",
|
| 4060 |
+
"mdast-util-to-markdown": "^2.0.0",
|
| 4061 |
+
"unified": "^11.0.0"
|
| 4062 |
+
},
|
| 4063 |
+
"funding": {
|
| 4064 |
+
"type": "opencollective",
|
| 4065 |
+
"url": "https://opencollective.com/unified"
|
| 4066 |
+
}
|
| 4067 |
+
},
|
| 4068 |
"node_modules/require-from-string": {
|
| 4069 |
"version": "2.0.2",
|
| 4070 |
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
package.json
CHANGED
|
@@ -19,6 +19,7 @@
|
|
| 19 |
"react": "18.3.1",
|
| 20 |
"react-dom": "18.3.1",
|
| 21 |
"react-markdown": "9.0.3",
|
|
|
|
| 22 |
"tailwind-merge": "2.6.0"
|
| 23 |
},
|
| 24 |
"devDependencies": {
|
|
|
|
| 19 |
"react": "18.3.1",
|
| 20 |
"react-dom": "18.3.1",
|
| 21 |
"react-markdown": "9.0.3",
|
| 22 |
+
"remark-gfm": "^4.0.1",
|
| 23 |
"tailwind-merge": "2.6.0"
|
| 24 |
},
|
| 25 |
"devDependencies": {
|
public/teich.png
ADDED
|
Git LFS Details
|
src/app/globals.css
CHANGED
|
@@ -3,54 +3,33 @@
|
|
| 3 |
@tailwind utilities;
|
| 4 |
|
| 5 |
:root {
|
| 6 |
-
--background:
|
| 7 |
-
--foreground:
|
| 8 |
-
--
|
| 9 |
-
--
|
| 10 |
-
--
|
| 11 |
-
--
|
| 12 |
-
--
|
| 13 |
-
--
|
| 14 |
-
--
|
| 15 |
-
--
|
| 16 |
-
--
|
| 17 |
-
--
|
| 18 |
-
--
|
| 19 |
-
--
|
| 20 |
-
--destructive:
|
| 21 |
-
--destructive-foreground:
|
| 22 |
-
--
|
| 23 |
-
--
|
| 24 |
-
--ring: 221 83% 53%;
|
| 25 |
--radius: 0.75rem;
|
| 26 |
}
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
--card: 224 71% 4%;
|
| 32 |
-
--card-foreground: 213 31% 91%;
|
| 33 |
-
--popover: 224 71% 4%;
|
| 34 |
-
--popover-foreground: 213 31% 91%;
|
| 35 |
-
--primary: 217 91% 60%;
|
| 36 |
-
--primary-foreground: 222 47% 11%;
|
| 37 |
-
--secondary: 222 47% 11%;
|
| 38 |
-
--secondary-foreground: 213 31% 91%;
|
| 39 |
-
--muted: 223 47% 11%;
|
| 40 |
-
--muted-foreground: 215 20% 65%;
|
| 41 |
-
--accent: 216 34% 17%;
|
| 42 |
-
--accent-foreground: 213 31% 91%;
|
| 43 |
-
--destructive: 0 63% 31%;
|
| 44 |
-
--destructive-foreground: 210 40% 98%;
|
| 45 |
-
--border: 216 34% 17%;
|
| 46 |
-
--input: 216 34% 17%;
|
| 47 |
-
--ring: 224 64% 33%;
|
| 48 |
-
}
|
| 49 |
-
|
| 50 |
-
* {
|
| 51 |
box-sizing: border-box;
|
| 52 |
-
padding: 0;
|
| 53 |
margin: 0;
|
|
|
|
| 54 |
}
|
| 55 |
|
| 56 |
html {
|
|
@@ -59,24 +38,23 @@ html {
|
|
| 59 |
|
| 60 |
html,
|
| 61 |
body {
|
|
|
|
| 62 |
max-width: 100vw;
|
| 63 |
-
min-height: 100vh;
|
| 64 |
overflow-x: hidden;
|
| 65 |
-
font-feature-settings: "rlig" 1, "calt" 1;
|
| 66 |
}
|
| 67 |
|
| 68 |
body {
|
| 69 |
-
color:
|
| 70 |
-
background:
|
| 71 |
-
font-family: -apple-system, BlinkMacSystemFont,
|
|
|
|
|
|
|
| 72 |
-webkit-font-smoothing: antialiased;
|
| 73 |
-moz-osx-font-smoothing: grayscale;
|
| 74 |
}
|
| 75 |
|
| 76 |
-
/* Scrollbar styling */
|
| 77 |
::-webkit-scrollbar {
|
| 78 |
width: 6px;
|
| 79 |
-
height: 6px;
|
| 80 |
}
|
| 81 |
|
| 82 |
::-webkit-scrollbar-track {
|
|
@@ -84,27 +62,24 @@ body {
|
|
| 84 |
}
|
| 85 |
|
| 86 |
::-webkit-scrollbar-thumb {
|
| 87 |
-
background:
|
| 88 |
border-radius: 3px;
|
| 89 |
}
|
| 90 |
|
| 91 |
::-webkit-scrollbar-thumb:hover {
|
| 92 |
-
background:
|
| 93 |
}
|
| 94 |
|
| 95 |
-
/* Selection */
|
| 96 |
::selection {
|
| 97 |
-
background:
|
| 98 |
-
color:
|
| 99 |
}
|
| 100 |
|
| 101 |
-
/* Focus styles */
|
| 102 |
:focus-visible {
|
| 103 |
-
outline: 2px solid
|
| 104 |
outline-offset: 2px;
|
| 105 |
}
|
| 106 |
|
| 107 |
-
/* Animations */
|
| 108 |
@keyframes fadeIn {
|
| 109 |
from {
|
| 110 |
opacity: 0;
|
|
@@ -115,10 +90,10 @@ body {
|
|
| 115 |
}
|
| 116 |
}
|
| 117 |
|
| 118 |
-
@keyframes
|
| 119 |
from {
|
| 120 |
opacity: 0;
|
| 121 |
-
transform: translateY(
|
| 122 |
}
|
| 123 |
|
| 124 |
to {
|
|
@@ -127,157 +102,284 @@ body {
|
|
| 127 |
}
|
| 128 |
}
|
| 129 |
|
| 130 |
-
@keyframes
|
| 131 |
|
| 132 |
0%,
|
|
|
|
| 133 |
100% {
|
| 134 |
-
|
| 135 |
}
|
| 136 |
|
| 137 |
-
|
| 138 |
-
|
| 139 |
}
|
| 140 |
}
|
| 141 |
|
| 142 |
.animate-fade-in {
|
| 143 |
-
animation: fadeIn 0.
|
| 144 |
}
|
| 145 |
|
| 146 |
-
.animate-slide-
|
| 147 |
-
animation:
|
| 148 |
}
|
| 149 |
|
| 150 |
-
.
|
| 151 |
-
animation:
|
|
|
|
| 152 |
}
|
| 153 |
|
| 154 |
-
|
| 155 |
-
.
|
| 156 |
-
|
| 157 |
}
|
| 158 |
|
| 159 |
-
|
| 160 |
-
.
|
| 161 |
-
background: hsl(var(--background) / 0.8);
|
| 162 |
-
backdrop-filter: blur(12px);
|
| 163 |
-
-webkit-backdrop-filter: blur(12px);
|
| 164 |
}
|
| 165 |
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
-
|
| 170 |
-
-
|
| 171 |
-
background-clip: text;
|
| 172 |
}
|
| 173 |
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
}
|
| 178 |
|
| 179 |
-
.
|
| 180 |
-
background:
|
|
|
|
| 181 |
}
|
| 182 |
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
|
|
|
| 186 |
}
|
| 187 |
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
|
|
|
|
|
|
| 192 |
}
|
| 193 |
|
| 194 |
-
.
|
| 195 |
-
margin
|
| 196 |
}
|
| 197 |
|
| 198 |
-
.
|
| 199 |
margin-bottom: 0;
|
| 200 |
}
|
| 201 |
|
| 202 |
-
.
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
}
|
| 211 |
|
| 212 |
-
.
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
| 218 |
}
|
| 219 |
|
| 220 |
-
.
|
| 221 |
-
|
| 222 |
-
padding: 0;
|
| 223 |
-
border-radius: 0;
|
| 224 |
}
|
| 225 |
|
| 226 |
-
.
|
| 227 |
-
|
| 228 |
-
padding-left: 1.5rem;
|
| 229 |
-
margin: 0.75rem 0;
|
| 230 |
}
|
| 231 |
|
| 232 |
-
.
|
| 233 |
-
|
| 234 |
}
|
| 235 |
|
| 236 |
-
.
|
| 237 |
-
|
| 238 |
-
padding-left: 1rem;
|
| 239 |
-
margin: 1rem 0;
|
| 240 |
-
color: hsl(var(--muted-foreground));
|
| 241 |
-
font-style: italic;
|
| 242 |
}
|
| 243 |
|
| 244 |
-
.
|
| 245 |
-
.
|
| 246 |
-
.
|
| 247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
font-weight: 600;
|
| 249 |
-
margin-top: 1.5em;
|
| 250 |
-
margin-bottom: 0.5em;
|
| 251 |
}
|
| 252 |
|
| 253 |
-
.
|
| 254 |
-
font-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
}
|
| 256 |
|
| 257 |
-
.
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
}
|
| 260 |
|
| 261 |
-
.
|
| 262 |
-
|
|
|
|
|
|
|
|
|
|
| 263 |
}
|
| 264 |
|
| 265 |
-
.
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
}
|
| 270 |
|
| 271 |
-
.
|
| 272 |
-
|
| 273 |
}
|
| 274 |
|
| 275 |
-
.
|
|
|
|
|
|
|
| 276 |
font-weight: 600;
|
|
|
|
| 277 |
}
|
| 278 |
|
| 279 |
-
.
|
| 280 |
-
|
| 281 |
-
border-
|
| 282 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
}
|
|
|
|
| 3 |
@tailwind utilities;
|
| 4 |
|
| 5 |
:root {
|
| 6 |
+
--background: #09090b;
|
| 7 |
+
--foreground: #fafafa;
|
| 8 |
+
--muted: #18181b;
|
| 9 |
+
--muted-foreground: #a1a1aa;
|
| 10 |
+
--border: #27272a;
|
| 11 |
+
--card: #18181b;
|
| 12 |
+
--card-foreground: #fafafa;
|
| 13 |
+
--accent: #ff4c00;
|
| 14 |
+
--accent-hover: #ff6a2a;
|
| 15 |
+
--accent-light: rgba(255, 76, 0, 0.15);
|
| 16 |
+
--primary: #ff4c00;
|
| 17 |
+
--primary-foreground: #ffffff;
|
| 18 |
+
--secondary: #18181b;
|
| 19 |
+
--secondary-foreground: #fafafa;
|
| 20 |
+
--destructive: #ef4444;
|
| 21 |
+
--destructive-foreground: #ffffff;
|
| 22 |
+
--input: #27272a;
|
| 23 |
+
--ring: #ff4c00;
|
|
|
|
| 24 |
--radius: 0.75rem;
|
| 25 |
}
|
| 26 |
|
| 27 |
+
*,
|
| 28 |
+
*::before,
|
| 29 |
+
*::after {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
box-sizing: border-box;
|
|
|
|
| 31 |
margin: 0;
|
| 32 |
+
padding: 0;
|
| 33 |
}
|
| 34 |
|
| 35 |
html {
|
|
|
|
| 38 |
|
| 39 |
html,
|
| 40 |
body {
|
| 41 |
+
height: 100%;
|
| 42 |
max-width: 100vw;
|
|
|
|
| 43 |
overflow-x: hidden;
|
|
|
|
| 44 |
}
|
| 45 |
|
| 46 |
body {
|
| 47 |
+
color: var(--foreground);
|
| 48 |
+
background: var(--background);
|
| 49 |
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
| 50 |
+
font-size: 15px;
|
| 51 |
+
line-height: 1.6;
|
| 52 |
-webkit-font-smoothing: antialiased;
|
| 53 |
-moz-osx-font-smoothing: grayscale;
|
| 54 |
}
|
| 55 |
|
|
|
|
| 56 |
::-webkit-scrollbar {
|
| 57 |
width: 6px;
|
|
|
|
| 58 |
}
|
| 59 |
|
| 60 |
::-webkit-scrollbar-track {
|
|
|
|
| 62 |
}
|
| 63 |
|
| 64 |
::-webkit-scrollbar-thumb {
|
| 65 |
+
background: #3f3f46;
|
| 66 |
border-radius: 3px;
|
| 67 |
}
|
| 68 |
|
| 69 |
::-webkit-scrollbar-thumb:hover {
|
| 70 |
+
background: #52525b;
|
| 71 |
}
|
| 72 |
|
|
|
|
| 73 |
::selection {
|
| 74 |
+
background: var(--accent-light);
|
| 75 |
+
color: var(--foreground);
|
| 76 |
}
|
| 77 |
|
|
|
|
| 78 |
:focus-visible {
|
| 79 |
+
outline: 2px solid var(--ring);
|
| 80 |
outline-offset: 2px;
|
| 81 |
}
|
| 82 |
|
|
|
|
| 83 |
@keyframes fadeIn {
|
| 84 |
from {
|
| 85 |
opacity: 0;
|
|
|
|
| 90 |
}
|
| 91 |
}
|
| 92 |
|
| 93 |
+
@keyframes slideIn {
|
| 94 |
from {
|
| 95 |
opacity: 0;
|
| 96 |
+
transform: translateY(8px);
|
| 97 |
}
|
| 98 |
|
| 99 |
to {
|
|
|
|
| 102 |
}
|
| 103 |
}
|
| 104 |
|
| 105 |
+
@keyframes bounce-dot {
|
| 106 |
|
| 107 |
0%,
|
| 108 |
+
80%,
|
| 109 |
100% {
|
| 110 |
+
transform: scale(0);
|
| 111 |
}
|
| 112 |
|
| 113 |
+
40% {
|
| 114 |
+
transform: scale(1);
|
| 115 |
}
|
| 116 |
}
|
| 117 |
|
| 118 |
.animate-fade-in {
|
| 119 |
+
animation: fadeIn 0.2s ease-out;
|
| 120 |
}
|
| 121 |
|
| 122 |
+
.animate-slide-in {
|
| 123 |
+
animation: slideIn 0.3s ease-out;
|
| 124 |
}
|
| 125 |
|
| 126 |
+
.dot-1 {
|
| 127 |
+
animation: bounce-dot 1.4s infinite ease-in-out both;
|
| 128 |
+
animation-delay: -0.32s;
|
| 129 |
}
|
| 130 |
|
| 131 |
+
.dot-2 {
|
| 132 |
+
animation: bounce-dot 1.4s infinite ease-in-out both;
|
| 133 |
+
animation-delay: -0.16s;
|
| 134 |
}
|
| 135 |
|
| 136 |
+
.dot-3 {
|
| 137 |
+
animation: bounce-dot 1.4s infinite ease-in-out both;
|
|
|
|
|
|
|
|
|
|
| 138 |
}
|
| 139 |
|
| 140 |
+
.input-container {
|
| 141 |
+
background: var(--card);
|
| 142 |
+
border: 1px solid var(--border);
|
| 143 |
+
border-radius: 12px;
|
| 144 |
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
|
|
| 145 |
}
|
| 146 |
|
| 147 |
+
.input-container:focus-within {
|
| 148 |
+
border-color: var(--accent);
|
| 149 |
+
box-shadow: 0 0 0 3px var(--accent-light);
|
| 150 |
}
|
| 151 |
|
| 152 |
+
.user-message {
|
| 153 |
+
background: var(--accent);
|
| 154 |
+
color: var(--primary-foreground);
|
| 155 |
}
|
| 156 |
|
| 157 |
+
.markdown-content {
|
| 158 |
+
font-size: 15px;
|
| 159 |
+
line-height: 1.7;
|
| 160 |
+
color: var(--foreground);
|
| 161 |
}
|
| 162 |
|
| 163 |
+
.markdown-content>*:first-child {
|
| 164 |
+
margin-top: 0;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
.markdown-content>*:last-child {
|
| 168 |
+
margin-bottom: 0;
|
| 169 |
}
|
| 170 |
|
| 171 |
+
.markdown-content p {
|
| 172 |
+
margin: 0 0 1em 0;
|
| 173 |
}
|
| 174 |
|
| 175 |
+
.markdown-content p:last-child {
|
| 176 |
margin-bottom: 0;
|
| 177 |
}
|
| 178 |
|
| 179 |
+
.markdown-content h1,
|
| 180 |
+
.markdown-content h2,
|
| 181 |
+
.markdown-content h3,
|
| 182 |
+
.markdown-content h4 {
|
| 183 |
+
font-weight: 600;
|
| 184 |
+
line-height: 1.3;
|
| 185 |
+
margin: 1.5em 0 0.5em 0;
|
| 186 |
+
color: var(--foreground);
|
| 187 |
}
|
| 188 |
|
| 189 |
+
.markdown-content h1:first-child,
|
| 190 |
+
.markdown-content h2:first-child,
|
| 191 |
+
.markdown-content h3:first-child,
|
| 192 |
+
.markdown-content h4:first-child {
|
| 193 |
+
margin-top: 0;
|
|
|
|
| 194 |
}
|
| 195 |
|
| 196 |
+
.markdown-content h1 {
|
| 197 |
+
font-size: 1.5em;
|
|
|
|
|
|
|
| 198 |
}
|
| 199 |
|
| 200 |
+
.markdown-content h2 {
|
| 201 |
+
font-size: 1.25em;
|
|
|
|
|
|
|
| 202 |
}
|
| 203 |
|
| 204 |
+
.markdown-content h3 {
|
| 205 |
+
font-size: 1.1em;
|
| 206 |
}
|
| 207 |
|
| 208 |
+
.markdown-content h4 {
|
| 209 |
+
font-size: 1em;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
}
|
| 211 |
|
| 212 |
+
.markdown-content ul,
|
| 213 |
+
.markdown-content ol {
|
| 214 |
+
margin: 0.75em 0;
|
| 215 |
+
padding-left: 1.5em;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
.markdown-content li {
|
| 219 |
+
margin: 0.35em 0;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
.markdown-content li>ul,
|
| 223 |
+
.markdown-content li>ol {
|
| 224 |
+
margin: 0.25em 0;
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
.markdown-content a {
|
| 228 |
+
color: var(--accent);
|
| 229 |
+
text-decoration: none;
|
| 230 |
+
border-bottom: 1px solid transparent;
|
| 231 |
+
transition: border-color 0.15s;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
.markdown-content a:hover {
|
| 235 |
+
border-bottom-color: var(--accent);
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
.markdown-content strong {
|
| 239 |
font-weight: 600;
|
|
|
|
|
|
|
| 240 |
}
|
| 241 |
|
| 242 |
+
.markdown-content em {
|
| 243 |
+
font-style: italic;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
.markdown-content blockquote {
|
| 247 |
+
margin: 1em 0;
|
| 248 |
+
padding: 0.5em 0 0.5em 1em;
|
| 249 |
+
border-left: 3px solid var(--accent);
|
| 250 |
+
color: var(--muted-foreground);
|
| 251 |
+
font-style: italic;
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
.markdown-content hr {
|
| 255 |
+
margin: 1.5em 0;
|
| 256 |
+
border: none;
|
| 257 |
+
border-top: 1px solid var(--border);
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
.markdown-content .code-block {
|
| 261 |
+
margin: 1em 0;
|
| 262 |
+
padding: 1em;
|
| 263 |
+
background: var(--muted);
|
| 264 |
+
border: 1px solid var(--border);
|
| 265 |
+
border-radius: 8px;
|
| 266 |
+
overflow-x: auto;
|
| 267 |
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, monospace;
|
| 268 |
+
font-size: 0.875em;
|
| 269 |
+
line-height: 1.5;
|
| 270 |
}
|
| 271 |
|
| 272 |
+
.markdown-content .inline-code {
|
| 273 |
+
padding: 0.15em 0.4em;
|
| 274 |
+
background: var(--muted);
|
| 275 |
+
border-radius: 4px;
|
| 276 |
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, monospace;
|
| 277 |
+
font-size: 0.9em;
|
| 278 |
}
|
| 279 |
|
| 280 |
+
.markdown-content .table-wrapper {
|
| 281 |
+
margin: 1em 0;
|
| 282 |
+
overflow-x: auto;
|
| 283 |
+
border: 1px solid var(--border);
|
| 284 |
+
border-radius: 8px;
|
| 285 |
}
|
| 286 |
|
| 287 |
+
.markdown-content table {
|
| 288 |
+
width: 100%;
|
| 289 |
+
border-collapse: collapse;
|
| 290 |
+
font-size: 0.9em;
|
| 291 |
}
|
| 292 |
|
| 293 |
+
.markdown-content thead {
|
| 294 |
+
background: var(--muted);
|
| 295 |
}
|
| 296 |
|
| 297 |
+
.markdown-content th {
|
| 298 |
+
padding: 0.75em 1em;
|
| 299 |
+
text-align: left;
|
| 300 |
font-weight: 600;
|
| 301 |
+
border-bottom: 1px solid var(--border);
|
| 302 |
}
|
| 303 |
|
| 304 |
+
.markdown-content td {
|
| 305 |
+
padding: 0.75em 1em;
|
| 306 |
+
border-bottom: 1px solid var(--border);
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
.markdown-content tbody tr:last-child td {
|
| 310 |
+
border-bottom: none;
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
.markdown-content tbody tr:hover {
|
| 314 |
+
background: rgba(255, 255, 255, 0.02);
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
.thinking-block {
|
| 318 |
+
margin: 0.5em 0;
|
| 319 |
+
border: 1px solid var(--border);
|
| 320 |
+
border-radius: 8px;
|
| 321 |
+
overflow: hidden;
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
.thinking-header {
|
| 325 |
+
display: flex;
|
| 326 |
+
align-items: center;
|
| 327 |
+
gap: 0.5em;
|
| 328 |
+
padding: 0.625em 0.875em;
|
| 329 |
+
background: var(--muted);
|
| 330 |
+
cursor: pointer;
|
| 331 |
+
-webkit-user-select: none;
|
| 332 |
+
user-select: none;
|
| 333 |
+
font-size: 0.875em;
|
| 334 |
+
color: var(--muted-foreground);
|
| 335 |
+
transition: background 0.15s;
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
.thinking-header:hover {
|
| 339 |
+
background: #27272a;
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
.thinking-content {
|
| 343 |
+
padding: 0.875em;
|
| 344 |
+
font-size: 0.875em;
|
| 345 |
+
color: var(--muted-foreground);
|
| 346 |
+
line-height: 1.6;
|
| 347 |
+
white-space: pre-wrap;
|
| 348 |
+
border-top: 1px solid var(--border);
|
| 349 |
+
background: var(--background);
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
.tool-block {
|
| 353 |
+
margin: 0.5em 0;
|
| 354 |
+
border: 1px solid var(--border);
|
| 355 |
+
border-radius: 8px;
|
| 356 |
+
overflow: hidden;
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
.tool-header {
|
| 360 |
+
display: flex;
|
| 361 |
+
align-items: center;
|
| 362 |
+
gap: 0.625em;
|
| 363 |
+
padding: 0.625em 0.875em;
|
| 364 |
+
background: var(--muted);
|
| 365 |
+
cursor: pointer;
|
| 366 |
+
-webkit-user-select: none;
|
| 367 |
+
user-select: none;
|
| 368 |
+
font-size: 0.875em;
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
.tool-header:hover {
|
| 372 |
+
background: #27272a;
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
.tool-content {
|
| 376 |
+
padding: 0.875em;
|
| 377 |
+
font-size: 0.8125em;
|
| 378 |
+
color: var(--muted-foreground);
|
| 379 |
+
line-height: 1.5;
|
| 380 |
+
white-space: pre-wrap;
|
| 381 |
+
border-top: 1px solid var(--border);
|
| 382 |
+
max-height: 200px;
|
| 383 |
+
overflow-y: auto;
|
| 384 |
+
background: var(--background);
|
| 385 |
}
|
src/components/chat.tsx
CHANGED
|
@@ -1,10 +1,7 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
import { useState, useRef, useEffect } from "react";
|
| 4 |
-
import { Globe, Send, Loader2,
|
| 5 |
-
import { Button } from "@/components/ui/button";
|
| 6 |
-
import { ReasoningBlock } from "@/components/reasoning-block";
|
| 7 |
-
import { ToolInvocation } from "@/components/tool-invocation";
|
| 8 |
import { Markdown } from "@/components/markdown";
|
| 9 |
import { cn } from "@/lib/utils";
|
| 10 |
|
|
@@ -22,6 +19,93 @@ interface Message {
|
|
| 22 |
}>;
|
| 23 |
}
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
export function Chat() {
|
| 26 |
const [messages, setMessages] = useState<Message[]>([]);
|
| 27 |
const [input, setInput] = useState("");
|
|
@@ -39,12 +123,11 @@ export function Chat() {
|
|
| 39 |
scrollToBottom();
|
| 40 |
}, [messages, streamingMessage]);
|
| 41 |
|
| 42 |
-
// Auto-resize textarea
|
| 43 |
useEffect(() => {
|
| 44 |
const textarea = textareaRef.current;
|
| 45 |
if (textarea) {
|
| 46 |
textarea.style.height = "auto";
|
| 47 |
-
textarea.style.height = Math.min(textarea.scrollHeight,
|
| 48 |
}
|
| 49 |
}, [input]);
|
| 50 |
|
|
@@ -169,7 +252,6 @@ export function Chat() {
|
|
| 169 |
}
|
| 170 |
}
|
| 171 |
|
| 172 |
-
// Finalize message
|
| 173 |
setStreamingMessage((prev) => {
|
| 174 |
if (prev) {
|
| 175 |
setMessages((msgs) => [...msgs, prev]);
|
|
@@ -198,62 +280,75 @@ export function Chat() {
|
|
| 198 |
|
| 199 |
const hasMessages = messages.length > 0 || streamingMessage;
|
| 200 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
return (
|
| 202 |
<div className="flex flex-col h-screen bg-background">
|
| 203 |
{/* Header */}
|
| 204 |
-
<header className="
|
| 205 |
-
<div className="max-w-3xl mx-auto px-4 h-14 flex items-center justify-between">
|
| 206 |
-
<div className="flex items-center gap-
|
| 207 |
-
<
|
| 208 |
-
|
| 209 |
-
</div>
|
| 210 |
-
<div>
|
| 211 |
-
<h1 className="text-sm font-semibold">Qwen3 Claude Opus</h1>
|
| 212 |
-
<p className="text-xs text-muted-foreground">Reasoning Model</p>
|
| 213 |
-
</div>
|
| 214 |
</div>
|
| 215 |
{hasMessages && (
|
| 216 |
-
<
|
| 217 |
-
variant="ghost"
|
| 218 |
-
size="sm"
|
| 219 |
onClick={clearChat}
|
| 220 |
-
className="text-
|
| 221 |
>
|
| 222 |
-
<
|
| 223 |
-
|
| 224 |
-
</
|
| 225 |
)}
|
| 226 |
</div>
|
| 227 |
</header>
|
| 228 |
|
| 229 |
-
{/* Messages
|
| 230 |
-
<
|
| 231 |
-
<div className="max-w-3xl mx-auto px-4 py-6">
|
| 232 |
-
{/* Empty State */}
|
| 233 |
{!hasMessages && (
|
| 234 |
-
<div className="flex flex-col items-center justify-center min-h-[
|
| 235 |
-
<
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
<h2 className="text-2xl font-semibold mb-2 text-balance">
|
| 239 |
-
How can I help you today?
|
| 240 |
-
</h2>
|
| 241 |
-
<p className="text-muted-foreground max-w-md text-balance">
|
| 242 |
-
I'm a reasoning model that thinks through problems step by step.
|
| 243 |
-
Toggle web search for real-time information.
|
| 244 |
</p>
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
<div className="flex flex-wrap gap-2 mt-8 justify-center">
|
| 248 |
-
{[
|
| 249 |
-
"Explain quantum computing",
|
| 250 |
-
"Write a Python function",
|
| 251 |
-
"What's the latest news?",
|
| 252 |
-
].map((prompt) => (
|
| 253 |
<button
|
| 254 |
key={prompt}
|
| 255 |
onClick={() => setInput(prompt)}
|
| 256 |
-
className="px-
|
| 257 |
>
|
| 258 |
{prompt}
|
| 259 |
</button>
|
|
@@ -262,160 +357,92 @@ export function Chat() {
|
|
| 262 |
</div>
|
| 263 |
)}
|
| 264 |
|
| 265 |
-
{/* Messages */}
|
| 266 |
<div className="space-y-6">
|
| 267 |
{messages.map((message) => (
|
| 268 |
-
<div
|
| 269 |
-
key={message.id}
|
| 270 |
-
className={cn(
|
| 271 |
-
"animate-slide-up",
|
| 272 |
-
message.role === "user" ? "flex justify-end" : ""
|
| 273 |
-
)}
|
| 274 |
-
>
|
| 275 |
{message.role === "user" ? (
|
| 276 |
-
<div className="
|
| 277 |
-
<
|
|
|
|
|
|
|
| 278 |
</div>
|
| 279 |
) : (
|
| 280 |
-
|
| 281 |
-
{message.reasoning && (
|
| 282 |
-
<ReasoningBlock
|
| 283 |
-
content={message.reasoning}
|
| 284 |
-
duration={message.reasoningDuration}
|
| 285 |
-
/>
|
| 286 |
-
)}
|
| 287 |
-
{message.toolInvocations?.map((tool, idx) => (
|
| 288 |
-
<ToolInvocation
|
| 289 |
-
key={idx}
|
| 290 |
-
toolName={tool.toolName}
|
| 291 |
-
args={tool.args}
|
| 292 |
-
result={tool.result}
|
| 293 |
-
status={tool.status}
|
| 294 |
-
/>
|
| 295 |
-
))}
|
| 296 |
-
{message.content && (
|
| 297 |
-
<div className="prose text-foreground">
|
| 298 |
-
<Markdown>{message.content}</Markdown>
|
| 299 |
-
</div>
|
| 300 |
-
)}
|
| 301 |
-
</div>
|
| 302 |
)}
|
| 303 |
</div>
|
| 304 |
))}
|
| 305 |
|
| 306 |
-
{/* Streaming message */}
|
| 307 |
{streamingMessage && (
|
| 308 |
-
<div className="
|
| 309 |
-
{streamingMessage
|
| 310 |
-
<ReasoningBlock
|
| 311 |
-
content={streamingMessage.reasoning}
|
| 312 |
-
isStreaming={!streamingMessage.reasoningDuration}
|
| 313 |
-
duration={streamingMessage.reasoningDuration}
|
| 314 |
-
/>
|
| 315 |
-
)}
|
| 316 |
-
{streamingMessage.toolInvocations?.map((tool, idx) => (
|
| 317 |
-
<ToolInvocation
|
| 318 |
-
key={idx}
|
| 319 |
-
toolName={tool.toolName}
|
| 320 |
-
args={tool.args}
|
| 321 |
-
result={tool.result}
|
| 322 |
-
status={tool.status}
|
| 323 |
-
/>
|
| 324 |
-
))}
|
| 325 |
-
{streamingMessage.content && (
|
| 326 |
-
<div className="prose text-foreground">
|
| 327 |
-
<Markdown>{streamingMessage.content}</Markdown>
|
| 328 |
-
</div>
|
| 329 |
-
)}
|
| 330 |
-
{!streamingMessage.content && !streamingMessage.reasoning && (
|
| 331 |
-
<div className="flex items-center gap-3 text-muted-foreground py-2">
|
| 332 |
-
<div className="flex gap-1">
|
| 333 |
-
<span className="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style={{ animationDelay: "0ms" }} />
|
| 334 |
-
<span className="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style={{ animationDelay: "150ms" }} />
|
| 335 |
-
<span className="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style={{ animationDelay: "300ms" }} />
|
| 336 |
-
</div>
|
| 337 |
-
<span className="text-sm">Thinking...</span>
|
| 338 |
-
</div>
|
| 339 |
-
)}
|
| 340 |
</div>
|
| 341 |
)}
|
| 342 |
</div>
|
| 343 |
|
| 344 |
<div ref={messagesEndRef} className="h-4" />
|
| 345 |
</div>
|
| 346 |
-
</
|
| 347 |
-
|
| 348 |
-
{/* Input
|
| 349 |
-
<
|
| 350 |
-
<div className="max-w-3xl mx-auto px-4 py-4">
|
| 351 |
-
<form onSubmit={handleSubmit}
|
| 352 |
-
<div className=
|
| 353 |
-
"flex items-end gap-2 p-2 rounded-2xl border border-border bg-background/50 transition-all duration-200",
|
| 354 |
-
"input-glow focus-within:border-primary/50"
|
| 355 |
-
)}>
|
| 356 |
-
{/* Web Search Toggle */}
|
| 357 |
<button
|
| 358 |
type="button"
|
| 359 |
onClick={() => setSearchEnabled(!searchEnabled)}
|
| 360 |
className={cn(
|
| 361 |
-
"shrink-0 w-
|
| 362 |
searchEnabled
|
| 363 |
-
? "bg-
|
| 364 |
-
: "
|
| 365 |
)}
|
| 366 |
-
title={searchEnabled ? "
|
| 367 |
>
|
| 368 |
-
<Globe className="w-
|
| 369 |
</button>
|
| 370 |
|
| 371 |
-
{/* Textarea */}
|
| 372 |
<textarea
|
| 373 |
ref={textareaRef}
|
| 374 |
value={input}
|
| 375 |
onChange={(e) => setInput(e.target.value)}
|
| 376 |
onKeyDown={handleKeyDown}
|
| 377 |
-
placeholder="
|
| 378 |
rows={1}
|
| 379 |
disabled={isLoading}
|
| 380 |
-
className=
|
| 381 |
-
"flex-1 bg-transparent border-0 resize-none text-sm leading-6",
|
| 382 |
-
"placeholder:text-muted-foreground focus:outline-none focus:ring-0",
|
| 383 |
-
"min-h-[40px] max-h-[200px] py-2 px-1"
|
| 384 |
-
)}
|
| 385 |
/>
|
| 386 |
|
| 387 |
-
{/* Send Button */}
|
| 388 |
<button
|
| 389 |
type="submit"
|
| 390 |
disabled={isLoading || !input.trim()}
|
| 391 |
className={cn(
|
| 392 |
-
"shrink-0 w-
|
| 393 |
input.trim() && !isLoading
|
| 394 |
-
? "bg-
|
| 395 |
-
: "
|
| 396 |
)}
|
| 397 |
>
|
| 398 |
{isLoading ? (
|
| 399 |
-
<Loader2 className="w-
|
| 400 |
) : (
|
| 401 |
-
<Send className="w-
|
| 402 |
)}
|
| 403 |
</button>
|
| 404 |
</div>
|
| 405 |
|
| 406 |
-
|
| 407 |
-
<div className="flex items-center justify-center gap-4 mt-3 text-xs text-muted-foreground">
|
| 408 |
{searchEnabled && (
|
| 409 |
<span className="flex items-center gap-1.5">
|
| 410 |
-
<span className="w-1.5 h-1.5 bg-
|
| 411 |
-
Web search
|
| 412 |
</span>
|
| 413 |
)}
|
| 414 |
-
<span>
|
| 415 |
</div>
|
| 416 |
</form>
|
| 417 |
</div>
|
| 418 |
-
</
|
| 419 |
</div>
|
| 420 |
);
|
| 421 |
}
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
import { useState, useRef, useEffect } from "react";
|
| 4 |
+
import { Globe, Send, Loader2, Plus, ChevronDown, ChevronRight } from "lucide-react";
|
|
|
|
|
|
|
|
|
|
| 5 |
import { Markdown } from "@/components/markdown";
|
| 6 |
import { cn } from "@/lib/utils";
|
| 7 |
|
|
|
|
| 19 |
}>;
|
| 20 |
}
|
| 21 |
|
| 22 |
+
function ThinkingBlock({
|
| 23 |
+
content,
|
| 24 |
+
duration,
|
| 25 |
+
isStreaming
|
| 26 |
+
}: {
|
| 27 |
+
content: string;
|
| 28 |
+
duration?: number;
|
| 29 |
+
isStreaming?: boolean;
|
| 30 |
+
}) {
|
| 31 |
+
const [isOpen, setIsOpen] = useState(isStreaming);
|
| 32 |
+
|
| 33 |
+
useEffect(() => {
|
| 34 |
+
if (isStreaming) setIsOpen(true);
|
| 35 |
+
}, [isStreaming]);
|
| 36 |
+
|
| 37 |
+
const formatDuration = (s?: number) => {
|
| 38 |
+
if (!s) return "";
|
| 39 |
+
if (s < 1) return " · <1s";
|
| 40 |
+
return ` · ${Math.round(s)}s`;
|
| 41 |
+
};
|
| 42 |
+
|
| 43 |
+
return (
|
| 44 |
+
<div className="thinking-block">
|
| 45 |
+
<button
|
| 46 |
+
className="thinking-header w-full"
|
| 47 |
+
onClick={() => setIsOpen(!isOpen)}
|
| 48 |
+
>
|
| 49 |
+
{isOpen ? <ChevronDown className="w-4 h-4" /> : <ChevronRight className="w-4 h-4" />}
|
| 50 |
+
<span>
|
| 51 |
+
{isStreaming ? "Thinking..." : `Thought process${formatDuration(duration)}`}
|
| 52 |
+
</span>
|
| 53 |
+
{isStreaming && (
|
| 54 |
+
<span className="ml-auto flex gap-1">
|
| 55 |
+
<span className="w-1.5 h-1.5 rounded-full bg-current dot-1" />
|
| 56 |
+
<span className="w-1.5 h-1.5 rounded-full bg-current dot-2" />
|
| 57 |
+
<span className="w-1.5 h-1.5 rounded-full bg-current dot-3" />
|
| 58 |
+
</span>
|
| 59 |
+
)}
|
| 60 |
+
</button>
|
| 61 |
+
{isOpen && (
|
| 62 |
+
<div className="thinking-content">{content}</div>
|
| 63 |
+
)}
|
| 64 |
+
</div>
|
| 65 |
+
);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
function ToolBlock({
|
| 69 |
+
toolName,
|
| 70 |
+
args,
|
| 71 |
+
result,
|
| 72 |
+
status
|
| 73 |
+
}: {
|
| 74 |
+
toolName: string;
|
| 75 |
+
args: Record<string, unknown>;
|
| 76 |
+
result?: string;
|
| 77 |
+
status: string;
|
| 78 |
+
}) {
|
| 79 |
+
const [isOpen, setIsOpen] = useState(false);
|
| 80 |
+
const isRunning = status === "running" || status === "pending";
|
| 81 |
+
const query = (args.query as string) || JSON.stringify(args);
|
| 82 |
+
|
| 83 |
+
return (
|
| 84 |
+
<div className="tool-block">
|
| 85 |
+
<button
|
| 86 |
+
className="tool-header w-full"
|
| 87 |
+
onClick={() => setIsOpen(!isOpen)}
|
| 88 |
+
>
|
| 89 |
+
{isRunning ? (
|
| 90 |
+
<Loader2 className="w-4 h-4 animate-spin text-blue-500" />
|
| 91 |
+
) : (
|
| 92 |
+
<Globe className="w-4 h-4 text-green-500" />
|
| 93 |
+
)}
|
| 94 |
+
<span className="font-medium">
|
| 95 |
+
{toolName === "web_search" ? "Web Search" : toolName}
|
| 96 |
+
</span>
|
| 97 |
+
<span className="text-muted-foreground truncate flex-1 text-left ml-1">
|
| 98 |
+
{query}
|
| 99 |
+
</span>
|
| 100 |
+
{isOpen ? <ChevronDown className="w-4 h-4 shrink-0" /> : <ChevronRight className="w-4 h-4 shrink-0" />}
|
| 101 |
+
</button>
|
| 102 |
+
{isOpen && result && (
|
| 103 |
+
<div className="tool-content">{result}</div>
|
| 104 |
+
)}
|
| 105 |
+
</div>
|
| 106 |
+
);
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
export function Chat() {
|
| 110 |
const [messages, setMessages] = useState<Message[]>([]);
|
| 111 |
const [input, setInput] = useState("");
|
|
|
|
| 123 |
scrollToBottom();
|
| 124 |
}, [messages, streamingMessage]);
|
| 125 |
|
|
|
|
| 126 |
useEffect(() => {
|
| 127 |
const textarea = textareaRef.current;
|
| 128 |
if (textarea) {
|
| 129 |
textarea.style.height = "auto";
|
| 130 |
+
textarea.style.height = Math.min(textarea.scrollHeight, 160) + "px";
|
| 131 |
}
|
| 132 |
}, [input]);
|
| 133 |
|
|
|
|
| 252 |
}
|
| 253 |
}
|
| 254 |
|
|
|
|
| 255 |
setStreamingMessage((prev) => {
|
| 256 |
if (prev) {
|
| 257 |
setMessages((msgs) => [...msgs, prev]);
|
|
|
|
| 280 |
|
| 281 |
const hasMessages = messages.length > 0 || streamingMessage;
|
| 282 |
|
| 283 |
+
const renderMessage = (message: Message, isStreaming = false) => (
|
| 284 |
+
<div className="space-y-3">
|
| 285 |
+
{message.reasoning && (
|
| 286 |
+
<ThinkingBlock
|
| 287 |
+
content={message.reasoning}
|
| 288 |
+
duration={message.reasoningDuration}
|
| 289 |
+
isStreaming={isStreaming && !message.reasoningDuration}
|
| 290 |
+
/>
|
| 291 |
+
)}
|
| 292 |
+
{message.toolInvocations?.map((tool, idx) => (
|
| 293 |
+
<ToolBlock
|
| 294 |
+
key={idx}
|
| 295 |
+
toolName={tool.toolName}
|
| 296 |
+
args={tool.args}
|
| 297 |
+
result={tool.result}
|
| 298 |
+
status={tool.status}
|
| 299 |
+
/>
|
| 300 |
+
))}
|
| 301 |
+
{message.content && (
|
| 302 |
+
<Markdown>{message.content}</Markdown>
|
| 303 |
+
)}
|
| 304 |
+
{isStreaming && !message.content && !message.reasoning && (
|
| 305 |
+
<div className="flex items-center gap-2 py-2 text-muted-foreground">
|
| 306 |
+
<span className="flex gap-1">
|
| 307 |
+
<span className="w-2 h-2 rounded-full bg-current dot-1" />
|
| 308 |
+
<span className="w-2 h-2 rounded-full bg-current dot-2" />
|
| 309 |
+
<span className="w-2 h-2 rounded-full bg-current dot-3" />
|
| 310 |
+
</span>
|
| 311 |
+
</div>
|
| 312 |
+
)}
|
| 313 |
+
</div>
|
| 314 |
+
);
|
| 315 |
+
|
| 316 |
return (
|
| 317 |
<div className="flex flex-col h-screen bg-background">
|
| 318 |
{/* Header */}
|
| 319 |
+
<header className="shrink-0 border-b border-[#27272a] bg-[#09090b]">
|
| 320 |
+
<div className="max-w-3xl mx-auto px-4 sm:px-6 h-14 flex items-center justify-between">
|
| 321 |
+
<div className="flex items-center gap-2.5">
|
| 322 |
+
<img src="/teich.png" alt="Teich AI" className="w-8 h-8" />
|
| 323 |
+
<span className="font-semibold text-[#fafafa]">Qwen3-4B-Thinking-2507-Claude-4.5-Opus</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
</div>
|
| 325 |
{hasMessages && (
|
| 326 |
+
<button
|
|
|
|
|
|
|
| 327 |
onClick={clearChat}
|
| 328 |
+
className="flex items-center gap-1.5 px-3 py-1.5 text-sm text-[#a1a1aa] hover:text-[#fafafa] hover:bg-[#27272a] rounded-md transition-colors"
|
| 329 |
>
|
| 330 |
+
<Plus className="w-4 h-4" />
|
| 331 |
+
New Chat
|
| 332 |
+
</button>
|
| 333 |
)}
|
| 334 |
</div>
|
| 335 |
</header>
|
| 336 |
|
| 337 |
+
{/* Messages */}
|
| 338 |
+
<main className="flex-1 overflow-y-auto">
|
| 339 |
+
<div className="max-w-3xl mx-auto px-4 sm:px-6 py-6">
|
|
|
|
| 340 |
{!hasMessages && (
|
| 341 |
+
<div className="flex flex-col items-center justify-center min-h-[50vh] text-center animate-fade-in">
|
| 342 |
+
<h2 className="text-xl font-semibold mb-2 text-[#fafafa]">How can I help you?</h2>
|
| 343 |
+
<p className="text-[#a1a1aa] text-sm max-w-sm mb-8">
|
| 344 |
+
A reasoning model that thinks step by step. Enable web search for real-time information.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 345 |
</p>
|
| 346 |
+
<div className="flex flex-wrap gap-2 justify-center">
|
| 347 |
+
{["Explain quantum computing", "Write a Python function", "What's in the news?"].map((prompt) => (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
<button
|
| 349 |
key={prompt}
|
| 350 |
onClick={() => setInput(prompt)}
|
| 351 |
+
className="px-3 py-1.5 text-sm border border-[#27272a] text-[#a1a1aa] rounded-md hover:bg-[#18181b] hover:text-[#fafafa] hover:border-[#3f3f46] transition-colors"
|
| 352 |
>
|
| 353 |
{prompt}
|
| 354 |
</button>
|
|
|
|
| 357 |
</div>
|
| 358 |
)}
|
| 359 |
|
|
|
|
| 360 |
<div className="space-y-6">
|
| 361 |
{messages.map((message) => (
|
| 362 |
+
<div key={message.id} className="animate-slide-in">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
{message.role === "user" ? (
|
| 364 |
+
<div className="flex justify-end">
|
| 365 |
+
<div className="user-message px-4 py-2.5 rounded-2xl rounded-br-sm max-w-[85%]">
|
| 366 |
+
<p className="text-sm whitespace-pre-wrap">{message.content}</p>
|
| 367 |
+
</div>
|
| 368 |
</div>
|
| 369 |
) : (
|
| 370 |
+
renderMessage(message)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
)}
|
| 372 |
</div>
|
| 373 |
))}
|
| 374 |
|
|
|
|
| 375 |
{streamingMessage && (
|
| 376 |
+
<div className="animate-fade-in">
|
| 377 |
+
{renderMessage(streamingMessage, true)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 378 |
</div>
|
| 379 |
)}
|
| 380 |
</div>
|
| 381 |
|
| 382 |
<div ref={messagesEndRef} className="h-4" />
|
| 383 |
</div>
|
| 384 |
+
</main>
|
| 385 |
+
|
| 386 |
+
{/* Input */}
|
| 387 |
+
<footer className="shrink-0 border-t border-[#27272a] bg-[#09090b]">
|
| 388 |
+
<div className="max-w-3xl mx-auto px-4 sm:px-6 py-4">
|
| 389 |
+
<form onSubmit={handleSubmit}>
|
| 390 |
+
<div className="input-container flex items-end gap-2 p-2">
|
|
|
|
|
|
|
|
|
|
|
|
|
| 391 |
<button
|
| 392 |
type="button"
|
| 393 |
onClick={() => setSearchEnabled(!searchEnabled)}
|
| 394 |
className={cn(
|
| 395 |
+
"shrink-0 w-9 h-9 rounded-lg flex items-center justify-center transition-colors",
|
| 396 |
searchEnabled
|
| 397 |
+
? "bg-[#ff4c00] text-white"
|
| 398 |
+
: "text-[#a1a1aa] hover:text-[#fafafa] hover:bg-[#27272a]"
|
| 399 |
)}
|
| 400 |
+
title={searchEnabled ? "Disable web search" : "Enable web search"}
|
| 401 |
>
|
| 402 |
+
<Globe className="w-4 h-4" />
|
| 403 |
</button>
|
| 404 |
|
|
|
|
| 405 |
<textarea
|
| 406 |
ref={textareaRef}
|
| 407 |
value={input}
|
| 408 |
onChange={(e) => setInput(e.target.value)}
|
| 409 |
onKeyDown={handleKeyDown}
|
| 410 |
+
placeholder="Send a message..."
|
| 411 |
rows={1}
|
| 412 |
disabled={isLoading}
|
| 413 |
+
className="flex-1 bg-transparent border-0 resize-none text-sm leading-6 text-[#fafafa] placeholder:text-[#71717a] focus:outline-none min-h-[36px] max-h-[160px] py-1.5"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
/>
|
| 415 |
|
|
|
|
| 416 |
<button
|
| 417 |
type="submit"
|
| 418 |
disabled={isLoading || !input.trim()}
|
| 419 |
className={cn(
|
| 420 |
+
"shrink-0 w-9 h-9 rounded-lg flex items-center justify-center transition-colors",
|
| 421 |
input.trim() && !isLoading
|
| 422 |
+
? "bg-[#ff4c00] text-white hover:bg-[#ff6a2a]"
|
| 423 |
+
: "text-[#a1a1aa] cursor-not-allowed"
|
| 424 |
)}
|
| 425 |
>
|
| 426 |
{isLoading ? (
|
| 427 |
+
<Loader2 className="w-4 h-4 animate-spin" />
|
| 428 |
) : (
|
| 429 |
+
<Send className="w-4 h-4" />
|
| 430 |
)}
|
| 431 |
</button>
|
| 432 |
</div>
|
| 433 |
|
| 434 |
+
<div className="flex items-center justify-center gap-3 mt-2 text-xs text-[#a1a1aa]">
|
|
|
|
| 435 |
{searchEnabled && (
|
| 436 |
<span className="flex items-center gap-1.5">
|
| 437 |
+
<span className="w-1.5 h-1.5 bg-[#ff4c00] rounded-full" />
|
| 438 |
+
Web search on
|
| 439 |
</span>
|
| 440 |
)}
|
| 441 |
+
<span>Enter to send · Shift+Enter for newline</span>
|
| 442 |
</div>
|
| 443 |
</form>
|
| 444 |
</div>
|
| 445 |
+
</footer>
|
| 446 |
</div>
|
| 447 |
);
|
| 448 |
}
|
src/components/markdown.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
import ReactMarkdown from "react-markdown";
|
|
|
|
| 4 |
import { cn } from "@/lib/utils";
|
| 5 |
|
| 6 |
interface MarkdownProps {
|
|
@@ -11,48 +12,46 @@ interface MarkdownProps {
|
|
| 11 |
export function Markdown({ children, className }: MarkdownProps) {
|
| 12 |
return (
|
| 13 |
<ReactMarkdown
|
| 14 |
-
className={cn("
|
|
|
|
| 15 |
components={{
|
| 16 |
pre: ({ children }) => (
|
| 17 |
-
<pre className="
|
| 18 |
-
{children}
|
| 19 |
-
</pre>
|
| 20 |
),
|
| 21 |
code: ({ children, className: codeClassName }) => {
|
| 22 |
const isInline = !codeClassName;
|
| 23 |
if (isInline) {
|
| 24 |
-
return
|
| 25 |
-
<code className="bg-muted/70 px-1.5 py-0.5 rounded-md text-sm font-mono">
|
| 26 |
-
{children}
|
| 27 |
-
</code>
|
| 28 |
-
);
|
| 29 |
}
|
| 30 |
return <code className={codeClassName}>{children}</code>;
|
| 31 |
},
|
| 32 |
-
p: ({ children }) => <p
|
| 33 |
-
ul: ({ children }) => <ul
|
| 34 |
-
ol: ({ children }) => <ol
|
| 35 |
-
li: ({ children }) => <li
|
| 36 |
-
h1: ({ children }) => <h1
|
| 37 |
-
h2: ({ children }) => <h2
|
| 38 |
-
h3: ({ children }) => <h3
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
{children}
|
| 42 |
-
</blockquote>
|
| 43 |
-
),
|
| 44 |
a: ({ children, href }) => (
|
| 45 |
-
<a
|
| 46 |
-
href={href}
|
| 47 |
-
target="_blank"
|
| 48 |
-
rel="noopener noreferrer"
|
| 49 |
-
className="text-primary hover:underline underline-offset-2"
|
| 50 |
-
>
|
| 51 |
{children}
|
| 52 |
</a>
|
| 53 |
),
|
| 54 |
-
strong: ({ children }) => <strong
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
}}
|
| 57 |
>
|
| 58 |
{children}
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
import ReactMarkdown from "react-markdown";
|
| 4 |
+
import remarkGfm from "remark-gfm";
|
| 5 |
import { cn } from "@/lib/utils";
|
| 6 |
|
| 7 |
interface MarkdownProps {
|
|
|
|
| 12 |
export function Markdown({ children, className }: MarkdownProps) {
|
| 13 |
return (
|
| 14 |
<ReactMarkdown
|
| 15 |
+
className={cn("markdown-content", className)}
|
| 16 |
+
remarkPlugins={[remarkGfm]}
|
| 17 |
components={{
|
| 18 |
pre: ({ children }) => (
|
| 19 |
+
<pre className="code-block">{children}</pre>
|
|
|
|
|
|
|
| 20 |
),
|
| 21 |
code: ({ children, className: codeClassName }) => {
|
| 22 |
const isInline = !codeClassName;
|
| 23 |
if (isInline) {
|
| 24 |
+
return <code className="inline-code">{children}</code>;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
}
|
| 26 |
return <code className={codeClassName}>{children}</code>;
|
| 27 |
},
|
| 28 |
+
p: ({ children }) => <p>{children}</p>,
|
| 29 |
+
ul: ({ children }) => <ul>{children}</ul>,
|
| 30 |
+
ol: ({ children }) => <ol>{children}</ol>,
|
| 31 |
+
li: ({ children }) => <li>{children}</li>,
|
| 32 |
+
h1: ({ children }) => <h1>{children}</h1>,
|
| 33 |
+
h2: ({ children }) => <h2>{children}</h2>,
|
| 34 |
+
h3: ({ children }) => <h3>{children}</h3>,
|
| 35 |
+
h4: ({ children }) => <h4>{children}</h4>,
|
| 36 |
+
blockquote: ({ children }) => <blockquote>{children}</blockquote>,
|
|
|
|
|
|
|
|
|
|
| 37 |
a: ({ children, href }) => (
|
| 38 |
+
<a href={href} target="_blank" rel="noopener noreferrer">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
{children}
|
| 40 |
</a>
|
| 41 |
),
|
| 42 |
+
strong: ({ children }) => <strong>{children}</strong>,
|
| 43 |
+
em: ({ children }) => <em>{children}</em>,
|
| 44 |
+
hr: () => <hr />,
|
| 45 |
+
table: ({ children }) => (
|
| 46 |
+
<div className="table-wrapper">
|
| 47 |
+
<table>{children}</table>
|
| 48 |
+
</div>
|
| 49 |
+
),
|
| 50 |
+
thead: ({ children }) => <thead>{children}</thead>,
|
| 51 |
+
tbody: ({ children }) => <tbody>{children}</tbody>,
|
| 52 |
+
tr: ({ children }) => <tr>{children}</tr>,
|
| 53 |
+
th: ({ children }) => <th>{children}</th>,
|
| 54 |
+
td: ({ children }) => <td>{children}</td>,
|
| 55 |
}}
|
| 56 |
>
|
| 57 |
{children}
|
teich.png
ADDED
|
Git LFS Details
|