diff --git a/packages/app/src/components/chat/ChatMessage.vue b/packages/app/src/components/chat/ChatMessage.vue
index 44ccd43e..0b4d2d29 100644
--- a/packages/app/src/components/chat/ChatMessage.vue
+++ b/packages/app/src/components/chat/ChatMessage.vue
@@ -214,6 +214,15 @@
/>
+
+
+
+
{
return extractEvents(props.message.content)
})
+const runnableCodeBlocks = computed(() => {
+ if (isUser.value) return []
+ return extractRunnableCodeBlocks(props.message.content)
+})
+
const isCodeResponse = computed(() =>
!isUser.value && props.triggeringQuery.trim().toLowerCase() === '/code'
)
diff --git a/packages/app/src/components/renderers/CodeRunner.vue b/packages/app/src/components/renderers/CodeRunner.vue
new file mode 100644
index 00000000..fb0e0480
--- /dev/null
+++ b/packages/app/src/components/renderers/CodeRunner.vue
@@ -0,0 +1,133 @@
+
+
+
+
+
{{ language }}
+
+
+
+
+
+
+
{{ code }}
+
+
+
+
+
+
+
Console
+
+ {{ entry.text }}
+
+
+
+
+
+
diff --git a/packages/app/src/composables/useCodeBlockExtractor.ts b/packages/app/src/composables/useCodeBlockExtractor.ts
new file mode 100644
index 00000000..4f5e4ee1
--- /dev/null
+++ b/packages/app/src/composables/useCodeBlockExtractor.ts
@@ -0,0 +1,32 @@
+/**
+ * Extract runnable code blocks (HTML, JS, CSS) from markdown text.
+ */
+
+export interface CodeBlock {
+ language: string
+ code: string
+}
+
+const RUNNABLE_LANGS = new Set(['html', 'javascript', 'js', 'css'])
+
+const FENCED_CODE_RE = /```(html|javascript|js|css)\n([\s\S]*?)```/gi
+
+export function extractRunnableCodeBlocks(text: string): CodeBlock[] {
+ const results: CodeBlock[] = []
+ let m: RegExpExecArray | null
+ const re = new RegExp(FENCED_CODE_RE.source, FENCED_CODE_RE.flags)
+
+ while ((m = re.exec(text)) !== null) {
+ const language = m[1].toLowerCase()
+ const code = m[2].trim()
+ if (RUNNABLE_LANGS.has(language) && code.length > 0) {
+ results.push({ language, code })
+ }
+ }
+
+ return results
+}
+
+export function hasRunnableCode(text: string): boolean {
+ return FENCED_CODE_RE.test(text)
+}