Skip to main content
Glama

GenAIScript

Official
by microsoft
MIT License
43
2,820
  • Linux
  • Apple
App.tsx26.5 kB
/// <reference path="../../core/src/types/prompt_template.d.ts" /> /// <reference path="./vscode-elements.d.ts" /> import React, { useEffect, useMemo, useRef, useState } from "react" 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 "@vscode-elements/elements/dist/vscode-tree" import "@vscode-elements/elements/dist/vscode-split-layout" import Markdown from "./Markdown" import { useDropzone } from "react-dropzone" import { lookupMime } from "../../core/src/mime" import { VscodeMultiSelect } from "@vscode-elements/elements/dist/vscode-multi-select/vscode-multi-select" import MarkdownPreviewTabs from "./MarkdownPreviewTabs" import { TreeItem, VscodeTree, } from "@vscode-elements/elements/dist/vscode-tree/vscode-tree" import CONFIGURATION from "../../core/src/llms.json" import { MODEL_PROVIDER_GITHUB_COPILOT_CHAT } from "../../core/src/constants" import { apiKey, hosted, viewMode } from "./configuration" import { JSONBooleanOptionsGroup, JSONSchemaObjectForm } from "./JSONSchema" import { ActionButton } from "./ActionButton" import Suspense from "./Suspense" import type { ChatCompletion } from "../../core/src/chattypes" import { RunClientProvider, useClientReadyState } from "./RunClientContext" import { useApi, useScripts, useEnv, useScript, useModels, ApiProvider, } from "./ApiContext" import { ScriptProvider, useScriptId } from "./ScriptContext" import { ResultsTabs } from "./Results" import { RunnerProvider, useRunner } from "./RunnerContext" import { ImportedFile } from "./types" import { ProjectView } from "./ProjectView" import { prettyBytes } from "../../core/src/pretty" import { RunResultSelector } from "./Runs" function useSyncProjectScript() { const { scriptid, setScriptid } = useScriptId() const { runId } = useRunner() const scripts = useScripts() useEffect(() => { if (!scriptid && scripts.length > 0) { if (!runId) setScriptid(scripts[0].id) } else if (scriptid && !scripts.find((s) => s.id === scriptid)) { setScriptid(runId ? undefined : scripts[0]?.id) } }, [scripts, scriptid, runId]) } function GenAIScriptLogo(props: { height: string }) { const { height } = props return ( <img alt="GenAIScript logo" src="/favicon.svg" style={{ height, borderRadius: "2px" }} /> ) } function acceptToAccept(accept: string | undefined) { if (!accept) return undefined const res: Record<string, string[]> = {} const extensions = accept .split(",") .map((ext) => ext.trim().replace(/^\*\./, ".")) for (const ext of extensions) { const mime = lookupMime(ext) if (mime) { const exts = res[mime] || (res[mime] = []) if (!exts.includes(ext)) exts.push(ext) } } return res } function FilesFormInput() { const script = useScript() const { accept } = script || {} if (!script) return ( <vscode-form-group> <vscode-label>Select a script to run.</vscode-label> </vscode-form-group> ) if (accept === "none") return ( <vscode-form-group> <vscode-label>No files accepted.</vscode-label> </vscode-form-group> ) return <FilesDropZone script={script} /> } function FilesDropZone(props: { script: PromptScript }) { const { script } = props const { accept } = script || {} const { acceptedFiles, isDragActive, getRootProps, getInputProps } = useDropzone({ multiple: true, accept: acceptToAccept(accept) }) const { importedFiles, setImportedFiles } = useApi() useEffect(() => { const newImportedFiles = [...importedFiles] if (acceptedFiles?.length) { for (const f of acceptedFiles) if (!newImportedFiles.find((nf) => nf.path === f.path)) { ;(f as ImportedFile).selected = true newImportedFiles.push(f) } } if (newImportedFiles.length !== importedFiles.length) setImportedFiles(newImportedFiles) }, [importedFiles, acceptedFiles]) return ( <> <vscode-form-group> <vscode-label>Files</vscode-label> <vscode-multi-select onChange={(e) => { e.preventDefault() const target = e.target as VscodeMultiSelect const newImportedFiles = [...importedFiles] const selected = target.selectedIndexes for (let i = 0; i < newImportedFiles.length; i++) { newImportedFiles[i].selected = selected.includes(i) } setImportedFiles(newImportedFiles) }} > {importedFiles.map((file) => ( <vscode-option key={file.path} value={file.path} selected={file.selected} > {file.name} ({prettyBytes(file.size)}) </vscode-option> ))} </vscode-multi-select> </vscode-form-group> <vscode-form-group className="dropzone" style={{ ...(isDragActive ? { outline: "2px dashed #333" } : {}), }} {...getRootProps({ className: "dropzone" })} > <input {...getInputProps()} /> <vscode-form-helper> {isDragActive ? `Drop the files here ...` : `Drag 'n' drop some files here, or click to select files ${accept ? `(${accept})` : ""}`} </vscode-form-helper> </vscode-form-group> </> ) } function ScriptDescription() { const script = useScript() if (!script) return null const { title, description } = script return ( <vscode-form-helper> {title ? <b>{title}</b> : null} {description ? ( <Markdown readme={true} className="no-margins"> {description} </Markdown> ) : null} </vscode-form-helper> ) } function RefreshButton() { const { refresh } = useApi() return ( <ActionButton name="refresh" label="reload script list" onClick={refresh} /> ) } function ScriptSelect() { const scripts = useScripts() const { scriptid, setScriptid } = useScriptId() const { refresh } = useApi() const { filename } = useScript() || {} return ( <vscode-form-group> <vscode-label style={{ padding: 0 }}> <GenAIScriptLogo height="2em" /> </vscode-label> <vscode-single-select id="script-selector" value={scriptid} combobox filter="fuzzy" onvsc-change={(e: Event) => { const target = e.target as HTMLSelectElement setScriptid(target.value) }} title={filename} > {scripts .filter((s) => !s.isSystem && !s.unlisted) .map(({ id, title }) => ( <vscode-option value={id} selected={scriptid === id} description={title} > {id} </vscode-option> ))} </vscode-single-select> <ScriptDescription /> </vscode-form-group> ) } function ScriptForm() { return ( <vscode-collapsible open title="Script"> <RefreshButton /> <ScriptSelect /> <FilesFormInput /> <PromptParametersFields /> <RunScriptButton /> </vscode-collapsible> ) } function RunButtonOptions() { const script = useScript() const { parameters, setParameters } = useApi() const { inputSchema } = script || {} if (!Object.keys(inputSchema?.properties || {}).length) return null const scriptParameters = inputSchema.properties[ "script" ] as JSONSchemaObject const runOptions: [string, JSONSchemaBoolean][] = scriptParameters?.properties ? Object.entries( scriptParameters.properties as Record< string, JSONSchemaSimpleType > ) .filter( ([, f]) => f.type === "boolean" && f.uiType === "runOption" ) .map(([k, f]) => [k, f as JSONSchemaBoolean]) : undefined if (!runOptions) return null return ( <vscode-form-group> <vscode-label></vscode-label> <JSONBooleanOptionsGroup properties={Object.fromEntries(runOptions)} value={parameters} fieldPrefix={""} onChange={setParameters} /> </vscode-form-group> ) } function PromptParametersFields() { const script = useScript() const { parameters, setParameters } = useApi() const { inputSchema } = script || {} if (!Object.keys(inputSchema?.properties || {}).length) return null const scriptParameters = inputSchema.properties[ "script" ] as JSONSchemaObject const systemParameters = Object.entries(inputSchema.properties).filter( ([k]) => k !== "script" ) return ( <> {scriptParameters && ( <JSONSchemaObjectForm schema={scriptParameters} value={parameters} fieldPrefix={""} onChange={setParameters} /> )} {!!systemParameters.length && ( <vscode-collapsible className="collapsible" title="System Parameters" > {Object.entries(inputSchema.properties) .filter(([k]) => k !== "script") .map(([key, fieldSchema]) => { return ( <JSONSchemaObjectForm schema={fieldSchema as JSONSchemaObject} value={parameters} fieldPrefix={`${key}.`} onChange={setParameters} /> ) })} </vscode-collapsible> )} </> ) } function ModelConfigurationTabPanel() { const { options, setOptions } = useApi() const env = useEnv() const { providers } = env || {} const models = providers?.flatMap( (p) => p.models?.map((m) => `${p.provider}:${m.id}`) || [] ) || [] const schema: JSONSchemaObject = { type: "object", properties: { cache: { type: "boolean", description: `Enable cache for LLM requests`, default: false, }, model: { type: "string", description: "'large' model identifier; this is the default model when no model is configured in the script.", default: "large", uiSuggestions: models, }, smallModel: { type: "string", description: "'small' model identifier", default: "small", uiSuggestions: models, }, visionModel: { type: "string", description: "'vision' model identifier", default: "vision", uiSuggestions: models, }, temperature: { type: "number", description: "LLM temperature from 0 to 2", minimum: 0, maximum: 2, default: 0.8, }, logprobs: { type: "boolean", description: "Enable reporting log probabilities for each token", default: false, }, topLogprobs: { type: "integer", description: "Enables reporting log probabilities for alternate tokens", minimum: 0, maximum: 5, default: 0, }, }, } return ( <> <vscode-tab-header slot="header">Model</vscode-tab-header> <vscode-tab-panel> <JSONSchemaObjectForm schema={schema} value={options} fieldPrefix="" onChange={setOptions} /> </vscode-tab-panel> </> ) } function ConfigurationTabPanel() { return ( <vscode-collapsible className="collapsible" title="Configuration"> <vscode-tabs panel> <ModelConfigurationTabPanel /> <ProviderConfigurationTabPanel /> <ChatCompletationTabPanel /> </vscode-tabs> </vscode-collapsible> ) } function ChatCompletationTabPanel() { const models = useModels() const [model, setModel] = useState<string>("") const [userContent, setUserContent] = useState<string>( "write a poem using emojis" ) const [response, setResponse] = useState< ChatCompletion | { error?: string } >(undefined) const [controller, setController] = useState<AbortController | undefined>( undefined ) const state = controller ? "running" : undefined const title = state === "running" ? "Abort" : "Run" const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (controller) controller.abort() const c = new AbortController() setController(c) setResponse(undefined) try { const body = { model: model, messages: [{ role: "user", content: userContent }], } const resp = await fetch("/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", Authorization: apiKey, }, signal: c.signal, body: JSON.stringify(body), }) if (c.signal.aborted) { console.log(`openai request aborted`) return } if (!resp.ok) setResponse({ error: `Error: ${resp.status} ${resp.statusText}`, }) else setResponse(await resp.json()) } catch (e) { c.abort() setResponse({ error: `Error: ${e}` }) } finally { setController(undefined) } } return ( <> <vscode-tab-header slot="header">OpenAI API</vscode-tab-header> <vscode-tab-panel> <form onSubmit={handleSubmit}> <vscode-form-group> <vscode-label>model:</vscode-label> <vscode-single-select value={model} combobox filter="fuzzy" creatable onvsc-change={(e: Event) => { const target = e.target as HTMLSelectElement setModel(target.value) }} > <vscode-option value=""></vscode-option> {models?.data?.map((m) => ( <vscode-option key={m.id} value={m.id}> {m.id} </vscode-option> ))} </vscode-single-select> </vscode-form-group> <vscode-form-container> <vscode-form-group> <vscode-label>user:</vscode-label> <vscode-textarea rows={5} value={userContent} onvsc-change={(e: Event) => { const target = e.target as HTMLTextAreaElement setUserContent(target.value) }} placeholder="user message" ></vscode-textarea> </vscode-form-group> <vscode-form-group> <vscode-label></vscode-label> <vscode-button icon={ state === "running" ? "stop-circle" : "play" } type="submit" title={title} > {title} </vscode-button> <ModelOptionsFormHelper /> </vscode-form-group> {response ? ( <vscode-form-group> <vscode-label></vscode-label> <vscode-tabs> <MarkdownPreviewTabs text={JSON.stringify(response, null, 2)} renderText={ (response as ChatCompletion) ?.choices?.length ? ( response as ChatCompletion )?.choices .map( ({ message }) => message.content ) .join("\n<br/>\n") : ` \`\`\`json ${JSON.stringify(response, null, 2) || ""} \`\`\` ` } /> </vscode-tabs> </vscode-form-group> ) : null} </vscode-form-container> </form> </vscode-tab-panel> </> ) } function ProviderConfigurationTabPanel() { const env = useEnv() const { providers } = env || {} if (!providers?.length) return null const ref = useRef<VscodeTree | null>(null) useEffect(() => { if (!ref.current) return if (!providers) ref.current.data = [] else { const icons = { leaf: "robot", branch: "chevron-right", open: "chevron-down", } const missingIcons = { branch: "circle-large", leaf: "circle-large", open: "chevron-down", } const errorIcons = { branch: "error", leaf: "error", open: "chevron-down", } const PROVIDERS = CONFIGURATION.providers const data: TreeItem[] = PROVIDERS.filter( ({ id }) => id !== MODEL_PROVIDER_GITHUB_COPILOT_CHAT ) .map((def) => ({ ...(providers.find((p) => p.provider === def.id) || {}), detail: def.detail, provider: def.id, url: def.url, missing: !providers.find((p) => p.provider === def.id), })) .map( (r) => ({ icons: r.error ? errorIcons : r.missing ? missingIcons : icons, label: r.provider, description: r.error ?? r.base, tooltip: r.detail, subItems: r.models?.map( ({ id, url }) => ({ label: id, description: url, }) satisfies TreeItem ), }) satisfies TreeItem ) ref.current.data = data } }, [providers]) return ( <> <vscode-tab-header slot="header">LLM Providers</vscode-tab-header> <vscode-tab-panel> <vscode-tree indent-guides indent={8} ref={ref} /> <vscode-label> <a href="https://microsoft.github.io/genaiscript/getting-started/configuration/"> Configuration documentation </a> </vscode-label> </vscode-tab-panel> </> ) } function ClientReadyStateLabel() { const readyState = useClientReadyState() if (readyState === "open") return null return ( <vscode-label title={`server connection status: ${readyState}`}> {readyState} </vscode-label> ) } function ModelOptionsFormHelper() { const { options } = useApi() return ( <> <vscode-form-helper> {Object.entries(options) .map(([key, value]) => `${key}: ${value}`) .join(", ")} </vscode-form-helper> </> ) } function RunScriptButton() { const { scriptid } = useScriptId() const { state } = useRunner() if (!scriptid) return null const title = state === "running" ? "Abort" : "Run" return ( <> <vscode-form-group> <ClientReadyStateLabel /> <vscode-button icon={state === "running" ? "stop-circle" : "play"} type="submit" title={title} > {title} </vscode-button> <ModelOptionsFormHelper /> </vscode-form-group> <RunButtonOptions /> </> ) } function ScriptView() { return ( <Suspense> <RunForm /> <ConfigurationTabPanel /> </Suspense> ) } function RunForm() { const { run, cancel, state } = useRunner() const { files, importedFiles, parameters, options } = useApi() const handleSubmit = (e: React.FormEvent) => { e.preventDefault() if (state === "running") cancel() else run(files, importedFiles, parameters, options) } useSyncProjectScript() return ( <form onSubmit={handleSubmit}> <ScriptForm /> </form> ) } function ResultsView() { const [showRuns, setShowRuns] = useState(false) const handleShowRuns = () => setShowRuns((prev) => !prev) return ( <vscode-collapsible className="collapsible" open title="Result"> <ActionButton name="history" label={showRuns ? "Show previous runs" : "Hide previous runs"} onClick={handleShowRuns} /> {showRuns && <RunResultSelector />} <Suspense> <ResultsTabs /> </Suspense> </vscode-collapsible> ) } function WebApp() { switch (viewMode) { case "results": return ( <Suspense> <ResultsTabs /> </Suspense> ) default: return ( <div style={{ minHeight: "100vh" }}> <ApiProvider> {!hosted ? <ProjectView /> : null} <ScriptView /> </ApiProvider> <ResultsView /> </div> ) } } export default function App() { return ( <ScriptProvider> <RunClientProvider> <RunnerProvider> <Suspense> <WebApp /> </Suspense> </RunnerProvider> </RunClientProvider> </ScriptProvider> ) }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/microsoft/genaiscript'

If you have feedback or need assistance with the MCP directory API, please join our Discord server