feat(ui): unified chat styling + composer markdown
Standardize thinking/tool-call block appearance to matching pill style. Switch message composer to emit markdown via tiptap-markdown. Add chat-markdown CSS for discord-compact rendering. Persist sidebar open state via cookie. Fix chat-provider resume dispatching generateUI calls on reload. Use flex layout for channel page to support thread panel.
This commit is contained in:
parent
1523d576b3
commit
5922dd9d3a
17
bun.lock
17
bun.lock
@ -110,6 +110,7 @@
|
|||||||
"streamdown": "^2.1.0",
|
"streamdown": "^2.1.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
|
"tiptap-markdown": "^0.9.0",
|
||||||
"tokenlens": "^1.3.1",
|
"tokenlens": "^1.3.1",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"use-stick-to-bottom": "^1.1.2",
|
"use-stick-to-bottom": "^1.1.2",
|
||||||
@ -1154,13 +1155,13 @@
|
|||||||
|
|
||||||
"@types/koa-compose": ["@types/koa-compose@3.2.9", "", { "dependencies": { "@types/koa": "*" } }, "sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA=="],
|
"@types/koa-compose": ["@types/koa-compose@3.2.9", "", { "dependencies": { "@types/koa": "*" } }, "sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA=="],
|
||||||
|
|
||||||
"@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="],
|
"@types/linkify-it": ["@types/linkify-it@3.0.5", "", {}, "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw=="],
|
||||||
|
|
||||||
"@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="],
|
"@types/markdown-it": ["@types/markdown-it@13.0.9", "", { "dependencies": { "@types/linkify-it": "^3", "@types/mdurl": "^1" } }, "sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw=="],
|
||||||
|
|
||||||
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
|
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
|
||||||
|
|
||||||
"@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="],
|
"@types/mdurl": ["@types/mdurl@1.0.5", "", {}, "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA=="],
|
||||||
|
|
||||||
"@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],
|
"@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],
|
||||||
|
|
||||||
@ -2004,6 +2005,8 @@
|
|||||||
|
|
||||||
"markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="],
|
"markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="],
|
||||||
|
|
||||||
|
"markdown-it-task-lists": ["markdown-it-task-lists@2.1.1", "", {}, "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA=="],
|
||||||
|
|
||||||
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
|
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
|
||||||
|
|
||||||
"marked": ["marked@17.0.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-s5HZGFQea7Huv5zZcAGhJLT3qLpAfnY7v7GWkICUr0+Wd5TFEtdlRR2XUL5Gg+RH7u2Df595ifrxR03mBaw7gA=="],
|
"marked": ["marked@17.0.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-s5HZGFQea7Huv5zZcAGhJLT3qLpAfnY7v7GWkICUr0+Wd5TFEtdlRR2XUL5Gg+RH7u2Df595ifrxR03mBaw7gA=="],
|
||||||
@ -2556,6 +2559,8 @@
|
|||||||
|
|
||||||
"tippy.js": ["tippy.js@6.3.7", "", { "dependencies": { "@popperjs/core": "^2.9.0" } }, "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ=="],
|
"tippy.js": ["tippy.js@6.3.7", "", { "dependencies": { "@popperjs/core": "^2.9.0" } }, "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ=="],
|
||||||
|
|
||||||
|
"tiptap-markdown": ["tiptap-markdown@0.9.0", "", { "dependencies": { "@types/markdown-it": "^13.0.7", "markdown-it": "^14.1.0", "markdown-it-task-lists": "^2.1.1", "prosemirror-markdown": "^1.11.1" }, "peerDependencies": { "@tiptap/core": "^3.0.1" } }, "sha512-dKLQ9iiuGNgrlGVjrNauF/UBzWu4LYOx5pkD0jNkmQt/GOwfCJsBuzZTsf1jZ204ANHOm572mZ9PYvGh1S7tpQ=="],
|
||||||
|
|
||||||
"tldts": ["tldts@7.0.23", "", { "dependencies": { "tldts-core": "^7.0.23" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw=="],
|
"tldts": ["tldts@7.0.23", "", { "dependencies": { "tldts-core": "^7.0.23" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw=="],
|
||||||
|
|
||||||
"tldts-core": ["tldts-core@7.0.23", "", {}, "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ=="],
|
"tldts-core": ["tldts-core@7.0.23", "", {}, "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ=="],
|
||||||
@ -3470,6 +3475,8 @@
|
|||||||
|
|
||||||
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||||
|
|
||||||
|
"prosemirror-markdown/@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="],
|
||||||
|
|
||||||
"radix-ui/@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g=="],
|
"radix-ui/@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g=="],
|
||||||
|
|
||||||
"radix-ui/@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="],
|
"radix-ui/@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="],
|
||||||
@ -4030,6 +4037,10 @@
|
|||||||
|
|
||||||
"node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
"node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||||
|
|
||||||
|
"prosemirror-markdown/@types/markdown-it/@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="],
|
||||||
|
|
||||||
|
"prosemirror-markdown/@types/markdown-it/@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="],
|
||||||
|
|
||||||
"rimraf/glob/minimatch": ["minimatch@10.1.2", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.1" } }, "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw=="],
|
"rimraf/glob/minimatch": ["minimatch@10.1.2", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.1" } }, "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw=="],
|
||||||
|
|
||||||
"vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A=="],
|
"vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A=="],
|
||||||
|
|||||||
@ -137,6 +137,7 @@
|
|||||||
"streamdown": "^2.1.0",
|
"streamdown": "^2.1.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
|
"tiptap-markdown": "^0.9.0",
|
||||||
"tokenlens": "^1.3.1",
|
"tokenlens": "^1.3.1",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"use-stick-to-bottom": "^1.1.2",
|
"use-stick-to-bottom": "^1.1.2",
|
||||||
|
|||||||
@ -25,9 +25,9 @@ export default async function ChannelPage({
|
|||||||
const messages = messagesResult.success && messagesResult.data ? messagesResult.data : []
|
const messages = messagesResult.success && messagesResult.data ? messagesResult.data : []
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex h-full w-full min-w-0 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="grid h-full overflow-hidden"
|
className="grid min-w-0 flex-1 overflow-hidden"
|
||||||
style={{ gridTemplateRows: "auto 1fr auto" }}
|
style={{ gridTemplateRows: "auto 1fr auto" }}
|
||||||
>
|
>
|
||||||
<ChannelHeader
|
<ChannelHeader
|
||||||
@ -46,6 +46,6 @@ export default async function ChannelPage({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ThreadPanel />
|
<ThreadPanel />
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { getProjects } from "@/app/actions/projects"
|
|||||||
import { getCustomDashboards } from "@/app/actions/dashboards"
|
import { getCustomDashboards } from "@/app/actions/dashboards"
|
||||||
import { ProjectListProvider } from "@/components/project-list-provider"
|
import { ProjectListProvider } from "@/components/project-list-provider"
|
||||||
import { getCurrentUser, toSidebarUser } from "@/lib/auth"
|
import { getCurrentUser, toSidebarUser } from "@/lib/auth"
|
||||||
|
import { cookies } from "next/headers"
|
||||||
import { BiometricGuard } from "@/components/native/biometric-guard"
|
import { BiometricGuard } from "@/components/native/biometric-guard"
|
||||||
import { OfflineBanner } from "@/components/native/offline-banner"
|
import { OfflineBanner } from "@/components/native/offline-banner"
|
||||||
import { NativeShell } from "@/components/native/native-shell"
|
import { NativeShell } from "@/components/native/native-shell"
|
||||||
@ -35,12 +36,14 @@ export default async function DashboardLayout({
|
|||||||
}: {
|
}: {
|
||||||
readonly children: React.ReactNode
|
readonly children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const [projectList, authUser, dashboardResult] =
|
const [projectList, authUser, dashboardResult, cookieStore] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
getProjects(),
|
getProjects(),
|
||||||
getCurrentUser(),
|
getCurrentUser(),
|
||||||
getCustomDashboards(),
|
getCustomDashboards(),
|
||||||
|
cookies(),
|
||||||
])
|
])
|
||||||
|
const sidebarOpen = cookieStore.get("sidebar_state")?.value !== "false"
|
||||||
const user = authUser ? toSidebarUser(authUser) : null
|
const user = authUser ? toSidebarUser(authUser) : null
|
||||||
const activeOrgId = authUser?.organizationId ?? null
|
const activeOrgId = authUser?.organizationId ?? null
|
||||||
const activeOrgName = authUser?.organizationName ?? null
|
const activeOrgName = authUser?.organizationName ?? null
|
||||||
@ -61,7 +64,7 @@ export default async function DashboardLayout({
|
|||||||
<FeedbackWidget>
|
<FeedbackWidget>
|
||||||
<DashboardContextMenu>
|
<DashboardContextMenu>
|
||||||
<SidebarProvider
|
<SidebarProvider
|
||||||
defaultOpen={false}
|
defaultOpen={sidebarOpen}
|
||||||
className="h-screen overflow-hidden"
|
className="h-screen overflow-hidden"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
|
|||||||
@ -248,6 +248,73 @@ em-emoji-picker {
|
|||||||
max-height: 350px;
|
max-height: 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* discord-like compact markdown in chat messages */
|
||||||
|
.chat-markdown > div {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.chat-markdown p {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.375;
|
||||||
|
}
|
||||||
|
.chat-markdown p + p {
|
||||||
|
margin-top: 0.25em;
|
||||||
|
}
|
||||||
|
.chat-markdown strong {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.chat-markdown em {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.chat-markdown del, .chat-markdown s {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
.chat-markdown code:not(pre code) {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.85em;
|
||||||
|
background: color-mix(in oklch, var(--muted) 80%, transparent);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 0.1em 0.3em;
|
||||||
|
}
|
||||||
|
.chat-markdown pre {
|
||||||
|
margin: 0.375em 0;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: calc(var(--radius) - 4px);
|
||||||
|
background: color-mix(in oklch, var(--muted) 60%, transparent);
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.chat-markdown pre code {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.85em;
|
||||||
|
display: block;
|
||||||
|
padding: 0.5em 0.75em;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.chat-markdown blockquote {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
padding-left: 0.75em;
|
||||||
|
border-left: 3px solid var(--muted-foreground);
|
||||||
|
}
|
||||||
|
.chat-markdown ul, .chat-markdown ol {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
padding-left: 1.5em;
|
||||||
|
}
|
||||||
|
.chat-markdown li {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.chat-markdown a {
|
||||||
|
color: var(--primary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.chat-markdown a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.chat-markdown hr {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
/* mention pill styling in messages and editor */
|
/* mention pill styling in messages and editor */
|
||||||
.mention {
|
.mention {
|
||||||
border-radius: calc(var(--radius) - 4px);
|
border-radius: calc(var(--radius) - 4px);
|
||||||
|
|||||||
@ -535,6 +535,26 @@ export function ChatProvider({
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// mark all generateUI tool calls from restored messages
|
||||||
|
// as already-dispatched so the watcher doesn't re-trigger
|
||||||
|
// renders or navigate to /dashboard on resume
|
||||||
|
for (const m of restored) {
|
||||||
|
if (m.role !== "assistant") continue
|
||||||
|
const parts = m.parts as ReadonlyArray<unknown>
|
||||||
|
let result = findGenerateUIOutput(
|
||||||
|
parts,
|
||||||
|
renderDispatchedRef.current
|
||||||
|
)
|
||||||
|
while (result) {
|
||||||
|
renderDispatchedRef.current.add(result.callId)
|
||||||
|
result = findGenerateUIOutput(
|
||||||
|
parts,
|
||||||
|
renderDispatchedRef.current
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
chat.setMessages(restored)
|
chat.setMessages(restored)
|
||||||
setResumeLoaded(true)
|
setResumeLoaded(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,7 +92,7 @@ export const Reasoning = memo(
|
|||||||
return (
|
return (
|
||||||
<ReasoningContext.Provider value={{ isStreaming, isOpen, setIsOpen, duration }}>
|
<ReasoningContext.Provider value={{ isStreaming, isOpen, setIsOpen, duration }}>
|
||||||
<Collapsible
|
<Collapsible
|
||||||
className={cn("not-prose mb-4", className)}
|
className={cn("not-prose mb-2", className)}
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
{...props}
|
{...props}
|
||||||
@ -130,17 +130,17 @@ export const ReasoningTrigger = memo(
|
|||||||
return (
|
return (
|
||||||
<CollapsibleTrigger
|
<CollapsibleTrigger
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground",
|
"inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs text-muted-foreground hover:bg-muted/80",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? (
|
{children ?? (
|
||||||
<>
|
<>
|
||||||
<BrainIcon className="size-4" />
|
<BrainIcon className="size-3.5" />
|
||||||
{getThinkingMessage(isStreaming, duration)}
|
{getThinkingMessage(isStreaming, duration)}
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
className={cn("size-4 transition-transform", isOpen ? "rotate-180" : "rotate-0")}
|
className={cn("size-3 opacity-50 transition-transform", isOpen ? "rotate-180" : "rotate-0")}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import StarterKit from "@tiptap/starter-kit"
|
|||||||
import Placeholder from "@tiptap/extension-placeholder"
|
import Placeholder from "@tiptap/extension-placeholder"
|
||||||
import Link from "@tiptap/extension-link"
|
import Link from "@tiptap/extension-link"
|
||||||
import Mention from "@tiptap/extension-mention"
|
import Mention from "@tiptap/extension-mention"
|
||||||
|
import { Markdown } from "tiptap-markdown"
|
||||||
import {
|
import {
|
||||||
Bold,
|
Bold,
|
||||||
Italic,
|
Italic,
|
||||||
@ -166,7 +167,10 @@ export function MessageComposer({
|
|||||||
StarterKit.configure({
|
StarterKit.configure({
|
||||||
heading: false,
|
heading: false,
|
||||||
horizontalRule: false,
|
horizontalRule: false,
|
||||||
blockquote: false,
|
}),
|
||||||
|
Markdown.configure({
|
||||||
|
transformPastedText: true,
|
||||||
|
transformCopiedText: true,
|
||||||
}),
|
}),
|
||||||
Placeholder.configure({
|
Placeholder.configure({
|
||||||
placeholder: placeholder ?? `Message #${channelName}`,
|
placeholder: placeholder ?? `Message #${channelName}`,
|
||||||
@ -212,8 +216,8 @@ export function MessageComposer({
|
|||||||
const handleSend = React.useCallback(async () => {
|
const handleSend = React.useCallback(async () => {
|
||||||
if (!editor || isSending) return
|
if (!editor || isSending) return
|
||||||
|
|
||||||
const content = editor.getText().trim()
|
const plainText = editor.getText().trim()
|
||||||
if (!content) return
|
if (!plainText) return
|
||||||
|
|
||||||
setIsSending(true)
|
setIsSending(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
@ -222,12 +226,16 @@ export function MessageComposer({
|
|||||||
const mentions = extractMentions(
|
const mentions = extractMentions(
|
||||||
editor.getJSON() as Record<string, unknown>,
|
editor.getJSON() as Record<string, unknown>,
|
||||||
)
|
)
|
||||||
const contentHtml = editor.getHTML()
|
// send markdown so the server renders it via `marked`
|
||||||
|
const storage = editor.storage as unknown as Record<
|
||||||
|
string,
|
||||||
|
{ getMarkdown?: () => string } | undefined
|
||||||
|
>
|
||||||
|
const markdown = storage.markdown?.getMarkdown?.() ?? plainText
|
||||||
|
|
||||||
const result = await sendMessage({
|
const result = await sendMessage({
|
||||||
channelId,
|
channelId,
|
||||||
content,
|
content: markdown,
|
||||||
contentHtml,
|
|
||||||
threadId,
|
threadId,
|
||||||
mentions: mentions.length > 0 ? mentions : undefined,
|
mentions: mentions.length > 0 ? mentions : undefined,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -177,19 +177,8 @@ export const MessageItem = React.memo(function MessageItem({ message }: MessageI
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : message.contentHtml ? (
|
|
||||||
<div
|
|
||||||
className="mt-1 text-sm prose prose-sm dark:prose-invert max-w-none
|
|
||||||
prose-p:my-1 prose-p:leading-relaxed
|
|
||||||
prose-code:rounded prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:font-mono prose-code:text-sm
|
|
||||||
prose-pre:bg-muted prose-pre:p-3 prose-pre:rounded-md prose-pre:overflow-x-auto
|
|
||||||
prose-a:text-primary prose-a:no-underline hover:prose-a:underline
|
|
||||||
prose-ul:my-1 prose-ol:my-1 prose-li:my-0.5
|
|
||||||
prose-blockquote:border-l-primary prose-blockquote:text-muted-foreground"
|
|
||||||
dangerouslySetInnerHTML={{ __html: message.contentHtml }}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-1 text-sm">
|
<div className="chat-markdown mt-1 text-sm">
|
||||||
<MarkdownRenderer>{message.content}</MarkdownRenderer>
|
<MarkdownRenderer>{message.content}</MarkdownRenderer>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user