Merge pull request #13 from TGlide/syntax-highlighting

WIP syntax highlighting
This commit is contained in:
Thomas G. Lopes 2025-06-17 18:22:33 +01:00 committed by GitHub
commit fd07c1bf79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 874 additions and 55 deletions

View file

@ -22,6 +22,9 @@
"@eslint/js": "^9.18.0", "@eslint/js": "^9.18.0",
"@iconify/json": "^2.2.349", "@iconify/json": "^2.2.349",
"@playwright/test": "^1.49.1", "@playwright/test": "^1.49.1",
"@shikijs/langs": "^3.6.0",
"@shikijs/markdown-it": "^3.6.0",
"@shikijs/themes": "^3.6.0",
"@sveltejs/adapter-auto": "^6.0.0", "@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/kit": "^2.16.0", "@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0", "@sveltejs/vite-plugin-svelte": "^5.0.0",
@ -46,6 +49,7 @@
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.11", "prettier-plugin-tailwindcss": "^0.6.11",
"runed": "^0.28.0", "runed": "^0.28.0",
"shiki": "^3.6.0",
"svelte": "^5.0.0", "svelte": "^5.0.0",
"svelte-check": "^4.0.0", "svelte-check": "^4.0.0",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
@ -74,6 +78,7 @@
"@fontsource-variable/open-sans": "^5.2.6", "@fontsource-variable/open-sans": "^5.2.6",
"better-auth": "^1.2.9", "better-auth": "^1.2.9",
"convex-helpers": "^0.1.94", "convex-helpers": "^0.1.94",
"markdown-it-async": "^2.2.0",
"openai": "^5.3.0", "openai": "^5.3.0",
"zod": "^3.25.64" "zod": "^3.25.64"
} }

408
pnpm-lock.yaml generated
View file

@ -38,6 +38,9 @@ importers:
convex-helpers: convex-helpers:
specifier: ^0.1.94 specifier: ^0.1.94
version: 0.1.94(convex@1.24.8)(typescript@5.8.3)(zod@3.25.64) version: 0.1.94(convex@1.24.8)(typescript@5.8.3)(zod@3.25.64)
markdown-it-async:
specifier: ^2.2.0
version: 2.2.0
openai: openai:
specifier: ^5.3.0 specifier: ^5.3.0
version: 5.3.0(ws@8.18.2)(zod@3.25.64) version: 5.3.0(ws@8.18.2)(zod@3.25.64)
@ -60,6 +63,15 @@ importers:
'@playwright/test': '@playwright/test':
specifier: ^1.49.1 specifier: ^1.49.1
version: 1.53.0 version: 1.53.0
'@shikijs/langs':
specifier: ^3.6.0
version: 3.6.0
'@shikijs/markdown-it':
specifier: ^3.6.0
version: 3.6.0(markdown-it-async@2.2.0)
'@shikijs/themes':
specifier: ^3.6.0
version: 3.6.0
'@sveltejs/adapter-auto': '@sveltejs/adapter-auto':
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.1(@sveltejs/kit@2.21.5(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.34.1)(vite@6.3.5(@types/node@24.0.1)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.34.1)(vite@6.3.5(@types/node@24.0.1)(jiti@2.4.2)(lightningcss@1.30.1))) version: 6.0.1(@sveltejs/kit@2.21.5(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.34.1)(vite@6.3.5(@types/node@24.0.1)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.34.1)(vite@6.3.5(@types/node@24.0.1)(jiti@2.4.2)(lightningcss@1.30.1)))
@ -132,6 +144,9 @@ importers:
runed: runed:
specifier: ^0.28.0 specifier: ^0.28.0
version: 0.28.0(svelte@5.34.1) version: 0.28.0(svelte@5.34.1)
shiki:
specifier: ^3.6.0
version: 3.6.0
svelte: svelte:
specifier: ^5.0.0 specifier: ^5.0.0
version: 5.34.1 version: 5.34.1
@ -818,6 +833,35 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@shikijs/core@3.6.0':
resolution: {integrity: sha512-9By7Xb3olEX0o6UeJyPLI1PE1scC4d3wcVepvtv2xbuN9/IThYN4Wcwh24rcFeASzPam11MCq8yQpwwzCgSBRw==}
'@shikijs/engine-javascript@3.6.0':
resolution: {integrity: sha512-7YnLhZG/TU05IHMG14QaLvTW/9WiK8SEYafceccHUSXs2Qr5vJibUwsDfXDLmRi0zHdzsxrGKpSX6hnqe0k8nA==}
'@shikijs/engine-oniguruma@3.6.0':
resolution: {integrity: sha512-nmOhIZ9yT3Grd+2plmW/d8+vZ2pcQmo/UnVwXMUXAKTXdi+LK0S08Ancrz5tQQPkxvjBalpMW2aKvwXfelauvA==}
'@shikijs/langs@3.6.0':
resolution: {integrity: sha512-IdZkQJaLBu1LCYCwkr30hNuSDfllOT8RWYVZK1tD2J03DkiagYKRxj/pDSl8Didml3xxuyzUjgtioInwEQM/TA==}
'@shikijs/markdown-it@3.6.0':
resolution: {integrity: sha512-OFJb0EY1GOfWEpeXyfav4mDXt4QjqURwhQLTYaBeWP4QjBUUYIdPri+Jf8Fgkds+i4I6WcmX47Wu9vAq8USZsA==}
peerDependencies:
markdown-it-async: ^2.2.0
peerDependenciesMeta:
markdown-it-async:
optional: true
'@shikijs/themes@3.6.0':
resolution: {integrity: sha512-Fq2j4nWr1DF4drvmhqKq8x5vVQ27VncF8XZMBuHuQMZvUSS3NBgpqfwz/FoGe36+W6PvniZ1yDlg2d4kmYDU6w==}
'@shikijs/types@3.6.0':
resolution: {integrity: sha512-cLWFiToxYu0aAzJqhXTQsFiJRTFDAGl93IrMSBNaGSzs7ixkLfdG6pH11HipuWFGW5vyx4X47W8HDQ7eSrmBUg==}
'@shikijs/vscode-textmate@10.0.2':
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
'@simplewebauthn/browser@13.1.0': '@simplewebauthn/browser@13.1.0':
resolution: {integrity: sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==} resolution: {integrity: sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==}
@ -991,12 +1035,30 @@ packages:
'@types/estree@1.0.8': '@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
'@types/json-schema@7.0.15': '@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/linkify-it@5.0.0':
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
'@types/markdown-it@14.1.2':
resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
'@types/mdast@4.0.4':
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
'@types/mdurl@2.0.0':
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
'@types/node@24.0.1': '@types/node@24.0.1':
resolution: {integrity: sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==} resolution: {integrity: sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==}
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
'@typescript-eslint/eslint-plugin@8.34.0': '@typescript-eslint/eslint-plugin@8.34.0':
resolution: {integrity: sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==} resolution: {integrity: sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -1056,6 +1118,9 @@ packages:
resolution: {integrity: sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==} resolution: {integrity: sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
'@vercel/functions@2.2.0': '@vercel/functions@2.2.0':
resolution: {integrity: sha512-x1Zrc2jOclTSB9+Ic/XNMDinO0SG4ZS5YeV2Xz1m/tuJOM7QtPVU3Epw2czBao0dukefmC8HCNpyUL8ZchJ/Tg==} resolution: {integrity: sha512-x1Zrc2jOclTSB9+Ic/XNMDinO0SG4ZS5YeV2Xz1m/tuJOM7QtPVU3Epw2czBao0dukefmC8HCNpyUL8ZchJ/Tg==}
engines: {node: '>= 18'} engines: {node: '>= 18'}
@ -1176,6 +1241,9 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
chai@5.2.0: chai@5.2.0:
resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -1188,6 +1256,12 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'} engines: {node: '>=10'}
character-entities-html4@2.1.0:
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
character-entities-legacy@3.0.0:
resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
check-error@2.1.1: check-error@2.1.1:
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
engines: {node: '>= 16'} engines: {node: '>= 16'}
@ -1215,6 +1289,9 @@ packages:
color-name@1.1.4: color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
comma-separated-tokens@2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
concat-map@0.0.1: concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@ -1334,6 +1411,9 @@ packages:
devalue@5.1.1: devalue@5.1.1:
resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==}
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
diff-sequences@29.6.3: diff-sequences@29.6.3:
resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -1355,6 +1435,10 @@ packages:
resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
entities@6.0.1: entities@6.0.1:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
@ -1541,10 +1625,19 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
hast-util-to-html@9.0.5:
resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
hast-util-whitespace@3.0.0:
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
html-encoding-sniffer@4.0.0: html-encoding-sniffer@4.0.0:
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
http-proxy-agent@7.0.2: http-proxy-agent@7.0.2:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
@ -1749,6 +1842,9 @@ packages:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
local-pkg@1.1.1: local-pkg@1.1.1:
resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==} resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==}
engines: {node: '>=14'} engines: {node: '>=14'}
@ -1779,6 +1875,19 @@ packages:
magic-string@0.30.17: magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
markdown-it-async@2.2.0:
resolution: {integrity: sha512-sITME+kf799vMeO/ww/CjH6q+c05f6TLpn6VOmmWCGNqPJzSh+uFgZoMB9s0plNtW6afy63qglNAC3MhrhP/gg==}
markdown-it@14.1.0:
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
hasBin: true
mdast-util-to-hast@13.2.0:
resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
mdurl@2.0.0:
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
melt@0.35.0: melt@0.35.0:
resolution: {integrity: sha512-Y8/ImbAN83rf11AaqtQ6u9DXcZ2/Ozu6HM7/oV0BZUt0Dh8Q/j06TKyyBB3u65FRsZDJBuxiQHPklCW7dQcfLw==} resolution: {integrity: sha512-Y8/ImbAN83rf11AaqtQ6u9DXcZ2/Ozu6HM7/oV0BZUt0Dh8Q/j06TKyyBB3u65FRsZDJBuxiQHPklCW7dQcfLw==}
peerDependencies: peerDependencies:
@ -1789,6 +1898,21 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
micromark-util-character@2.1.1:
resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
micromark-util-encode@2.0.1:
resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}
micromark-util-sanitize-uri@2.0.1:
resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}
micromark-util-symbol@2.0.1:
resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}
micromark-util-types@2.0.2:
resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==}
micromatch@4.0.8: micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
@ -1860,6 +1984,12 @@ packages:
nwsapi@2.2.20: nwsapi@2.2.20:
resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==} resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==}
oniguruma-parser@0.12.1:
resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==}
oniguruma-to-es@4.3.3:
resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==}
openai@5.3.0: openai@5.3.0:
resolution: {integrity: sha512-VIKmoF7y4oJCDOwP/oHXGzM69+x0dpGFmN9QmYO+uPbLFOmmnwO+x1GbsgUtI+6oraxomGZ566Y421oYVu191w==} resolution: {integrity: sha512-VIKmoF7y4oJCDOwP/oHXGzM69+x0dpGFmN9QmYO+uPbLFOmmnwO+x1GbsgUtI+6oraxomGZ566Y421oYVu191w==}
hasBin: true hasBin: true
@ -2049,6 +2179,13 @@ packages:
resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
property-information@7.1.0:
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
punycode.js@2.3.1:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
engines: {node: '>=6'}
punycode@2.3.1: punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -2080,6 +2217,15 @@ packages:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'} engines: {node: '>=8'}
regex-recursion@6.0.2:
resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
regex-utilities@2.3.0:
resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==}
regex@6.0.1:
resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==}
require-directory@2.1.1: require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2155,6 +2301,9 @@ packages:
resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
shiki@3.6.0:
resolution: {integrity: sha512-tKn/Y0MGBTffQoklaATXmTqDU02zx8NYBGQ+F6gy87/YjKbizcLd+Cybh/0ZtOBX9r1NEnAy/GTRDKtOsc1L9w==}
siginfo@2.0.0: siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
@ -2166,6 +2315,9 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
stackback@0.0.2: stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
@ -2176,6 +2328,9 @@ packages:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'} engines: {node: '>=8'}
stringify-entities@4.0.4:
resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
strip-ansi@6.0.1: strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -2307,6 +2462,9 @@ packages:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true hasBin: true
trim-lines@3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
ts-api-utils@2.1.0: ts-api-utils@2.1.0:
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
engines: {node: '>=18.12'} engines: {node: '>=18.12'}
@ -2332,6 +2490,9 @@ packages:
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
ufo@1.6.1: ufo@1.6.1:
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
@ -2341,6 +2502,21 @@ packages:
undici-types@7.8.0: undici-types@7.8.0:
resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==}
unist-util-is@6.0.0:
resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
unist-util-position@5.0.0:
resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
unist-util-stringify-position@4.0.0:
resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
unist-util-visit-parents@6.0.1:
resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
unist-util-visit@5.0.0:
resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
unplugin-icons@22.1.0: unplugin-icons@22.1.0:
resolution: {integrity: sha512-ect2ZNtk1Zgwb0NVHd0C1IDW/MV+Jk/xaq4t8o6rYdVS3+L660ZdD5kTSQZvsgdwCvquRw+/wYn75hsweRjoIA==} resolution: {integrity: sha512-ect2ZNtk1Zgwb0NVHd0C1IDW/MV+Jk/xaq4t8o6rYdVS3+L660ZdD5kTSQZvsgdwCvquRw+/wYn75hsweRjoIA==}
peerDependencies: peerDependencies:
@ -2374,6 +2550,12 @@ packages:
util-deprecate@1.0.2: util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
vfile-message@4.0.2:
resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
vite-node@3.2.3: vite-node@3.2.3:
resolution: {integrity: sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==} resolution: {integrity: sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@ -2545,6 +2727,9 @@ packages:
zod@3.25.64: zod@3.25.64:
resolution: {integrity: sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==} resolution: {integrity: sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==}
zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
snapshots: snapshots:
'@adobe/css-tools@4.4.3': {} '@adobe/css-tools@4.4.3': {}
@ -3017,6 +3202,46 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.43.0': '@rollup/rollup-win32-x64-msvc@4.43.0':
optional: true optional: true
'@shikijs/core@3.6.0':
dependencies:
'@shikijs/types': 3.6.0
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
hast-util-to-html: 9.0.5
'@shikijs/engine-javascript@3.6.0':
dependencies:
'@shikijs/types': 3.6.0
'@shikijs/vscode-textmate': 10.0.2
oniguruma-to-es: 4.3.3
'@shikijs/engine-oniguruma@3.6.0':
dependencies:
'@shikijs/types': 3.6.0
'@shikijs/vscode-textmate': 10.0.2
'@shikijs/langs@3.6.0':
dependencies:
'@shikijs/types': 3.6.0
'@shikijs/markdown-it@3.6.0(markdown-it-async@2.2.0)':
dependencies:
markdown-it: 14.1.0
shiki: 3.6.0
optionalDependencies:
markdown-it-async: 2.2.0
'@shikijs/themes@3.6.0':
dependencies:
'@shikijs/types': 3.6.0
'@shikijs/types@3.6.0':
dependencies:
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
'@shikijs/vscode-textmate@10.0.2': {}
'@simplewebauthn/browser@13.1.0': {} '@simplewebauthn/browser@13.1.0': {}
'@simplewebauthn/server@13.1.1': '@simplewebauthn/server@13.1.1':
@ -3194,13 +3419,32 @@ snapshots:
'@types/estree@1.0.8': {} '@types/estree@1.0.8': {}
'@types/hast@3.0.4':
dependencies:
'@types/unist': 3.0.3
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
'@types/linkify-it@5.0.0': {}
'@types/markdown-it@14.1.2':
dependencies:
'@types/linkify-it': 5.0.0
'@types/mdurl': 2.0.0
'@types/mdast@4.0.4':
dependencies:
'@types/unist': 3.0.3
'@types/mdurl@2.0.0': {}
'@types/node@24.0.1': '@types/node@24.0.1':
dependencies: dependencies:
undici-types: 7.8.0 undici-types: 7.8.0
optional: true optional: true
'@types/unist@3.0.3': {}
'@typescript-eslint/eslint-plugin@8.34.0(@typescript-eslint/parser@8.34.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': '@typescript-eslint/eslint-plugin@8.34.0(@typescript-eslint/parser@8.34.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)':
dependencies: dependencies:
'@eslint-community/regexpp': 4.12.1 '@eslint-community/regexpp': 4.12.1
@ -3293,6 +3537,8 @@ snapshots:
'@typescript-eslint/types': 8.34.0 '@typescript-eslint/types': 8.34.0
eslint-visitor-keys: 4.2.1 eslint-visitor-keys: 4.2.1
'@ungap/structured-clone@1.3.0': {}
'@vercel/functions@2.2.0': {} '@vercel/functions@2.2.0': {}
'@vitest/expect@3.2.3': '@vitest/expect@3.2.3':
@ -3421,6 +3667,8 @@ snapshots:
callsites@3.1.0: {} callsites@3.1.0: {}
ccount@2.0.1: {}
chai@5.2.0: chai@5.2.0:
dependencies: dependencies:
assertion-error: 2.0.1 assertion-error: 2.0.1
@ -3439,6 +3687,10 @@ snapshots:
ansi-styles: 4.3.0 ansi-styles: 4.3.0
supports-color: 7.2.0 supports-color: 7.2.0
character-entities-html4@2.1.0: {}
character-entities-legacy@3.0.0: {}
check-error@2.1.1: {} check-error@2.1.1: {}
chokidar@4.0.3: chokidar@4.0.3:
@ -3461,6 +3713,8 @@ snapshots:
color-name@1.1.4: {} color-name@1.1.4: {}
comma-separated-tokens@2.0.3: {}
concat-map@0.0.1: {} concat-map@0.0.1: {}
concurrently@9.1.2: concurrently@9.1.2:
@ -3537,6 +3791,10 @@ snapshots:
devalue@5.1.1: {} devalue@5.1.1: {}
devlop@1.1.0:
dependencies:
dequal: 2.0.3
diff-sequences@29.6.3: {} diff-sequences@29.6.3: {}
dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.5.16: {}
@ -3552,6 +3810,8 @@ snapshots:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
tapable: 2.2.2 tapable: 2.2.2
entities@4.5.0: {}
entities@6.0.1: {} entities@6.0.1: {}
es-module-lexer@1.7.0: {} es-module-lexer@1.7.0: {}
@ -3791,10 +4051,30 @@ snapshots:
has-flag@4.0.0: {} has-flag@4.0.0: {}
hast-util-to-html@9.0.5:
dependencies:
'@types/hast': 3.0.4
'@types/unist': 3.0.3
ccount: 2.0.1
comma-separated-tokens: 2.0.3
hast-util-whitespace: 3.0.0
html-void-elements: 3.0.0
mdast-util-to-hast: 13.2.0
property-information: 7.1.0
space-separated-tokens: 2.0.2
stringify-entities: 4.0.4
zwitch: 2.0.4
hast-util-whitespace@3.0.0:
dependencies:
'@types/hast': 3.0.4
html-encoding-sniffer@4.0.0: html-encoding-sniffer@4.0.0:
dependencies: dependencies:
whatwg-encoding: 3.1.1 whatwg-encoding: 3.1.1
html-void-elements@3.0.0: {}
http-proxy-agent@7.0.2: http-proxy-agent@7.0.2:
dependencies: dependencies:
agent-base: 7.1.3 agent-base: 7.1.3
@ -3980,6 +4260,10 @@ snapshots:
lilconfig@2.1.0: {} lilconfig@2.1.0: {}
linkify-it@5.0.0:
dependencies:
uc.micro: 2.1.0
local-pkg@1.1.1: local-pkg@1.1.1:
dependencies: dependencies:
mlly: 1.7.4 mlly: 1.7.4
@ -4006,6 +4290,34 @@ snapshots:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0
markdown-it-async@2.2.0:
dependencies:
'@types/markdown-it': 14.1.2
markdown-it: 14.1.0
markdown-it@14.1.0:
dependencies:
argparse: 2.0.1
entities: 4.5.0
linkify-it: 5.0.0
mdurl: 2.0.0
punycode.js: 2.3.1
uc.micro: 2.1.0
mdast-util-to-hast@13.2.0:
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
'@ungap/structured-clone': 1.3.0
devlop: 1.1.0
micromark-util-sanitize-uri: 2.0.1
trim-lines: 3.0.1
unist-util-position: 5.0.0
unist-util-visit: 5.0.0
vfile: 6.0.3
mdurl@2.0.0: {}
melt@0.35.0(@floating-ui/dom@1.7.1)(svelte@5.34.1): melt@0.35.0(@floating-ui/dom@1.7.1)(svelte@5.34.1):
dependencies: dependencies:
'@floating-ui/dom': 1.7.1 '@floating-ui/dom': 1.7.1
@ -4017,6 +4329,23 @@ snapshots:
merge2@1.4.1: {} merge2@1.4.1: {}
micromark-util-character@2.1.1:
dependencies:
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-util-encode@2.0.1: {}
micromark-util-sanitize-uri@2.0.1:
dependencies:
micromark-util-character: 2.1.1
micromark-util-encode: 2.0.1
micromark-util-symbol: 2.0.1
micromark-util-symbol@2.0.1: {}
micromark-util-types@2.0.2: {}
micromatch@4.0.8: micromatch@4.0.8:
dependencies: dependencies:
braces: 3.0.3 braces: 3.0.3
@ -4073,6 +4402,14 @@ snapshots:
nwsapi@2.2.20: {} nwsapi@2.2.20: {}
oniguruma-parser@0.12.1: {}
oniguruma-to-es@4.3.3:
dependencies:
oniguruma-parser: 0.12.1
regex: 6.0.1
regex-recursion: 6.0.2
openai@5.3.0(ws@8.18.2)(zod@3.25.64): openai@5.3.0(ws@8.18.2)(zod@3.25.64):
optionalDependencies: optionalDependencies:
ws: 8.18.2 ws: 8.18.2
@ -4194,6 +4531,10 @@ snapshots:
ansi-styles: 5.2.0 ansi-styles: 5.2.0
react-is: 18.3.1 react-is: 18.3.1
property-information@7.1.0: {}
punycode.js@2.3.1: {}
punycode@2.3.1: {} punycode@2.3.1: {}
pvtsutils@1.3.6: pvtsutils@1.3.6:
@ -4217,6 +4558,16 @@ snapshots:
indent-string: 4.0.0 indent-string: 4.0.0
strip-indent: 3.0.0 strip-indent: 3.0.0
regex-recursion@6.0.2:
dependencies:
regex-utilities: 2.3.0
regex-utilities@2.3.0: {}
regex@6.0.1:
dependencies:
regex-utilities: 2.3.0
require-directory@2.1.1: {} require-directory@2.1.1: {}
resolve-from@4.0.0: {} resolve-from@4.0.0: {}
@ -4298,6 +4649,17 @@ snapshots:
shell-quote@1.8.3: {} shell-quote@1.8.3: {}
shiki@3.6.0:
dependencies:
'@shikijs/core': 3.6.0
'@shikijs/engine-javascript': 3.6.0
'@shikijs/engine-oniguruma': 3.6.0
'@shikijs/langs': 3.6.0
'@shikijs/themes': 3.6.0
'@shikijs/types': 3.6.0
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
siginfo@2.0.0: {} siginfo@2.0.0: {}
sirv@3.0.1: sirv@3.0.1:
@ -4308,6 +4670,8 @@ snapshots:
source-map-js@1.2.1: {} source-map-js@1.2.1: {}
space-separated-tokens@2.0.2: {}
stackback@0.0.2: {} stackback@0.0.2: {}
std-env@3.9.0: {} std-env@3.9.0: {}
@ -4318,6 +4682,11 @@ snapshots:
is-fullwidth-code-point: 3.0.0 is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1 strip-ansi: 6.0.1
stringify-entities@4.0.4:
dependencies:
character-entities-html4: 2.1.0
character-entities-legacy: 3.0.0
strip-ansi@6.0.1: strip-ansi@6.0.1:
dependencies: dependencies:
ansi-regex: 5.0.1 ansi-regex: 5.0.1
@ -4454,6 +4823,8 @@ snapshots:
tree-kill@1.2.2: {} tree-kill@1.2.2: {}
trim-lines@3.0.1: {}
ts-api-utils@2.1.0(typescript@5.8.3): ts-api-utils@2.1.0(typescript@5.8.3):
dependencies: dependencies:
typescript: 5.8.3 typescript: 5.8.3
@ -4476,6 +4847,8 @@ snapshots:
typescript@5.8.3: {} typescript@5.8.3: {}
uc.micro@2.1.0: {}
ufo@1.6.1: {} ufo@1.6.1: {}
uncrypto@0.1.3: {} uncrypto@0.1.3: {}
@ -4483,6 +4856,29 @@ snapshots:
undici-types@7.8.0: undici-types@7.8.0:
optional: true optional: true
unist-util-is@6.0.0:
dependencies:
'@types/unist': 3.0.3
unist-util-position@5.0.0:
dependencies:
'@types/unist': 3.0.3
unist-util-stringify-position@4.0.0:
dependencies:
'@types/unist': 3.0.3
unist-util-visit-parents@6.0.1:
dependencies:
'@types/unist': 3.0.3
unist-util-is: 6.0.0
unist-util-visit@5.0.0:
dependencies:
'@types/unist': 3.0.3
unist-util-is: 6.0.0
unist-util-visit-parents: 6.0.1
unplugin-icons@22.1.0(svelte@5.34.1): unplugin-icons@22.1.0(svelte@5.34.1):
dependencies: dependencies:
'@antfu/install-pkg': 1.1.0 '@antfu/install-pkg': 1.1.0
@ -4507,6 +4903,16 @@ snapshots:
util-deprecate@1.0.2: {} util-deprecate@1.0.2: {}
vfile-message@4.0.2:
dependencies:
'@types/unist': 3.0.3
unist-util-stringify-position: 4.0.0
vfile@6.0.3:
dependencies:
'@types/unist': 3.0.3
vfile-message: 4.0.2
vite-node@3.2.3(@types/node@24.0.1)(jiti@2.4.2)(lightningcss@1.30.1): vite-node@3.2.3(@types/node@24.0.1)(jiti@2.4.2)(lightningcss@1.30.1):
dependencies: dependencies:
cac: 6.7.14 cac: 6.7.14
@ -4653,3 +5059,5 @@ snapshots:
zimmerframe@1.1.2: {} zimmerframe@1.1.2: {}
zod@3.25.64: {} zod@3.25.64: {}
zwitch@2.0.4: {}

View file

@ -14,7 +14,7 @@
--card-foreground: oklch(0.3257 0.1161 325.0372); --card-foreground: oklch(0.3257 0.1161 325.0372);
--popover: oklch(1 0 0); --popover: oklch(1 0 0);
--popover-foreground: oklch(0.3257 0.1161 325.0372); --popover-foreground: oklch(0.3257 0.1161 325.0372);
--primary: oklch(0.5316 0.1409 355.1999); --primary: oklch(0.5797 0.1194 237.7893);
--heading: oklch(0.5797 0.1194 237.7893); --heading: oklch(0.5797 0.1194 237.7893);
--primary-foreground: oklch(1 0 0); --primary-foreground: oklch(1 0 0);
--secondary: oklch(0.8696 0.0675 334.8991); --secondary: oklch(0.8696 0.0675 334.8991);

View file

@ -5,6 +5,9 @@
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> = $props(); let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();
</script> </script>
<div class={cn('bg-card flex flex-col gap-4 rounded-lg border p-4', className)} {...restProps}> <div
class={cn('bg-card border-border flex flex-col gap-4 rounded-lg border p-4', className)}
{...restProps}
>
{@render children?.()} {@render children?.()}
</div> </div>

View file

@ -9,10 +9,10 @@
import { Button } from '$lib/components/ui/button/index.js'; import { Button } from '$lib/components/ui/button/index.js';
import type { LightSwitchProps } from './types'; import type { LightSwitchProps } from './types';
let { variant = 'outline' }: LightSwitchProps = $props(); let { variant = 'outline', class: className }: LightSwitchProps = $props();
</script> </script>
<Button onclick={toggleMode} {variant} size="icon"> <Button onclick={toggleMode} {variant} size="icon" class={className}>
<SunIcon class="absolute scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" /> <SunIcon class="absolute scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
<MoonIcon class="scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" /> <MoonIcon class="scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
<span class="sr-only">Toggle theme</span> <span class="sr-only">Toggle theme</span>

View file

@ -4,4 +4,5 @@
export type LightSwitchProps = { export type LightSwitchProps = {
variant?: 'outline' | 'ghost'; variant?: 'outline' | 'ghost';
class?: string;
}; };

View file

@ -0,0 +1,30 @@
import { Context, type Getter, type Setter } from 'runed';
class PromptState {
constructor(
readonly getPrompt: Getter<string>,
readonly setPrompt: Setter<string>
) {}
get current() {
return this.getPrompt();
}
set current(prompt: string) {
this.setPrompt(prompt);
}
}
export const ctx = new Context<PromptState>('prompt-context');
export function usePrompt(getPrompt?: Getter<string>, setPrompt?: Setter<string>) {
try {
return ctx.get();
} catch {
if (!getPrompt || !setPrompt) {
throw new Error('Prompt context not initialized. You must provide getPrompt and setPrompt!');
}
return ctx.set(new PromptState(getPrompt, setPrompt));
}
}

View file

@ -0,0 +1,35 @@
import { fromAsyncCodeToHtml } from '@shikijs/markdown-it/async';
import MarkdownItAsync from 'markdown-it-async';
import type { Getter } from 'runed';
import { codeToHtml } from 'shiki';
const md = MarkdownItAsync();
md.use(
fromAsyncCodeToHtml(
// Pass the codeToHtml function
codeToHtml,
{
themes: {
light: 'github-light-default',
dark: 'github-dark-default',
},
}
)
);
export class Markdown {
highlighted = $state<string | null>(null);
constructor(readonly code: Getter<string>) {
$effect(() => {
md.renderAsync(this.code()).then((html) => {
this.highlighted = html;
});
});
}
get current() {
return this.highlighted;
}
}

180
src/markdown.css Normal file
View file

@ -0,0 +1,180 @@
@import 'tailwindcss';
@reference './app.css';
.shiki,
.shiki span {
/* Optional, if you also want font styles */
font-style: var(--font-mono) !important;
}
html.dark .shiki,
html.dark .shiki span {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
/* Optional, if you also want font styles */
font-style: var(--font-mono) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
.prose {
@apply text-foreground max-w-[65ch];
& h1:not(.not-prose h1) {
@apply scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl;
}
& h2:not(.not-prose h2) {
@apply mt-12 scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0;
}
& h3:not(.not-prose h3) {
@apply mt-12 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0;
}
& h4:not(.not-prose h4) {
@apply mt-12 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0;
}
& h5:not(.not-prose h5) {
@apply mt-12 scroll-m-20 text-lg font-semibold tracking-tight first:mt-0;
}
& h6:not(.not-prose h6) {
@apply mt-12 scroll-m-20 text-base font-semibold tracking-tight first:mt-0;
}
& h1:not(.not-prose h1),
& h2:not(.not-prose h2),
& h3:not(.not-prose h3),
& h4:not(.not-prose h4),
& h5:not(.not-prose h5),
& h6:not(.not-prose h6) {
& + p {
@apply mt-0;
}
}
& p:not(.not-prose p) {
@apply leading-[175%] [&:not(:first-child)]:mt-6;
}
& li:not(.not-prose li) {
@apply leading-[175%];
}
& a:not(.not-prose a) {
@apply text-primary font-medium underline underline-offset-4;
}
& blockquote:not(.not-prose blockquote) {
@apply mt-6 border-l-2 pl-6 italic;
}
& table:not(.not-prose table) {
@apply my-6 w-full overflow-y-auto;
& thead tr {
@apply even:bg-muted m-0 border-t p-0;
}
& th {
@apply border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right;
}
& tbody tr {
@apply even:bg-muted m-0 border-t p-0;
}
& td {
@apply border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right;
}
}
& ul:not(.not-prose ul) {
@apply my-6 ml-6 list-disc [&>li]:mt-2;
& p {
@apply my-0 inline;
}
}
& ol:not(.not-prose ol) {
@apply my-6 ml-6 list-decimal [&>li]:mt-2;
& p {
@apply my-0 inline;
}
}
& pre:not(.not-prose pre) {
@apply bg-muted my-6 overflow-y-auto rounded-md p-4 text-sm;
}
& code:not(pre code):not(.not-prose code) {
@apply bg-muted relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold;
}
& .lead:not(.not-prose .lead) {
@apply text-muted-foreground text-xl;
}
& .large:not(.not-prose .large) {
@apply text-lg font-semibold;
}
& .small:not(.not-prose .small) {
@apply text-sm leading-none font-medium;
}
& .muted:not(.not-prose .muted) {
@apply text-muted-foreground text-sm;
}
& img:not(.not-prose img),
& picture:not(.not-prose picture),
& video:not(.not-prose video) {
@apply my-6;
}
& picture > img:not(.not-prose picture > img) {
@apply my-0;
}
& kbd:not(.not-prose kbd) {
@apply bg-muted rounded-md px-1.5 py-0.5 text-xs font-semibold;
}
& hr {
@apply my-10;
}
& dl:not(.not-prose dl) {
@apply my-6;
& dt {
@apply mt-6 font-semibold tracking-tight first:mt-0;
}
}
& details:not(.not-prose details) {
@apply mt-6;
& summary {
@apply mt-6 cursor-pointer font-semibold tracking-tight;
}
& p {
@apply [&:first-of-type]:mt-2;
}
}
& mark:not(.not-prose mark) {
@apply bg-yellow-300;
}
& small:not(.not-prose small) {
@apply text-xs leading-none;
}
}

View file

@ -83,7 +83,7 @@
</div> </div>
<div class="md:col-start-2 md:pl-12"> <div class="md:col-start-2 md:pl-12">
<div <div
class="bg-card scrollbar-hide text-muted-foreground flex w-fit max-w-full place-items-center gap-2 overflow-x-auto rounded-lg p-1 text-sm" class="bg-accent scrollbar-hide text-muted-foreground flex w-fit max-w-full place-items-center gap-2 overflow-x-auto rounded-lg p-1 text-sm"
> >
{#each navigation as tab (tab)} {#each navigation as tab (tab)}
<a <a

View file

View file

@ -237,7 +237,7 @@ async function generateAIResponse({
const systemMessage: ChatCompletionSystemMessageParam = { const systemMessage: ChatCompletionSystemMessageParam = {
role: 'system', role: 'system',
content: `The user may have mentioned one or more rules to follow with the @<rule_name> syntax. Please follow these rules. content: `Respond in markdown format. The user may have mentioned one or more rules to follow with the @<rule_name> syntax. Please follow these rules.
Rules to follow: Rules to follow:
${attachedRules.map((r) => `- ${r.name}: ${r.rule}`).join('\n')}`, ${attachedRules.map((r) => `- ${r.name}: ${r.rule}`).join('\n')}`,
}; };
@ -251,7 +251,6 @@ ${attachedRules.map((r) => `- ${r.name}: ${r.rule}`).join('\n')}`,
openai.chat.completions.create({ openai.chat.completions.create({
model: model.model_id, model: model.model_id,
messages: [...messages.map((m) => ({ role: m.role, content: m.content })), systemMessage], messages: [...messages.map((m) => ({ role: m.role, content: m.content })), systemMessage],
max_tokens: 1000,
temperature: 0.7, temperature: 0.7,
stream: true, stream: true,
}), }),

View file

@ -23,10 +23,14 @@
import { Popover } from 'melt/builders'; import { Popover } from 'melt/builders';
import { useConvexClient } from 'convex-svelte'; import { useConvexClient } from 'convex-svelte';
import { callModal } from '$lib/components/ui/modal/global-modal.svelte'; import { callModal } from '$lib/components/ui/modal/global-modal.svelte';
import { ElementSize } from 'runed'; import { ElementSize, ScrollState, Debounced, IsMounted } from 'runed';
import LoaderCircleIcon from '~icons/lucide/loader-circle'; import LoaderCircleIcon from '~icons/lucide/loader-circle';
import { cn } from '$lib/utils/utils.js'; import { cn } from '$lib/utils/utils.js';
import { pick } from '$lib/utils/object.js'; import { pick } from '$lib/utils/object.js';
import ChevronDownIcon from '~icons/lucide/chevron-down';
import { LightSwitch } from '$lib/components/ui/light-switch/index.js';
import Settings2Icon from '~icons/lucide/settings-2';
import { usePrompt } from '$lib/state/prompt.svelte.js';
const client = useConvexClient(); const client = useConvexClient();
@ -155,6 +159,11 @@
let message = $state(''); let message = $state('');
usePrompt(
() => message,
(v) => (message = v)
);
const suggestedRules = $derived.by(() => { const suggestedRules = $derived.by(() => {
if (!rulesQuery.data || rulesQuery.data.length === 0) return; if (!rulesQuery.data || rulesQuery.data.length === 0) return;
if (!textarea) return; if (!textarea) return;
@ -259,6 +268,18 @@
let textareaWrapper = $state<HTMLDivElement>(); let textareaWrapper = $state<HTMLDivElement>();
const wrapperSize = new ElementSize(() => textareaWrapper); const wrapperSize = new ElementSize(() => textareaWrapper);
let conversationList = $state<HTMLDivElement>();
const scrollState = new ScrollState({
element: () => conversationList,
});
const mounted = new IsMounted();
const notAtBottom = new Debounced(
() => !scrollState.arrived.bottom,
() => (mounted.current ? 500 : 0)
);
</script> </script>
<svelte:head> <svelte:head>
@ -374,7 +395,7 @@
</div> </div>
<div class="py-2"> <div class="py-2">
{#if data.session !== null} {#if data.session !== null}
<Button href="/account/api-keys" variant="ghost" class="h-auto w-full justify-start"> <Button href="/account" variant="ghost" class="h-auto w-full justify-start">
<Avatar src={data.session?.user.image ?? undefined}> <Avatar src={data.session?.user.image ?? undefined}>
{#snippet children(avatar)} {#snippet children(avatar)}
<img {...avatar.image} alt="Your avatar" class="size-10 rounded-full" /> <img {...avatar.image} alt="Your avatar" class="size-10 rounded-full" />
@ -398,17 +419,36 @@
</Sidebar.Sidebar> </Sidebar.Sidebar>
<Sidebar.Inset class="w-full overflow-clip "> <Sidebar.Inset class="w-full overflow-clip ">
<Sidebar.Trigger class="fixed top-3 left-2"> <Sidebar.Trigger class="fixed top-3 left-2 z-50">
<PanelLeftIcon /> <PanelLeftIcon />
</Sidebar.Trigger> </Sidebar.Trigger>
<!-- header -->
<div class="bg-sidebar fixed top-0 right-0 z-50 hidden rounded-bl-lg p-1 md:flex">
<Button variant="ghost" size="icon" class="size-8" href="/account">
<Settings2Icon />
</Button>
<LightSwitch variant="ghost" class="size-8" />
</div>
<div class="relative"> <div class="relative">
<div class="h-screen overflow-y-auto"> <div bind:this={conversationList} class="h-screen overflow-y-auto">
<div <div
class="mx-auto flex max-w-3xl flex-col" class="mx-auto flex max-w-3xl flex-col"
style:padding-bottom={wrapperSize.height + 'px'} style="padding-bottom: {page.url.pathname !== '/chat' ? wrapperSize.height : 0}px"
> >
{@render children()} {@render children()}
</div> </div>
{#if notAtBottom.current}
<Button
onclick={() => scrollState.scrollToBottom()}
variant="secondary"
size="sm"
class="text-muted-foreground absolute bottom-0 left-1/2 z-10 -translate-x-1/2 rounded-full text-xs"
style="bottom: {wrapperSize.height + 5}px;"
>
Scroll to bottom
<ChevronDownIcon class="inline" />
</Button>
{/if}
</div> </div>
<div <div
@ -525,11 +565,14 @@
</div> </div>
<!-- Credits in bottom-right, only on large screens --> <!-- Credits in bottom-right, only on large screens -->
<div class="fixed right-4 bottom-4 hidden flex-col items-end gap-1 lg:flex"> <div class="fixed right-4 bottom-4 hidden flex-col items-end gap-1 xl:flex">
<a href="https://github.com/TGlide/thom-chat" class="text-muted-foreground text-xs"> <a
Source on <Icons.GitHub class="ml-0.5 inline size-3" /> href="https://github.com/TGlide/thom-chat"
class="text-muted-foreground flex place-items-center gap-1 text-xs"
>
Source on <Icons.GitHub class="inline size-3" />
</a> </a>
<span class="text-muted-foreground text-xs"> <span class="text-muted-foreground flex place-items-center gap-1 text-xs">
Crafted by <Icons.Svelte class="inline size-3" /> wizards. Crafted by <Icons.Svelte class="inline size-3" /> wizards.
</span> </span>
</div> </div>

View file

@ -1,11 +1,67 @@
<script lang="ts"> <script lang="ts">
import Button from '$lib/components/ui/button/button.svelte';
import { session } from '$lib/state/session.svelte'; import { session } from '$lib/state/session.svelte';
import IconAi from '~icons/lucide/sparkles'; import IconAi from '~icons/lucide/sparkles';
import CodeIcon from '~icons/lucide/code';
import GraduationCapIcon from '~icons/lucide/graduation-cap';
import NewspaperIcon from '~icons/lucide/newspaper';
import { Button } from '$lib/components/ui/button';
import { usePrompt } from '$lib/state/prompt.svelte';
import { fade, scale } from 'svelte/transition';
const defaultSuggestions = [
'Give me bad medical advice, doctor.',
'Explain why Theo hates Svelte.',
'Write a song about losing money.',
'Why am I such a bozo?',
];
const suggestionCategories: Record<string, { icon: typeof IconAi; suggestions: string[] }> = {
Create: {
icon: IconAi,
suggestions: [
'Write a short story about discovering emotions',
'Help me outline a sci-fi novel set in a post-post-apocalyptic world',
'Create a character profile for a complex villain with sympathetic motives',
'Give me 5 creative writing prompts for flash fiction',
],
},
Explore: {
icon: NewspaperIcon,
suggestions: [
'Good books for fans of Rick Rubin',
'Countries ranked by number of corgis',
'Most successful companies in the world',
'How much does Claude cost?',
],
},
Code: {
icon: CodeIcon,
suggestions: [
'Write code to invert a binary search tree in Python',
"What's the difference between Promise.all and Promise.allSettled?",
"Explain React's useEffect cleanup function",
'Best practices for error handling in async/await',
],
},
Learn: {
icon: GraduationCapIcon,
suggestions: [
"Beginner's guide to TypeScript",
'Explain the CAP theorem in distributed systems',
'Why is AI so expensive?',
'Are black holes real?',
],
},
};
let selectedCategory = $state<string | null>(null);
const prompt = usePrompt();
</script> </script>
<div class="flex h-full flex-1 flex-col items-center justify-center"> <div class="flex h-svh flex-col items-center justify-center">
<div class="w-full p-2"> {#if prompt.current.length === 0}
<div class="w-full p-2" in:scale={{ duration: 500, start: 0.9 }}>
<h2 class="text-left font-serif text-3xl font-semibold">Hey there, Bozo!</h2> <h2 class="text-left font-serif text-3xl font-semibold">Hey there, Bozo!</h2>
<p class="mt-2 text-left text-lg"> <p class="mt-2 text-left text-lg">
{#if session.current?.user.name} {#if session.current?.user.name}
@ -15,18 +71,52 @@
{/if} {/if}
</p> </p>
<div class="mt-4 flex items-center gap-1"> <div class="mt-4 flex items-center gap-1">
{#each { length: 4 }} {#each Object.entries(suggestionCategories) as [category, opts]}
<Button variant="outline" class="rounded-full"> <button
<IconAi /> type="button"
Create class="data-[active=true]:bg-primary focus-visible:border-ring focus-visible:ring-ring/50 bg-muted relative inline-flex h-9 shrink-0 items-center justify-center gap-2 overflow-hidden rounded-full px-4 py-2 text-sm font-medium whitespace-nowrap outline-hidden transition-all select-none hover:cursor-pointer focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 has-[>svg]:px-3 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
</Button> data-active={selectedCategory === category}
onclick={() => {
if (selectedCategory === category) {
selectedCategory = null;
} else {
selectedCategory = category;
}
}}
>
<opts.icon />
{category}
</button>
{/each} {/each}
</div> </div>
<ul class="mt-2 flex flex-col gap-2 p-2"> <div class="mt-2 flex flex-col gap-2 p-2">
{#each { length: 3 } as _, i (i)} {#if selectedCategory && suggestionCategories[selectedCategory]}
<li class={['py-2', i !== 2 && 'border-b']}>Hey AI, write me a poem</li> {#each suggestionCategories[selectedCategory]?.suggestions ?? [] as suggestion}
<div class="border-border not-last:border-b not-last:pb-2">
<Button
onclick={() => (prompt.current = suggestion)}
variant="ghost"
class="w-full cursor-pointer justify-start py-2 text-start"
>
{suggestion}
</Button>
</div>
{/each} {/each}
</ul> {:else}
{#each defaultSuggestions as suggestion}
<div class="border-border group not-last:border-b not-last:pb-2">
<Button
onclick={() => (prompt.current = suggestion)}
variant="ghost"
class="w-full cursor-pointer justify-start py-2 text-start group-last:line-through"
>
{suggestion}
</Button>
</div>
{/each}
{/if}
</div> </div>
</div> </div>
{/if}
</div>

View file

@ -0,0 +1,13 @@
<script lang="ts">
import { Markdown } from '$lib/utils/markdown.svelte';
type Props = {
content: string;
};
let { content }: Props = $props();
const markdown = new Markdown(() => content);
</script>
{@html markdown.current}

View file

@ -3,12 +3,14 @@
import { tv } from 'tailwind-variants'; import { tv } from 'tailwind-variants';
import type { Doc } from '$lib/backend/convex/_generated/dataModel'; import type { Doc } from '$lib/backend/convex/_generated/dataModel';
import { CopyButton } from '$lib/components/ui/copy-button'; import { CopyButton } from '$lib/components/ui/copy-button';
import '../../../markdown.css';
import MarkdownRenderer from './markdown-renderer.svelte';
const style = tv({ const style = tv({
base: 'rounded-lg p-2', base: 'prose rounded-lg p-2',
variants: { variants: {
role: { role: {
user: 'bg-primary text-primary-foreground self-end', user: 'bg-primary !text-primary-foreground self-end',
assistant: 'text-foreground', assistant: 'text-foreground',
}, },
}, },
@ -24,7 +26,16 @@
{#if message.role !== 'system' && !(message.role === 'assistant' && message.content.length === 0)} {#if message.role !== 'system' && !(message.role === 'assistant' && message.content.length === 0)}
<div class={cn('group flex max-w-[80%] flex-col gap-1', { 'self-end': message.role === 'user' })}> <div class={cn('group flex max-w-[80%] flex-col gap-1', { 'self-end': message.role === 'user' })}>
<div class={style({ role: message.role })}> <div class={style({ role: message.role })}>
{message.content} <svelte:boundary>
<MarkdownRenderer content={message.content} />
{#snippet failed(error)}
<div class="text-destructive">
<span>Error rendering markdown:</span>
<pre class="!bg-sidebar"><code>{error.message}</code></pre>
</div>
{/snippet}
</svelte:boundary>
</div> </div>
<div <div
class={cn('flex place-items-center opacity-0 transition-opacity group-hover:opacity-100', { class={cn('flex place-items-center opacity-0 transition-opacity group-hover:opacity-100', {

View file

@ -3,6 +3,7 @@
import { useCachedQuery } from '$lib/cache/cached-query.svelte'; import { useCachedQuery } from '$lib/cache/cached-query.svelte';
import { session } from '$lib/state/session.svelte'; import { session } from '$lib/state/session.svelte';
import { settings } from '$lib/state/settings.svelte'; import { settings } from '$lib/state/settings.svelte';
import { cn } from '$lib/utils/utils';
type Props = { type Props = {
class?: string; class?: string;
@ -23,7 +24,7 @@
}); });
</script> </script>
<select bind:value={settings.modelId} class="border {className}"> <select bind:value={settings.modelId} class={cn('border-border border', className)}>
{#each enabledArr as model (model._id)} {#each enabledArr as model (model._id)}
<option value={model.model_id}>{model.model_id}</option> <option value={model.model_id}>{model.model_id}</option>
{/each} {/each}