diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx index 0d57ca81..65786cfe 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx @@ -15,6 +15,9 @@ export type ChatMessageLocation = { } +const cn = (className: string) => className?.split(' ').map(c => c ? `void-${c}` : '').join(' ') + + type ApplyBoxLocation = ChatMessageLocation & { tokenIdx: string } const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) => { @@ -23,28 +26,93 @@ const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) => -export const CodeSpan = ({ children, className }: { children: React.ReactNode, className?: string }) => { - return - {children} - +// all classnames must go in tailwind.config.js/safelist +export const noSpaceStyles = { + blockquote: 'pl-4 border-l-4 border-void-bg-2 italic', + br: '', + code: '', + codespan: 'bg-void-bg-1 px-1 rounded-sm font-mono font-medium break-all', + def: '', + del: 'line-through', + em: 'italic', + escape: '', + heading: { + h1: "text-4xl font-semibold pb-2 border-b border-void-bg-2", + h2: "text-3xl font-semibold pb-2 border-b border-void-bg-2", + h3: "text-2xl font-semibold", + h4: "text-xl font-semibold", + h5: "text-lg font-semibold", + h6: "text-base font-semibold text-gray-600" + }, + hr: 'border-t border-void-bg-2', + html: '', + image: 'max-w-full h-auto rounded', + link: 'underline cursor-pointer', + list: 'list-inside pl-2', + list_item: '', + paragraph: '', + space: '', + strong: 'font-semibold', + table: 'overflow-x-auto', + text: '', } -const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, tokenIdx }: { token: Token | string, nested?: boolean, noSpace?: boolean, chatMessageLocationForApply?: ChatMessageLocation, tokenIdx: string }): JSX.Element => { + +const defaultStyles = { + blockquote: 'mx-2 pl-4 border-l-4 border-void-bg-2 italic my-4', + br: '', + code: 'mx-2 my-4', + codespan: 'bg-void-bg-1 px-1 rounded-sm font-mono font-medium break-all', + def: '', + del: 'line-through', + em: 'italic', + escape: '', + heading: { + h1: 'mx-2 text-4xl font-semibold mt-6 mb-4 pb-2 border-b border-void-bg-2', + h2: 'mx-2 text-3xl font-semibold mt-6 mb-4 pb-2 border-b border-void-bg-2', + h3: 'mx-2 text-2xl font-semibold mt-6 mb-4', + h4: 'mx-2 text-xl font-semibold mt-6 mb-4', + h5: 'mx-2 text-lg font-semibold mt-6 mb-4', + h6: 'mx-2 text-base font-semibold mt-6 mb-4 text-gray-600' + }, + hr: 'mx-2 my-6 border-t border-void-bg-2', + html: 'mx-2 my-4', + image: 'mx-2 my-4 max-w-full h-auto rounded', + link: 'mx-2 underline', + list: 'mx-2 my-2 list-inside pl-2', + list_item: 'mx-2 mb-2', + paragraph: 'mx-2 my-4', + space: '', + strong: 'mx-2 font-semibold', + table: 'mx-2 my-4 overflow-x-auto', + text: '', +} + + + +type TokenClasses = typeof defaultStyles + +const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx, classes }: { token: Token | string, nested?: boolean, chatMessageLocationForApply?: ChatMessageLocation, tokenIdx: string, classes?: TokenClasses }): JSX.Element => { // deal with built-in tokens first (assume marked token) const t = token as MarkedToken + if(t.raw.trim() ===''){ + return <>; + } + + // compute the className + const defaultClassName = defaultStyles[t.type] + const classNameOverride = classes?.[t.type] + const _className = classNameOverride ?? defaultClassName + let className: string = '' + if (typeof defaultClassName === 'string') { + className = _className as string + } + if (t.type === "space") { - return {t.raw} + return {t.raw} } if (t.type === "code") { @@ -55,32 +123,28 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke tokenIdx: tokenIdx, }) : null - return
+ return
} - /> + initValue={t.text} + language={t.lang === undefined ? undefined : nameToVscodeLanguage[t.lang]} + buttonsOnHover={applyBoxId && } + />
} if (t.type === "heading") { - const HeadingTag = `h${t.depth}` as keyof JSX.IntrinsicElements - const headingClasses: { [h: string]: string } = { - h1: "text-4xl font-semibold mt-6 mb-4 pb-2 border-b border-void-bg-2", - h2: "text-3xl font-semibold mt-6 mb-4 pb-2 border-b border-void-bg-2", - h3: "text-2xl font-semibold mt-6 mb-4", - h4: "text-xl font-semibold mt-6 mb-4", - h5: "text-lg font-semibold mt-6 mb-4", - h6: "text-base font-semibold mt-6 mb-4 text-gray-600" - } - return {t.text} + + const HeadingTag = `h${t.depth}` as keyof typeof defaultStyles.heading + + const className = classes?.heading[HeadingTag] ?? defaultStyles.heading[HeadingTag] + + return {t.text} } if (t.type === "table") { return ( -
- +
+
{t.header.map((cell: any, index: number) => ( @@ -100,7 +164,7 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke {row.map((cell: any, cellIndex: number) => (
{cell.raw} @@ -115,22 +179,34 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke } if (t.type === "hr") { - return
+ return
} if (t.type === "blockquote") { - return
{t.text}
+ return
{t.text}
+ } + + if (t.type === 'list_item') { +
  • + + !!!!!!!!!!!!! + + +
  • } if (t.type === "list") { const ListTag = t.ordered ? "ol" : "ul" + + const itemClassName = classes?.['list_item'] ?? defaultStyles['list_item'] + return ( {t.items.map((item, index) => ( -
  • +
  • {item.task && ( )} @@ -145,10 +221,10 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke // return ( // // {t.items.map((item, index) => ( - //
  • + //
  • // {item.task && ( // // )} @@ -164,26 +240,26 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke if (t.type === "paragraph") { const contents = <> {t.tokens.map((token, index) => ( - // assign a unique tokenId to nested components + // assign a unique tokenId to nested components ))} if (nested) return contents - return

    + return

    {contents}

    } if (t.type === "html") { return ( -

    +

    {t.raw}

    ) } if (t.type === "text" || t.type === "escape") { - return {t.raw} + return {t.raw} } if (t.type === "def") { @@ -193,7 +269,7 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke if (t.type === "link") { return ( { window.open(t.href) }} href={t.href} title={t.title ?? undefined} @@ -208,51 +284,52 @@ const RenderToken = ({ token, nested, noSpace, chatMessageLocationForApply, toke src={t.href} alt={t.text} title={t.title ?? undefined} - className={`max4w-full h-auto rounded ${noSpace ? '' : 'my-4'}`} + className={cn(className)} /> } if (t.type === "strong") { - return {t.text} + return {t.text} } if (t.type === "em") { - return {t.text} + return {t.text} } // inline code if (t.type === "codespan") { return ( - + {t.text} - + ) } if (t.type === "br") { - return
    + return
    } // strikethrough if (t.type === "del") { - return {t.text} + return {t.text} } // default return (
    Unknown type: + {t.type} {t.raw}
    ) } -export const ChatMarkdownRender = ({ string, nested = false, noSpace, chatMessageLocationForApply }: { string: string, nested?: boolean, noSpace?: boolean, chatMessageLocationForApply?: ChatMessageLocation }) => { +export const ChatMarkdownRender = ({ string, nested = false, classes, chatMessageLocationForApply }: { string: string, nested?: boolean, classes?: TokenClasses, chatMessageLocationForApply?: ChatMessageLocation }) => { const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer return ( <> {tokens.map((token, index) => ( - + ))} ) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 6e21bc57..86df2e7c 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -1,3 +1,6 @@ +//!!!! merged + + /*-------------------------------------------------------------------------------------- * Copyright 2025 Glass Devtools, Inc. All rights reserved. diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index 8e40d6c5..1ebbefc9 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -15,7 +15,7 @@ import { isWindows, isLinux, isMacintosh } from '../../../../../../../base/commo import { URI } from '../../../../../../../base/common/uri.js' import { env } from '../../../../../../../base/common/process.js' import { ModelDropdown } from './ModelDropdown.js' -import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js' +import { ChatMarkdownRender, noSpaceStyles } from '../markdown/ChatMarkdownRender.js' import { WarningBox } from './WarningBox.js' import { os } from '../../../../common/helpers/systemInfo.js' @@ -293,7 +293,7 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider isPasswordField={isPasswordField} /> {subTextMd === undefined ? null :
    - +
    } @@ -413,11 +413,11 @@ export const FeaturesTab = () => { {/*

    {`Void can access any model that you host locally. We automatically detect your local models by default.`}

    */}

    {`Void can access any model that you host locally. We automatically detect your local models by default.`}

    - - - - - + + + + + {/* TODO we should create UI for downloading models without user going into terminal */}
    diff --git a/src/vs/workbench/contrib/void/browser/react/tailwind.config.js b/src/vs/workbench/contrib/void/browser/react/tailwind.config.js index 38a94f83..228b2847 100644 --- a/src/vs/workbench/contrib/void/browser/react/tailwind.config.js +++ b/src/vs/workbench/contrib/void/browser/react/tailwind.config.js @@ -167,6 +167,82 @@ module.exports = { }, }, plugins: [], - prefix: 'void-' + prefix: 'void-', + safelist: [ + // Background colors + 'void-bg-void-bg-1', + + // Borders + 'void-border-b', + 'void-border-l-4', + 'void-border-t', + 'void-border-void-bg-2', + + // Typography + 'void-text-2xl', + 'void-text-3xl', + 'void-text-4xl', + 'void-text-base', + 'void-text-lg', + 'void-text-xl', + 'void-text-gray-600', + 'void-font-medium', + 'void-font-mono', + 'void-font-semibold', + 'void-italic', + 'void-line-through', + 'void-underline', + + // Spacing + 'void-mt-1', + 'void-mt-2', + 'void-mt-4', + 'void-mt-6', + 'void-mb-1', + 'void-mb-2', + 'void-mb-4', + 'void-mx-1', + 'void-mx-2', + 'void-mx-4', + 'void-my-1', + 'void-my-2', + 'void-my-4', + 'void-my-6', + 'void-pb-1', + 'void-pb-2', + 'void-pb-4', + 'void-pl-1', + 'void-pl-2', + 'void-pl-4', + 'void-px-1', + 'void-px-2', + 'void-px-4', + + // Sizing and layout + 'void-h-auto', + 'void-max-w-full', + 'void-overflow-x-auto', + + // Lists + 'void-list-inside', + 'void-list-decimal', + 'void-list-disc', + + // Effects and decoration + 'void-cursor-pointer', + 'void-ring-8', + 'void-ring-[#123456]', + 'void-rounded', + 'void-rounded-sm', + + // misc + 'void-break-all', + 'void-bg-void-bg-1', + 'void-px-1', + 'void-rounded-sm', + 'void-font-mono', + 'void-font-medium', + 'void-break-all' + ] }