From f14aeb507f126f218aade4fbdb49fb3d483c9543 Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Tue, 28 Jan 2025 15:09:13 -0800 Subject: [PATCH] use vscode-elements natively (#1061) --- packages/cli/src/server.ts | 61 +++-- packages/web/index.html | 1 + packages/web/package.json | 3 +- packages/web/src/App.tsx | 301 ++++++++++++----------- packages/web/src/Code.tsx | 7 +- packages/web/src/Markdown.tsx | 1 - packages/web/src/MarkdownWithPreview.tsx | 26 +- packages/web/src/vscode-elements.d.ts | 108 ++++++++ yarn.lock | 21 +- 9 files changed, 327 insertions(+), 202 deletions(-) create mode 100644 packages/web/src/vscode-elements.d.ts diff --git a/packages/cli/src/server.ts b/packages/cli/src/server.ts index 0537c6201a..7635987656 100644 --- a/packages/cli/src/server.ts +++ b/packages/cli/src/server.ts @@ -53,6 +53,7 @@ import { networkInterfaces } from "os" import { GitClient } from "../../core/src/git" import { exists } from "fs-extra" import { deleteUndefinedValues } from "../../core/src/cleaners" +import { readFile } from "fs/promises" /** * Starts a WebSocket server for handling chat and script execution. @@ -325,15 +326,17 @@ export async function startServer(options: { output: run.outputTrace.content, } satisfies PromptScriptProgressResponseEvent) ) - chunkString(run.trace.content, WS_MAX_FRAME_LENGTH - 200).forEach( - (c) => - ws.send( - toPayload({ - type: "script.progress", - runId, - trace: c, - } satisfies PromptScriptProgressResponseEvent) - ) + chunkString( + run.trace.content, + WS_MAX_FRAME_LENGTH - 200 + ).forEach((c) => + ws.send( + toPayload({ + type: "script.progress", + runId, + trace: c, + } satisfies PromptScriptProgressResponseEvent) + ) ) } } else if (lastRunResult) { @@ -416,15 +419,17 @@ export async function startServer(options: { const outputTrace = new MarkdownTrace() trace.addEventListener(TRACE_CHUNK, (ev) => { const tev = ev as TraceChunkEvent - chunkString(tev.chunk, WS_MAX_FRAME_LENGTH - 200).forEach( - (c) => sendProgress(runId, { trace: c }) - ) + chunkString( + tev.chunk, + WS_MAX_FRAME_LENGTH - 200 + ).forEach((c) => sendProgress(runId, { trace: c })) }) outputTrace.addEventListener(TRACE_CHUNK, (ev) => { const tev = ev as TraceChunkEvent - chunkString(tev.chunk, WS_MAX_FRAME_LENGTH - 200).forEach( - (c) => sendProgress(runId, { output: c }) - ) + chunkString( + tev.chunk, + WS_MAX_FRAME_LENGTH - 200 + ).forEach((c) => sendProgress(runId, { output: c })) }) logVerbose(`run ${runId}: starting ${script}`) await runtimeHost.readConfig() @@ -562,9 +567,31 @@ export async function startServer(options: { res.setHeader("Content-Type", "text/html") res.setHeader("Cache-Control", "no-store") res.statusCode = 200 + + const cspUrl = new URL(`http://${req.headers.host}`).origin + const wsCspUrl = new URL(`ws://${req.headers.host}`).origin + const nonce = randomHex(32) + const csp = ` + + ` + const filePath = join(__dirname, "index.html") - const stream = createReadStream(filePath) - stream.pipe(res) + const html = (await readFile(filePath, { encoding: "utf8" })) + .replace("", csp) + res.write(html) + res.statusCode = 200 + res.end() } else if (method === "GET" && route === "/built/markdown.css") { res.setHeader("Content-Type", "text/css") res.statusCode = 200 diff --git a/packages/web/index.html b/packages/web/index.html index 9f8a40206c..abac7c7626 100644 --- a/packages/web/index.html +++ b/packages/web/index.html @@ -6,6 +6,7 @@ GenAIScript Script Runner +
diff --git a/packages/web/package.json b/packages/web/package.json index 1ed547daa7..6221f9d915 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -14,8 +14,7 @@ "@types/marked": "^6.0.0", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", - "@vscode-elements/elements": "^1.11.0", - "@vscode-elements/react-elements": "^0.8.0", + "@vscode-elements/elements": "1.11.1-pre.0", "@vscode-elements/webview-playground": "^1.4.0", "clsx": "^2.1.1", "esbuild": "^0.24.2", diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 3c27e3ad38..5038726985 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -1,4 +1,5 @@ /// +/// import React, { createContext, Dispatch, @@ -11,26 +12,26 @@ import React, { useMemo, useState, } from "react" -import { - VscodeButton, - VscodeSingleSelect, - VscodeOption, - VscodeTextfield, - VscodeCheckbox, - VscodeFormContainer, - VscodeFormGroup, - VscodeFormHelper, - VscodeLabel, - VscodeProgressRing, - VscodeCollapsible, - VscodeTabs, - VscodeTabHeader, - VscodeTabPanel, - VscodeBadge, - VscodeTextarea, - VscodeMultiSelect, - VscodeScrollable, -} from "@vscode-elements/react-elements" + +import "@vscode-elements/elements/dist/vscode-button" +import "@vscode-elements/elements/dist/vscode-single-select" +import "@vscode-elements/elements/dist/vscode-option" +import "@vscode-elements/elements/dist/vscode-textfield" +import "@vscode-elements/elements/dist/vscode-checkbox" +import "@vscode-elements/elements/dist/vscode-form-container" +import "@vscode-elements/elements/dist/vscode-form-group" +import "@vscode-elements/elements/dist/vscode-form-helper" +import "@vscode-elements/elements/dist/vscode-label" +import "@vscode-elements/elements/dist/vscode-progress-ring" +import "@vscode-elements/elements/dist/vscode-collapsible" +import "@vscode-elements/elements/dist/vscode-tabs" +import "@vscode-elements/elements/dist/vscode-tab-header" +import "@vscode-elements/elements/dist/vscode-tab-panel" +import "@vscode-elements/elements/dist/vscode-badge" +import "@vscode-elements/elements/dist/vscode-textarea" +import "@vscode-elements/elements/dist/vscode-multi-select" +import "@vscode-elements/elements/dist/vscode-scrollable" + import Markdown from "./Markdown" import type { Project, @@ -57,11 +58,12 @@ import { underscore } from "inflection" import { lookupMime } from "../../core/src/mime" import dedent from "dedent" import { markdownDiff } from "../../core/src/mddiff" -import { VscodeMultiSelect as VscodeMultiSelectElement } from "@vscode-elements/elements" import { cleanedClone } from "../../core/src/clone" import { WebSocketClient } from "../../core/src/server/wsclient" import { convertAnnotationToItem } from "../../core/src/annotations" import MarkdownWithPreview from "./MarkdownWithPreview" +import { VscodeMultiSelect } from "@vscode-elements/elements/dist/vscode-multi-select/vscode-multi-select" +import { VscTabsSelectEvent } from "@vscode-elements/elements/dist/vscode-tabs/vscode-tabs" interface GenAIScriptViewOptions { apiKey?: string @@ -81,6 +83,7 @@ const viewMode = (hosted ? "results" : urlParams.get("view")) as const hashParams = new URLSearchParams(window.location.hash.slice(1)) const base = config?.base || "" const apiKey = hashParams.get("api-key") || config?.apiKey || "" +const nonce = (window as any as { litNonce?: string }).litNonce window.location.hash = "" if (!hosted) import("@vscode-elements/webview-playground") @@ -562,7 +565,7 @@ function JSONSchemaNumber(props: { }, [valueText]) return ( - { @@ -611,15 +614,15 @@ function JSONSchemaSimpleTypeFormField(props: { }} > {field.enum.map((option) => ( - + {option} - + ))} - + ) } return ( - { @@ -650,7 +653,7 @@ function JSONSchemaSimpleTypeFormField(props: { ) default: return ( - + {Object.entries(properties).map(([fieldName, field]) => ( - - + + {underscore(fieldPrefix + fieldName).replaceAll( /[_\.]/g, " " )} - + {field?.description && ( - {field.description} + + {field.description} + )} - + ))} - + ) } @@ -715,9 +720,9 @@ function CounterBadge(props: { collection: any | undefined }) { } else if (collection) count = "1" return count ? ( - + {count} - + ) : ( "" ) @@ -726,9 +731,9 @@ function CounterBadge(props: { collection: any | undefined }) { function TraceMarkdown() { const trace = useTrace() return ( - + {trace} - + ) } @@ -736,10 +741,10 @@ function TraceTabPanel(props: { selected?: boolean }) { const { selected } = props return ( <> - Trace - + Trace + {selected ? : null} - + ) } @@ -747,9 +752,9 @@ function TraceTabPanel(props: { selected?: boolean }) { function OutputMarkdown() { const output = useOutput() return ( - + {output} - + ) } @@ -757,10 +762,10 @@ function OutputTraceTabPanel(props: { selected?: boolean }) { const { selected } = props return ( <> - Output - + Output + {selected ? : null} - + ) } @@ -774,13 +779,13 @@ function ProblemsTabPanel() { return ( <> - + Problems - - + + {annotationsMarkdown} - + ) } @@ -795,13 +800,13 @@ function MessagesTabPanel() { }) return ( <> - + Chat - - + + {md} - + ) } @@ -823,17 +828,17 @@ function StatsTabPanel() { const md = stats ? YAMLStringify(rest) : "" return ( <> - + Stats {!!cost && ( - + {renderCost(cost)} - + )} - - + + {md ? {fenceMD(md, "yaml")} : null} - + ) } @@ -846,13 +851,13 @@ function LogProbsTabPanel() { const md = logprobs?.map((lp) => logprobToMarkdown(lp)).join("\n") return ( <> - + Perplexity - - + + {md} - + ) } @@ -865,13 +870,13 @@ function TopLogProbsTabPanel() { const md = logprobs?.map((lp) => topLogprobsToMarkdown(lp)).join("\n") return ( <> - + Entropy - - + + {md} - + ) } @@ -883,11 +888,11 @@ function FileEditsTabPanel() { return ( <> - + Edits - - + + {files ?.map( @@ -908,7 +913,7 @@ function FileEditsTabPanel() { ) .join("\n")} - + ) } @@ -919,11 +924,11 @@ function DataTabPanel() { return ( <> - + Data - - + + {frames.map((frame, i) => ( {` @@ -933,7 +938,7 @@ ${JSON.stringify(frame, null, 2)}} `} ))} - + ) } @@ -943,11 +948,11 @@ function JSONTabPanel() { const { json } = result || {} return ( <> - + JSON - - + + {json && ( {` @@ -957,7 +962,7 @@ ${JSON.stringify(json, null, 2)}} `} )} - + ) } @@ -966,8 +971,8 @@ function RawTabPanel() { const result = useResult() return ( <> - Raw - + Raw + {result && ( {` @@ -977,7 +982,7 @@ ${JSON.stringify(result, null, 2)}} `} )} - + ) } @@ -1025,12 +1030,12 @@ function FilesDropZone() { return ( <> - - Files - + Files + { e.preventDefault() - const target = e.target as VscodeMultiSelectElement + const target = e.target as VscodeMultiSelect const newImportedFiles = [...importedFiles] const selected = target.selectedIndexes for (let i = 0; i < newImportedFiles.length; i++) { @@ -1040,29 +1045,29 @@ function FilesDropZone() { }} > {importedFiles.map((file) => ( - {file.name} ({prettyBytes(file.size)}) - + ))} - - - + + - + {isDragActive ? `Drop the files here ...` : `Drag 'n' drop some files here, or click to select files`} - - + + ) } @@ -1070,10 +1075,10 @@ function FilesDropZone() { function GlobsForm() { const { files = [], setFiles } = useApi() return ( - - - Globs - + + Globs + { @@ -1081,8 +1086,8 @@ function GlobsForm() { startTransition(() => setFiles(target.value.split(","))) }} /> - - + + ) } @@ -1093,17 +1098,13 @@ function RemoteInfo() { const { url, branch } = remote const value = `${url}#${branch}` return ( - - Remote - - + + Remote + + Running GenAIScript on a clone of this repository. - - + + ) } @@ -1113,16 +1114,16 @@ function ScriptSelect() { const script = useScript() return ( - - + + - - + { + onvsc-change={(e: Event) => { const target = e.target as HTMLSelectElement setScriptid(target.value) }} @@ -1130,39 +1131,39 @@ function ScriptSelect() { {scripts .filter((s) => !s.isSystem && !s.unlisted) .map(({ id, title }) => ( - {id} - + ))} - + {script && ( - + {toStringList( script.title, script.description, script.filename )} - + )} - + ) } function ScriptForm() { return ( - - + + - - + + ) } @@ -1170,7 +1171,7 @@ function ScriptSourcesView() { const script = useScript() const { jsSource, text, filename } = script || {} return ( - + {filename ? {`- ${filename}`} : null} {text ? ( {`\`\`\`\`\`\` @@ -1184,7 +1185,7 @@ ${jsSource.trim()} \`\`\`\`\`\``} ) : null} - + ) } @@ -1205,17 +1206,17 @@ function PromptParametersFields() { return ( <> {scriptParameters && ( - + - + )} {!!systemParameters.length && ( - + {Object.entries(inputSchema.properties) .filter(([k]) => k !== "script") .map(([key, fieldSchema]) => { @@ -1228,7 +1229,7 @@ function PromptParametersFields() { /> ) })} - + )} ) @@ -1295,14 +1296,14 @@ function ModelConnectionOptionsForm() { }, } return ( - + - + ) } @@ -1312,17 +1313,17 @@ function RunButton() { const disabled = !scriptid return ( - - - + + + {state === "running" ? "Abort" : "Run"} - - + + {Object.entries(options) .map(([key, value]) => `${key}: ${value}`) .join(", ")} - - + + ) } @@ -1346,8 +1347,10 @@ function RunForm() { function ResultsTabs() { const [selected, setSelected] = useState(0) return ( - setSelected(e.detail.selectedIndex)} + + setSelected(e.detail.selectedIndex) + } panel > @@ -1360,7 +1363,7 @@ function ResultsTabs() { - + ) } @@ -1372,9 +1375,9 @@ function WebApp() { return ( <> - + - + ) } @@ -1384,7 +1387,7 @@ export default function App() { return ( - }> + }> diff --git a/packages/web/src/Code.tsx b/packages/web/src/Code.tsx index faf52d806b..b421dce0ed 100644 --- a/packages/web/src/Code.tsx +++ b/packages/web/src/Code.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react" import Mermaid from "./Mermaid" -import { VscodeButton } from "@vscode-elements/react-elements" + +import "@vscode-elements/elements/dist/vscode-button" function CodeRender(props: { className?: string; children: any }) { const { className, children, ...restProps } = props @@ -31,14 +32,14 @@ export default function Code(props: { className?: string; children: any }) { {children} - {copied ? "Copied!" : "Copy"} - + ) } diff --git a/packages/web/src/Markdown.tsx b/packages/web/src/Markdown.tsx index 569758f351..71e8033df3 100644 --- a/packages/web/src/Markdown.tsx +++ b/packages/web/src/Markdown.tsx @@ -60,7 +60,6 @@ export default function Markdown(props: { className?: string; children: any }) { }, }} urlTransform={url => { - console.log(url) return url }} rehypePlugins={[ diff --git a/packages/web/src/MarkdownWithPreview.tsx b/packages/web/src/MarkdownWithPreview.tsx index 20f190b960..bc6ea6bb54 100644 --- a/packages/web/src/MarkdownWithPreview.tsx +++ b/packages/web/src/MarkdownWithPreview.tsx @@ -1,14 +1,14 @@ -import { - VscodeTabHeader, - VscodeTabPanel, - VscodeTabs, -} from "@vscode-elements/react-elements" import React from "react" import { fenceMD } from "../../core/src/mkmd" import Markdown from "./Markdown" import { convertThinkToMarkdown } from "../../core/src/think" import { convertAnnotationsToMarkdown } from "../../core/src/annotations" +import "@vscode-elements/elements/dist/vscode-tabs" +import "@vscode-elements/elements/dist/vscode-tab-header" +import "@vscode-elements/elements/dist/vscode-tab-panel" + + export default function MarkdownWithPreview(props: { className?: string children: any @@ -23,15 +23,15 @@ export default function MarkdownWithPreview(props: { ) return ( - - Preview - + + Preview + {md} - - Source - + + Source + {fenceMD(children, "markdown")} - - + + ) } diff --git a/packages/web/src/vscode-elements.d.ts b/packages/web/src/vscode-elements.d.ts new file mode 100644 index 0000000000..0a180162a8 --- /dev/null +++ b/packages/web/src/vscode-elements.d.ts @@ -0,0 +1,108 @@ +import { + VscodeBadge, + VscodeButton, + VscodeCheckbox, + VscodeCheckboxGroup, + VscodeCollapsible, + VscodeContextMenu, + VscodeContextMenuItem, + VscodeDivider, + VscodeFormContainer, + VscodeFormGroup, + VscodeFormHelper, + VscodeIcon, + VscodeLabel, + VscodeMultiSelect, + VscodeOption, + VscodeProgressRing, + VscodeRadio, + VscodeRadioGroup, + VscodeScrollable, + VscodeSingleSelect, + VscodeTabHeader, + VscodeTable, + VscodeTableBody, + VscodeTableCell, + VscodeTableHeader, + VscodeTableHeaderCell, + VscodeTableRow, + VscodeTabPanel, + VscodeTabs, + VscodeTextarea, + VscodeTextfield, + VscodeTree, +} from "@vscode-elements/elements"; +import type { VscCollapsibleToggleEvent } from "@vscode-elements/elements/dist/vscode-collapsible/vscode-collapsible"; +import type { VscContextMenuSelectEvent } from "@vscode-elements/elements/dist/vscode-context-menu/vscode-context-menu"; +import type { + VscodeSplitLayout, + VscSplitLayoutChangeEvent, +} from "@vscode-elements/elements/dist/vscode-split-layout/vscode-split-layout"; +import type { VscTabsSelectEvent } from "@vscode-elements/elements/dist/vscode-tabs/vscode-tabs"; +import type { + VscTreeActionEvent, + VscTreeSelectEvent, +} from "@vscode-elements/elements/dist/vscode-tree/vscode-tree"; + +type ElementProps = Partial>; +type CustomEventHandler = (e: E) => void; + +type WebComponentProps = React.DetailedHTMLProps< + React.HTMLAttributes, + I +> & + ElementProps; + +declare module "react" { + namespace JSX { + interface IntrinsicElements { + "vscode-dev-toolbar": { + hidden?: boolean; + }; + "vscode-badge": WebComponentProps; + "vscode-button": WebComponentProps; + "vscode-checkbox": WebComponentProps; + "vscode-checkbox-group": WebComponentProps; + "vscode-collapsible": WebComponentProps & { + "onvsc-collapsible-toggle"?: CustomEventHandler; + }; + "vscode-context-menu": WebComponentProps & { + "onvsc-context-menu-select"?: CustomEventHandler; + }; + "vscode-context-menu-item": WebComponentProps; + "vscode-divider": WebComponentProps; + "vscode-form-container": WebComponentProps; + "vscode-form-group": WebComponentProps; + "vscode-form-helper": WebComponentProps; + "vscode-icon": WebComponentProps; + "vscode-label": WebComponentProps; + "vscode-multi-select": WebComponentProps; + "vscode-option": WebComponentProps; + "vscode-progress-ring": WebComponentProps; + "vscode-radio": WebComponentProps; + "vscode-radio-group": WebComponentProps; + "vscode-scrollable": WebComponentProps; + "vscode-single-select": WebComponentProps; + "vscode-split-layout": WebComponentProps & { + "onvsc-split-layout-change"?: CustomEventHandler; + }; + "vscode-tab-header": WebComponentProps; + "vscode-tab-panel": WebComponentProps; + "vscode-table": WebComponentProps; + "vscode-table-body": WebComponentProps; + "vscode-table-body": WebComponentProps; + "vscode-table-header": WebComponentProps; + "vscode-table-header-cell": WebComponentProps; + "vscode-table-row": WebComponentProps; + "vscode-tabs": WebComponentProps & { + "onvsc-tabs-select"?: CustomEventHandler; + }; + "vscode-textarea": WebComponentProps; + "vscode-textfield": WebComponentProps; + "vscode-tree": WebComponentProps & { + "onvsc-tree-select"?: CustomEventHandler; + "onvsc-tree-action"?: CustomEventHandler; + }; + } + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index daec131306..55e8aaffe0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1737,11 +1737,6 @@ resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz#a28799c463177d1a0b0e5cefdc173da5ac859eb4" integrity sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ== -"@lit/react@^1.0.6": - version "1.0.7" - resolved "https://registry.yarnpkg.com/@lit/react/-/react-1.0.7.tgz#2ffb7f7b6955a53b7eb53f3557e0dda91d7420ff" - integrity sha512-cencnwwLXQKiKxjfFzSgZRngcWJzUDZi/04E0fSaF86wZgchMdvTyu+lE36DrUfvuus3bH8+xLPrhM1cTjwpzw== - "@lit/reactive-element@^2.0.4": version "2.0.4" resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-2.0.4.tgz#8f2ed950a848016383894a26180ff06c56ae001b" @@ -3474,21 +3469,13 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== -"@vscode-elements/elements@^1.11.0", "@vscode-elements/elements@^1.8.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@vscode-elements/elements/-/elements-1.11.0.tgz#72573d11863d3d309c0b442f6783090827e88e70" - integrity sha512-vC1QDaelqERypHinavJbe4Bl4/g66CVJWKFWFXZyp1seBvTxkgscayDTiE//H95V8b75BgLPDAS1y3pbSIKXag== +"@vscode-elements/elements@1.11.1-pre.0": + version "1.11.1-pre.0" + resolved "https://registry.yarnpkg.com/@vscode-elements/elements/-/elements-1.11.1-pre.0.tgz#1d02e1fcf84b31f5c55e8ad9e20368f352d1ce37" + integrity sha512-bGnEYtl8I2oZz8d0wdSQpC8rxTIk8mo3Vcwd/y5AKrnU7TXVXOCsj2IpTL/psR9iSJ86h2xTq+p76klOgAuPYw== dependencies: lit "^3.2.1" -"@vscode-elements/react-elements@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@vscode-elements/react-elements/-/react-elements-0.8.0.tgz#3d93d36af94738d404a8b1c8d58de1394f823cf0" - integrity sha512-lSOynIyoUsRYuLEKRokKu1UL58GTXwixM5zjmQDLbs5WoDbdHZTr6YQKcqV7kcd2UBzN8hdclKMsnliuxYESpw== - dependencies: - "@lit/react" "^1.0.6" - "@vscode-elements/elements" "^1.8.0" - "@vscode-elements/webview-playground@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@vscode-elements/webview-playground/-/webview-playground-1.4.0.tgz#4c39d0d0815b78d3cb7115cbedbdbdb25c7c1fac"