diff --git a/web/package-lock.json b/web/package-lock.json index af5f5078b..0e19baab4 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -19,6 +19,7 @@ "@types/js-cookie": "^3.0.3", "@types/lodash": "^4.17.0", "@types/node": "18.15.11", + "@types/prismjs": "^1.26.4", "@types/react": "18.0.32", "@types/react-dom": "18.0.11", "@types/uuid": "^9.0.8", @@ -30,12 +31,14 @@ "next": "^14.2.3", "npm": "^10.8.0", "postcss": "^8.4.31", + "prismjs": "^1.29.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-dropzone": "^14.2.3", "react-icons": "^4.8.0", "react-loader-spinner": "^5.4.5", "react-markdown": "^9.0.1", + "rehype-prism-plus": "^2.0.0", "remark-gfm": "^4.0.0", "semver": "^7.5.4", "sharp": "^0.32.6", @@ -1762,6 +1765,11 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==" }, + "node_modules/@types/prismjs": { + "version": "1.26.4", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.4.tgz", + "integrity": "sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==" + }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", @@ -3171,6 +3179,17 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -4320,6 +4339,95 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-from-html": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz", + "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", + "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^8.0.0", + "property-information": "^6.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/hastscript": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", + "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/hast-util-parse-selector/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz", @@ -4346,6 +4454,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.0.tgz", + "integrity": "sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", @@ -4358,6 +4478,35 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/hastscript/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -8801,6 +8950,22 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -9126,6 +9291,14 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -9516,6 +9689,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/refractor": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-4.8.1.tgz", + "integrity": "sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/prismjs": "^1.0.0", + "hastscript": "^7.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/refractor/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -9539,6 +9740,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/rehype-parse": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.0.tgz", + "integrity": "sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-prism-plus": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/rehype-prism-plus/-/rehype-prism-plus-2.0.0.tgz", + "integrity": "sha512-FeM/9V2N7EvDZVdR2dqhAzlw5YI49m9Tgn7ZrYJeYHIahM6gcXpH0K1y2gNnKanZCydOMluJvX2cB9z3lhY8XQ==", + "dependencies": { + "hast-util-to-string": "^3.0.0", + "parse-numeric-range": "^1.3.0", + "refractor": "^4.8.0", + "rehype-parse": "^9.0.0", + "unist-util-filter": "^5.0.0", + "unist-util-visit": "^5.0.0" + } + }, "node_modules/remark-gfm": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", @@ -10692,6 +10920,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-filter": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/unist-util-filter/-/unist-util-filter-5.0.1.tgz", + "integrity": "sha512-pHx7D4Zt6+TsfwylH9+lYhBhzyhEnCXs/lbq/Hstxno5z4gVdyc2WEW0asfjGKPyG4pEKrnBv5hdkO6+aRnQJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + } + }, "node_modules/unist-util-is": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", @@ -10886,6 +11124,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vfile-location": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", + "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vfile-message": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", @@ -10920,6 +11171,15 @@ "d3-timer": "^3.0.1" } }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/web/package.json b/web/package.json index ba7b23ea2..1ba7286d4 100644 --- a/web/package.json +++ b/web/package.json @@ -20,6 +20,7 @@ "@types/js-cookie": "^3.0.3", "@types/lodash": "^4.17.0", "@types/node": "18.15.11", + "@types/prismjs": "^1.26.4", "@types/react": "18.0.32", "@types/react-dom": "18.0.11", "@types/uuid": "^9.0.8", @@ -31,12 +32,14 @@ "next": "^14.2.3", "npm": "^10.8.0", "postcss": "^8.4.31", + "prismjs": "^1.29.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-dropzone": "^14.2.3", "react-icons": "^4.8.0", "react-loader-spinner": "^5.4.5", "react-markdown": "^9.0.1", + "rehype-prism-plus": "^2.0.0", "remark-gfm": "^4.0.0", "semver": "^7.5.4", "sharp": "^0.32.6", diff --git a/web/src/app/chat/ChatPage.tsx b/web/src/app/chat/ChatPage.tsx index 477d5ca86..66da8ba78 100644 --- a/web/src/app/chat/ChatPage.tsx +++ b/web/src/app/chat/ChatPage.tsx @@ -5,7 +5,6 @@ import { BackendChatSession, BackendMessage, ChatFileType, - ChatSession, ChatSessionSharedStatus, DocumentsResponse, FileDescriptor, @@ -16,12 +15,9 @@ import { ToolRunKickoff, } from "./interfaces"; import { ChatSidebar } from "./sessionSidebar/ChatSidebar"; -import { DocumentSet, Tag, User, ValidSources } from "@/lib/types"; import { Persona } from "../admin/assistants/interfaces"; -import { Header } from "@/components/header/Header"; import { HealthCheckBanner } from "@/components/health/healthcheck"; import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh"; -import { Settings } from "../admin/settings/interfaces"; import { buildChatUrl, buildLatestMessageChain, @@ -53,22 +49,16 @@ import { DanswerInitializingLoader } from "@/components/DanswerInitializingLoade import { FeedbackModal } from "./modal/FeedbackModal"; import { ShareChatSessionModal } from "./modal/ShareChatSessionModal"; import { ChatPersonaSelector } from "./ChatPersonaSelector"; -import { HEADER_PADDING } from "@/lib/constants"; -import { FiSend, FiShare2, FiStopCircle } from "react-icons/fi"; +import { FiShare2 } from "react-icons/fi"; import { ChatIntro } from "./ChatIntro"; import { AIMessage, HumanMessage } from "./message/Messages"; import { ThreeDots } from "react-loader-spinner"; import { StarterMessage } from "./StarterMessage"; -import { SelectedDocuments } from "./modifiers/SelectedDocuments"; -import { ChatFilters } from "./modifiers/ChatFilters"; import { AnswerPiecePacket, DanswerDocument } from "@/lib/search/interfaces"; import { buildFilters } from "@/lib/search/utils"; import { SettingsContext } from "@/components/settings/SettingsProvider"; import Dropzone from "react-dropzone"; -import { LLMProviderDescriptor } from "../admin/models/llm/interfaces"; import { checkLLMSupportsImageInput, getFinalLLM } from "@/lib/llm/utils"; -import { InputBarPreviewImage } from "./files/images/InputBarPreviewImage"; -import { Folder } from "./folders/interfaces"; import { ChatInputBar } from "./input/ChatInputBar"; import { ConfigurationModal } from "./modal/configuration/ConfigurationModal"; import { useChatContext } from "@/components/context/ChatContext"; diff --git a/web/src/app/chat/message/CodeBlock.tsx b/web/src/app/chat/message/CodeBlock.tsx new file mode 100644 index 000000000..8c6d984a4 --- /dev/null +++ b/web/src/app/chat/message/CodeBlock.tsx @@ -0,0 +1,95 @@ +import React from "react"; +import { useState, ReactNode } from "react"; +import { FiCheck, FiCopy } from "react-icons/fi"; + +interface CodeBlockProps { + className?: string | undefined; + children?: ReactNode; + content: string; + [key: string]: any; +} + +export function CodeBlock({ + className = "", + children, + content, + ...props +}: CodeBlockProps) { + const language = className + .split(" ") + .filter((cls) => cls.startsWith("language-")) + .map((cls) => cls.replace("language-", "")) + .join(" "); + const [copied, setCopied] = useState(false); + + if (!language) { + return ( + + {children} + + ); + } + + let codeText: string | null = null; + if ( + props.node?.position?.start?.offset && + props.node?.position?.end?.offset + ) { + codeText = content.slice( + props.node.position.start.offset, + props.node.position.end.offset + ); + + // Remove the language declaration and trailing backticks + const codeLines = codeText.split("\n"); + if (codeLines.length > 1 && codeLines[0].startsWith("```")) { + codeLines.shift(); // Remove the first line with the language declaration + if (codeLines[codeLines.length - 1] === "```") { + codeLines.pop(); // Remove the last line with the trailing backticks + } + codeText = codeLines.join("\n"); + } + } + + const handleCopy = () => { + if (!codeText) { + return; + } + + navigator.clipboard.writeText(codeText).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); // Reset copy status after 2 seconds + }); + }; + + return ( +
+
+ {language} + {codeText && ( +
+ {copied ? ( +
+ + Copied! +
+ ) : ( +
+ + Copy code +
+ )} +
+ )} +
+
+        
+          {children}
+        
+      
+
+ ); +} diff --git a/web/src/app/chat/message/Messages.tsx b/web/src/app/chat/message/Messages.tsx index a098d7a36..47623f7e5 100644 --- a/web/src/app/chat/message/Messages.tsx +++ b/web/src/app/chat/message/Messages.tsx @@ -1,3 +1,5 @@ +"use client"; + import { FiCpu, FiImage, @@ -24,6 +26,14 @@ import { ToolRunningAnimation } from "../tools/ToolRunningAnimation"; import { Hoverable } from "@/components/Hoverable"; import { DocumentPreview } from "../files/documents/DocumentPreview"; import { InMessageImage } from "../files/images/InMessageImage"; +import { CodeBlock } from "./CodeBlock"; +import rehypePrism from "rehype-prism-plus"; + +// Prism stuff +import Prism from "prismjs"; + +import "prismjs/themes/prism-tomorrow.css"; +import "./custom-code-styles.css"; function FileDisplay({ files }: { files: FileDescriptor[] }) { const imageFiles = files.filter((file) => file.type === ChatFileType.IMAGE); @@ -31,7 +41,6 @@ function FileDisplay({ files }: { files: FileDescriptor[] }) { return ( <> - {" "} {nonImgFiles && nonImgFiles.length > 0 && (
@@ -94,6 +103,36 @@ export const AIMessage = ({ handleForceSearch?: () => void; retrievalDisabled?: boolean; }) => { + const [isReady, setIsReady] = useState(false); + useEffect(() => { + Prism.highlightAll(); + setIsReady(true); + }, []); + + // this is needed to give Prism a chance to load + if (!isReady) { + return
; + } + + if (!isComplete) { + const trimIncompleteCodeSection = ( + content: string | JSX.Element + ): string | JSX.Element => { + if (typeof content === "string") { + const pattern = /```[a-zA-Z]+[^\s]*$/; + const match = content.match(pattern); + if (match && match.index && match.index > 3) { + const newContent = content.slice(0, match.index - 3); + return newContent; + } + return content; + } + return content; + }; + + content = trimIncompleteCodeSection(content); + } + const loader = currentTool === IMAGE_GENERATION_TOOL_NAME ? (
@@ -181,6 +220,7 @@ export const AIMessage = ({ {typeof content === "string" ? ( ( @@ -191,8 +231,12 @@ export const AIMessage = ({ rel="noopener noreferrer" /> ), + code: (props) => ( + + ), }} remarkPlugins={[remarkGfm]} + rehypePlugins={[rehypePrism]} > {content} diff --git a/web/src/app/chat/message/custom-code-styles.css b/web/src/app/chat/message/custom-code-styles.css new file mode 100644 index 000000000..b7d419beb --- /dev/null +++ b/web/src/app/chat/message/custom-code-styles.css @@ -0,0 +1,36 @@ +pre[class*="language-"] { + padding: 0px; /* Override padding */ + margin: 0px; + border: none; +} + +.prose :where(pre):not(:where([class~="not-prose"], [class~="not-prose"] *)) { + padding: 0px; /* Override padding */ + margin: 0px; + + /* Override scrollbar style to match prism-tomorrow */ + ::-webkit-scrollbar { + width: 8px; /* Vertical scrollbar width */ + height: 8px; /* Horizontal scrollbar height */ + } + + ::-webkit-scrollbar-track { + background: #1f2937; /* Dark track background color */ + } + + ::-webkit-scrollbar-thumb { + background: #4b5563; /* Dark handle color */ + border-radius: 10px; + transition: + background 0.3s ease, + box-shadow 0.3s ease; /* Smooth transition for hover effect */ + } + + ::-webkit-scrollbar-thumb:hover { + background: #6b7280; /* Dark handle color on hover */ + box-shadow: 0 0 10px #6b7280; /* Light up effect on hover */ + } + + scrollbar-width: thin; + scrollbar-color: #4b5563 #1f2937; /* thumb and track colors */ +} diff --git a/web/src/app/globals.css b/web/src/app/globals.css index 413e9d46e..37d92f36a 100644 --- a/web/src/app/globals.css +++ b/web/src/app/globals.css @@ -2,6 +2,19 @@ @tailwind components; @tailwind utilities; +@layer utilities { + /* Hide scrollbar for Chrome, Safari and Opera */ + .no-scrollbar::-webkit-scrollbar { + display: none; + } + + /* Hide scrollbar for IE, Edge and Firefox */ + .no-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } +} + ::-webkit-scrollbar-track { background: #f9fafb; /* Track background color */ }