The app's own interface: agent chats first, two explorers, a viewer
Plain HTML, CSS and ES modules in ui/, no build step. The sidebar opens on agent chats (this computer first), with chats on the server as the second tab; the transcript draws the /api/v2 steps, follows a reply as it is written, answers cards, and starts agent chats in one of the agent's folders. Two explorers kept apart (this computer's, the server's) and a viewer: Markdown drawn by a safe renderer that never parses HTML, code in read-only CodeMirror with the server's token colours, HTML in the sealed frame, show calls opened in it. Settings for the sign-in, the agent and its folders, notifications and updates; no admin pages. Layout on fixed and shared tracks; the sidebar is a drawer below 48rem, the viewer goes over the chat below 64rem. All 124 sentences in the server's nine languages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
20 files changed
+4275
-758
No files matched your search
+104
@@ -0,0 +1,104 @@
|
||||
// Source in the viewer: CodeMirror, read-only, from the server's own vendored build (ui/vendor,
|
||||
// copied and pinned by scripts/fetch-vendor.sh). The look is the server's editor's: colours from
|
||||
// the theme's tokens, never CodeMirror's own. Loaded the first time a file is shown.
|
||||
|
||||
let cm = null
|
||||
|
||||
async function load() {
|
||||
if (cm) return cm
|
||||
const [state, view, language, search, commands, highlight] = await Promise.all([
|
||||
import("../vendor/cm-state.mjs"),
|
||||
import("../vendor/cm-view.mjs"),
|
||||
import("../vendor/cm-language.mjs"),
|
||||
import("../vendor/cm-search.mjs"),
|
||||
import("../vendor/cm-commands.mjs"),
|
||||
import("../vendor/lezer-highlight.mjs"),
|
||||
])
|
||||
const { EditorView } = view
|
||||
const { HighlightStyle, syntaxHighlighting } = language
|
||||
const { tags } = highlight
|
||||
// As the server's static/js/editor.js has them.
|
||||
const look = EditorView.theme({
|
||||
"&": { color: "var(--ink)", backgroundColor: "var(--code-bg)", fontSize: "var(--text-sm)", height: "100%" },
|
||||
".cm-scroller": { fontFamily: "var(--font-mono)", lineHeight: "var(--leading-normal)" },
|
||||
".cm-content": { caretColor: "var(--accent)" },
|
||||
".cm-gutters": { backgroundColor: "var(--code-bg)", color: "var(--ink-faint)", border: "none" },
|
||||
".cm-activeLine, .cm-activeLineGutter": { backgroundColor: "var(--surface-hover)" },
|
||||
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection": { backgroundColor: "var(--selection)" },
|
||||
"&.cm-focused": { outline: "none" },
|
||||
".cm-panels": { backgroundColor: "var(--surface)", color: "var(--ink)" },
|
||||
".cm-searchMatch": { backgroundColor: "var(--accent-soft)" },
|
||||
})
|
||||
const colours = HighlightStyle.define([
|
||||
{ tag: [tags.keyword, tags.operatorKeyword, tags.modifier], color: "var(--accent)" },
|
||||
{ tag: [tags.string, tags.special(tags.string)], color: "var(--leaf)" },
|
||||
{ tag: [tags.comment], color: "var(--ink-faint)", fontStyle: "italic" },
|
||||
{ tag: [tags.number, tags.bool, tags.null, tags.atom], color: "var(--warning)" },
|
||||
{ tag: [tags.function(tags.variableName), tags.definition(tags.variableName)], color: "var(--ink)", fontWeight: "600" },
|
||||
{ tag: [tags.heading], color: "var(--accent)", fontWeight: "700" },
|
||||
{ tag: [tags.link, tags.url], color: "var(--accent)", textDecoration: "underline" },
|
||||
{ tag: [tags.tagName, tags.typeName, tags.className], color: "var(--accent-hover)" },
|
||||
{ tag: [tags.invalid], color: "var(--danger)" },
|
||||
])
|
||||
cm = {
|
||||
state,
|
||||
view,
|
||||
base: [
|
||||
view.lineNumbers(),
|
||||
view.highlightActiveLine(),
|
||||
view.highlightActiveLineGutter(),
|
||||
view.drawSelection(),
|
||||
view.keymap.of([...search.searchKeymap, ...commands.defaultKeymap]),
|
||||
search.highlightSelectionMatches(),
|
||||
state.EditorState.readOnly.of(true),
|
||||
view.EditorView.editable.of(false),
|
||||
view.EditorView.lineWrapping,
|
||||
look,
|
||||
syntaxHighlighting(colours),
|
||||
],
|
||||
}
|
||||
return cm
|
||||
}
|
||||
|
||||
/** The language support for a name as `show` gives it; nothing for one without support. */
|
||||
async function language(name) {
|
||||
try {
|
||||
switch (name) {
|
||||
case "markdown":
|
||||
return (await import("../vendor/cm-lang-markdown.mjs")).markdown()
|
||||
case "python":
|
||||
return (await import("../vendor/cm-lang-python.mjs")).python()
|
||||
case "javascript":
|
||||
case "jsx":
|
||||
return (await import("../vendor/cm-lang-javascript.mjs")).javascript({ jsx: name === "jsx" })
|
||||
case "typescript":
|
||||
case "tsx":
|
||||
return (await import("../vendor/cm-lang-javascript.mjs")).javascript({ typescript: true, jsx: name === "tsx" })
|
||||
case "html":
|
||||
return (await import("../vendor/cm-lang-html.mjs")).html()
|
||||
case "css":
|
||||
case "scss":
|
||||
return (await import("../vendor/cm-lang-css.mjs")).css()
|
||||
case "json":
|
||||
return (await import("../vendor/cm-lang-json.mjs")).json()
|
||||
default:
|
||||
return []
|
||||
}
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/** Show `text` in `parent`, read-only, at `line` when given. Returns something to `destroy()`. */
|
||||
export async function codeView(parent, text, { lang = "", line = 0 } = {}) {
|
||||
const { state, view, base } = await load()
|
||||
const ed = new view.EditorView({
|
||||
parent,
|
||||
state: state.EditorState.create({ doc: text, extensions: [base, await language(lang)] }),
|
||||
})
|
||||
if (line > 0 && line <= ed.state.doc.lines) {
|
||||
const at = ed.state.doc.line(line).from
|
||||
ed.dispatch({ selection: { anchor: at }, effects: view.EditorView.scrollIntoView(at, { y: "center" }) })
|
||||
}
|
||||
return ed
|
||||
}
|
||||
Reference in new issue
Block a user