From a3ffd8269709fcc6081bbb32b64a9d37e22c7ba3 Mon Sep 17 00:00:00 2001 From: Antonio Marangi Date: Wed, 8 Jul 2026 17:56:39 +0200 Subject: [PATCH 1/4] filter out unknown capabilities, assume markdown --- mock/config.json | 2 +- package-lock.json | 42 -------------------------------- specification.md | 6 ++--- src/components/SettingsPanel.vue | 4 +-- src/stores/chat.ts | 9 ++++++- 5 files changed, 14 insertions(+), 49 deletions(-) diff --git a/mock/config.json b/mock/config.json index 0dd89f5..2e46ee9 100644 --- a/mock/config.json +++ b/mock/config.json @@ -1,7 +1,7 @@ { "openAIBaseURL": "http://localhost:8080/v1", "capabilities": [ - "text:markdown", + "text", "vision" ], "instanceName": "My Model", diff --git a/package-lock.json b/package-lock.json index 8998938..07d897d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1207,9 +1207,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1227,9 +1224,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1247,9 +1241,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1267,9 +1258,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1287,9 +1275,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1307,9 +1292,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1327,9 +1309,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1347,9 +1326,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1847,9 +1823,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1867,9 +1840,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1887,9 +1857,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1907,9 +1874,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1927,9 +1891,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1947,9 +1908,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ diff --git a/specification.md b/specification.md index fdb9402..26b11ba 100644 --- a/specification.md +++ b/specification.md @@ -46,7 +46,7 @@ If the model name cannot be retrieved from the /models endpoint of the OpenAI AP ## Configuration The web application should consume static configurations of the following parameters: - openAIBaseURL(string): OpenAI base URL -- capabilities(string array): Model capabilities (text, vision, audio, etc) - only text and vision should be supported for now. +- capabilities(string array): Model capabilities (text, vision, audio, thinking, etc) - any unsupported capabilities should be ignored and not shown but not cause the application to error out - instanceName(string): used to construct the UI title - engineName(string): for display in the UI - chatFormat(string): the markup used by the model, e.g. "markdown" or "plaintext" @@ -97,8 +97,9 @@ curl http://localhost:8000/v1/chat/completions \ ### Chat format -The capabilities field can contain a special value `text:markdown`. +If the capabilities field contains `text` assume it to be Markdown. This field indicates that the markup used by the respective model is Markdown. +If the `text` capability is present, format the prompts, reasoning and responses following Markdown. Always sanitize the input and output to prevent XSS vulnerabilities. @@ -106,7 +107,6 @@ If this key is not set, assume plain text is used. The prompts, reasoning and responses should be rendered directly, and not be interpreted as any markup. Therefore, any characters that could be interpreted as HTML should be escaped, and line breaks should be preserved. -If the `text:markdown` capability is present, format the prompts, reasoning and responses following Markdown. As far as practically possible, use standard Vanilla Framework [typography](https://vanillaframework.io/docs/base/typography) to format the Markdown styles. Use `markdown-it` as the Markdown parser, and apply syntax highlighting using `highlight.js` for code blocks in the responses. diff --git a/src/components/SettingsPanel.vue b/src/components/SettingsPanel.vue index 6ab8ea1..c91790d 100644 --- a/src/components/SettingsPanel.vue +++ b/src/components/SettingsPanel.vue @@ -45,9 +45,9 @@ const store = useChatStore() Capabilities -
+
{{ cap }} diff --git a/src/stores/chat.ts b/src/stores/chat.ts index a2e4c4c..0f89e5f 100644 --- a/src/stores/chat.ts +++ b/src/stores/chat.ts @@ -92,7 +92,13 @@ export const useChatStore = defineStore('chat', () => { thinkingInstanceNames.some((name) => config.value.instanceName?.includes(name) ?? false), ) - const isMarkdown = computed(() => config.value.capabilities?.includes('text:markdown') ?? false) + const isMarkdown = computed(() => config.value.capabilities?.includes('text') ?? false) + + const supportedCapabilities = ['text', 'vision'] + + const displayedCapabilities = computed(() => + config.value.capabilities?.filter((cap) => supportedCapabilities.includes(cap)) ?? [], + ) // ── Actions ─────────────────────────────────────────────────────────────── async function fetchConfig(): Promise { @@ -147,6 +153,7 @@ export const useChatStore = defineStore('chat', () => { supportsVision, showThinkingToggle, isMarkdown, + displayedCapabilities, fetchConfig, fetchModelName, openLightbox, From 2be50a1a808a0234c1bb65c0a7e935d86a879316 Mon Sep 17 00:00:00 2001 From: Antonio Marangi Date: Thu, 9 Jul 2026 11:44:17 +0200 Subject: [PATCH 2/4] revert capabilities filter --- specification.md | 2 +- src/components/SettingsPanel.vue | 4 ++-- src/stores/chat.ts | 9 +-------- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/specification.md b/specification.md index 26b11ba..9ce0106 100644 --- a/specification.md +++ b/specification.md @@ -46,7 +46,7 @@ If the model name cannot be retrieved from the /models endpoint of the OpenAI AP ## Configuration The web application should consume static configurations of the following parameters: - openAIBaseURL(string): OpenAI base URL -- capabilities(string array): Model capabilities (text, vision, audio, thinking, etc) - any unsupported capabilities should be ignored and not shown but not cause the application to error out +- capabilities(string array): Model capabilities (text, vision, audio, thinking, etc) - instanceName(string): used to construct the UI title - engineName(string): for display in the UI - chatFormat(string): the markup used by the model, e.g. "markdown" or "plaintext" diff --git a/src/components/SettingsPanel.vue b/src/components/SettingsPanel.vue index c91790d..6ab8ea1 100644 --- a/src/components/SettingsPanel.vue +++ b/src/components/SettingsPanel.vue @@ -45,9 +45,9 @@ const store = useChatStore() Capabilities -
+
{{ cap }} diff --git a/src/stores/chat.ts b/src/stores/chat.ts index 0f89e5f..ab256bc 100644 --- a/src/stores/chat.ts +++ b/src/stores/chat.ts @@ -91,15 +91,9 @@ export const useChatStore = defineStore('chat', () => { const showThinkingToggle = computed(() => thinkingInstanceNames.some((name) => config.value.instanceName?.includes(name) ?? false), ) - + // markdown is assumed when text capability is present const isMarkdown = computed(() => config.value.capabilities?.includes('text') ?? false) - const supportedCapabilities = ['text', 'vision'] - - const displayedCapabilities = computed(() => - config.value.capabilities?.filter((cap) => supportedCapabilities.includes(cap)) ?? [], - ) - // ── Actions ─────────────────────────────────────────────────────────────── async function fetchConfig(): Promise { const response = await fetch(import.meta.env.VITE_CONFIG_URL) @@ -153,7 +147,6 @@ export const useChatStore = defineStore('chat', () => { supportsVision, showThinkingToggle, isMarkdown, - displayedCapabilities, fetchConfig, fetchModelName, openLightbox, From b1de973f63e98e558f048914432458c7cc76a549 Mon Sep 17 00:00:00 2001 From: Antonio Marangi Date: Mon, 13 Jul 2026 12:07:49 +0200 Subject: [PATCH 3/4] remove markdown check and assume it --- mock/config.json | 2 +- specification.md | 9 ++------- src/components/MessageBubble.vue | 32 ++++++-------------------------- src/stores/chat.ts | 3 --- 4 files changed, 9 insertions(+), 37 deletions(-) diff --git a/mock/config.json b/mock/config.json index 6d75262..975f17e 100644 --- a/mock/config.json +++ b/mock/config.json @@ -1,7 +1,7 @@ { "openAIBaseURL": "http://localhost:8080/v1", "capabilities": [ - "text:markdown", + "text", "vision", "thinking" ], diff --git a/specification.md b/specification.md index 787283d..4eca32c 100644 --- a/specification.md +++ b/specification.md @@ -97,16 +97,11 @@ curl http://localhost:8000/v1/chat/completions \ ### Chat format -If the capabilities field contains `text` assume it to be Markdown. -This field indicates that the markup used by the respective model is Markdown. -If the `text` capability is present, format the prompts, reasoning and responses following Markdown. +Always assume the markup used by the model is Markdown. +Format the prompts, reasoning and responses following Markdown. Always sanitize the input and output to prevent XSS vulnerabilities. -If this key is not set, assume plain text is used. -The prompts, reasoning and responses should be rendered directly, and not be interpreted as any markup. -Therefore, any characters that could be interpreted as HTML should be escaped, and line breaks should be preserved. - As far as practically possible, use standard Vanilla Framework [typography](https://vanillaframework.io/docs/base/typography) to format the Markdown styles. Use `markdown-it` as the Markdown parser, and apply syntax highlighting using `highlight.js` for code blocks in the responses. diff --git a/src/components/MessageBubble.vue b/src/components/MessageBubble.vue index 035271f..dea9913 100644 --- a/src/components/MessageBubble.vue +++ b/src/components/MessageBubble.vue @@ -32,28 +32,11 @@ const emit = defineEmits<{ const store = useChatStore() -function formatPlain(text: string | null | undefined): string { - if (!text) return '' - return DOMPurify.sanitize( - String(text) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(/\n/g, '
'), - ) -} - function formatMarkdown(text: string | null | undefined): string { if (!text) return '' return DOMPurify.sanitize(md.render(String(text))) } -function formatText(text: string | null | undefined): string { - return store.isMarkdown ? formatMarkdown(text) : formatPlain(text) -} - function getUserText(): string { const c = props.message.content if (Array.isArray(c)) { @@ -114,9 +97,8 @@ watch(
You
-
+
@@ -170,11 +151,10 @@ watch(
diff --git a/src/stores/chat.ts b/src/stores/chat.ts index fb2dff7..6fadc4a 100644 --- a/src/stores/chat.ts +++ b/src/stores/chat.ts @@ -89,8 +89,6 @@ export const useChatStore = defineStore('chat', () => { const showThinkingToggle = computed( () => config.value.capabilities?.includes('thinking') ?? false, ) - // markdown is assumed when text capability is present - const isMarkdown = computed(() => config.value.capabilities?.includes('text') ?? false) // ── Actions ─────────────────────────────────────────────────────────────── async function fetchConfig(): Promise { @@ -144,7 +142,6 @@ export const useChatStore = defineStore('chat', () => { uiTitle, supportsVision, showThinkingToggle, - isMarkdown, fetchConfig, fetchModelName, openLightbox, From b8616de7b13491469b4fd3e136f3ffe06f9d1a94 Mon Sep 17 00:00:00 2001 From: Antonio Marangi Date: Mon, 13 Jul 2026 13:55:22 +0200 Subject: [PATCH 4/4] restore package-lock --- package-lock.json | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/package-lock.json b/package-lock.json index e663a53..61d2199 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1207,6 +1207,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1224,6 +1227,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1241,6 +1247,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1258,6 +1267,9 @@ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1275,6 +1287,9 @@ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1292,6 +1307,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1309,6 +1327,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1326,6 +1347,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1823,6 +1847,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1840,6 +1867,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1857,6 +1887,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1874,6 +1907,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1891,6 +1927,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1908,6 +1947,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [