Skip to content

Commit af36c1b

Browse files
committed
Add data analytics skill documentation with chart examples and usage instructions
1 parent 683a830 commit af36c1b

5 files changed

Lines changed: 1016 additions & 6 deletions

File tree

custom/Message.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
<div
33
class="max-w-[80%] flex px-4 m-2 rounded-xl border border-gray-200 dark:border-gray-700"
44
@click="handleMarkdownLinkClick"
5-
:class="props.role === 'user' ? 'bg-lightListTableHeading dark:bg-darkListTableHeading self-end'
5+
:class="[
6+
hasVegaLite ? 'w-full' : '',
7+
props.role === 'user' ? 'bg-lightListTableHeading dark:bg-darkListTableHeading self-end'
68
: isTypeReasoning || isTypeToolCall ? 'bg-transparent border-none self-start'
7-
: 'bg-blue-100 dark:bg-blue-700/10 self-start'"
9+
: 'bg-blue-100 dark:bg-blue-700/10 self-start'
10+
]"
811
>
912
<IncremarkContent
10-
class="text-wrap break-words max-w-full"
13+
class="text-wrap break-words w-full max-w-full"
1114
v-if="content && props.type === 'text'"
1215
:content="content"
1316
:is-finished="isFinished"
@@ -88,6 +91,7 @@
8891
const content = computed(() => props.message)
8992
const isFinished = computed(() => props.state === 'done')
9093
const isThoughtsExpanded = ref(false)
94+
const hasVegaLite = computed(() => props.type === 'text' && props.message.includes('```vega-lite'))
9195
9296
const isTypeReasoning = computed(() => props.type === 'reasoning')
9397
const isTypeToolCall = computed(() => props.type === 'data-tool-call')

custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@
1818

1919
<div class="incremark-shiki-body">
2020
<div
21-
v-if="renderedHtml"
21+
v-if="shouldRenderVega && !renderedHtml"
22+
ref="vegaContainer"
23+
class="incremark-vega"
24+
/>
25+
26+
<div
27+
v-else-if="renderedHtml"
2228
class="incremark-shiki-html"
2329
v-html="renderedHtml"
2430
/>
@@ -31,6 +37,7 @@
3137
<script setup lang="ts">
3238
import type { Code } from 'mdast';
3339
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
40+
import embed from 'vega-embed';
3441
3542
import { highlightCodeSnippetHtml, type IncremarkCodeTheme } from './incremarkCodeHighlight';
3643
@@ -53,15 +60,18 @@ const props = withDefaults(defineProps<{
5360
const renderedHtml = ref('');
5461
const copied = ref(false);
5562
const prefersDarkMode = ref(isDarkDocument());
63+
const vegaContainer = ref<HTMLDivElement | null>(null);
5664
5765
let copyResetTimeout: number | null = null;
5866
let renderRequestId = 0;
5967
let scheduledFrameId: number | null = null;
6068
let themeObserver: MutationObserver | null = null;
69+
let vegaResult: { finalize: () => void } | null = null;
6170
6271
const sourceCode = computed(() => props.node.value ?? '');
6372
const language = computed(() => props.node.lang?.trim().toLowerCase() || 'text');
6473
const languageLabel = computed(() => props.node.lang?.trim() || 'text');
74+
const shouldRenderVega = computed(() => language.value === 'vega-lite' && props.blockStatus === 'completed');
6575
const codeTheme = computed<IncremarkCodeTheme>(() => {
6676
const requestedTheme = props.theme ?? (prefersDarkMode.value ? props.darkTheme : props.lightTheme);
6777
@@ -77,7 +87,7 @@ const codeTheme = computed<IncremarkCodeTheme>(() => {
7787
});
7888
7989
watch(
80-
[sourceCode, language, codeTheme, () => props.disableHighlight],
90+
[sourceCode, language, codeTheme, () => props.disableHighlight, () => props.blockStatus],
8191
() => {
8292
scheduleHighlight();
8393
},
@@ -86,6 +96,7 @@ watch(
8696
8797
onMounted(() => {
8898
if (typeof MutationObserver === 'undefined' || typeof document === 'undefined') {
99+
scheduleHighlight();
89100
return;
90101
}
91102
@@ -97,10 +108,13 @@ onMounted(() => {
97108
attributes: true,
98109
attributeFilter: ['class'],
99110
});
111+
112+
scheduleHighlight();
100113
});
101114
102115
onBeforeUnmount(() => {
103116
renderRequestId += 1;
117+
clearVega();
104118
105119
if (copyResetTimeout !== null) {
106120
window.clearTimeout(copyResetTimeout);
@@ -154,6 +168,45 @@ function scheduleHighlight() {
154168
async function renderHighlight() {
155169
const requestId = ++renderRequestId;
156170
171+
if (shouldRenderVega.value) {
172+
renderedHtml.value = '';
173+
174+
if (!sourceCode.value || !vegaContainer.value) {
175+
return;
176+
}
177+
178+
try {
179+
clearVega();
180+
const spec = JSON.parse(sourceCode.value);
181+
182+
if (spec.width == null) {
183+
spec.width = 'container';
184+
}
185+
186+
if (spec.autosize == null) {
187+
spec.autosize = { type: 'fit-x', contains: 'padding' };
188+
}
189+
190+
const result = await embed(vegaContainer.value, spec, {
191+
actions: false,
192+
renderer: 'svg',
193+
});
194+
195+
if (requestId !== renderRequestId) {
196+
result.finalize();
197+
return;
198+
}
199+
200+
vegaResult = result;
201+
return;
202+
} catch (error) {
203+
clearVega();
204+
console.error('Failed to render Vega-Lite block', error);
205+
}
206+
} else {
207+
clearVega();
208+
}
209+
157210
if (!sourceCode.value || props.disableHighlight) {
158211
renderedHtml.value = '';
159212
return;
@@ -177,6 +230,15 @@ async function renderHighlight() {
177230
function isDarkDocument(): boolean {
178231
return typeof document !== 'undefined' && document.documentElement.classList.contains('dark');
179232
}
233+
234+
function clearVega() {
235+
vegaResult?.finalize();
236+
vegaResult = null;
237+
238+
if (vegaContainer.value) {
239+
vegaContainer.value.innerHTML = '';
240+
}
241+
}
180242
</script>
181243

182244
<style scoped>
@@ -265,6 +327,11 @@ function isDarkDocument(): boolean {
265327
overflow-x: auto;
266328
}
267329
330+
.incremark-vega {
331+
padding: 18px;
332+
width: 100%;
333+
}
334+
268335
.incremark-shiki-fallback {
269336
margin: 0;
270337
padding: 18px;
@@ -298,4 +365,12 @@ function isDarkDocument(): boolean {
298365
:deep(.incremark-shiki-html .line) {
299366
min-height: 1.65em;
300367
}
368+
369+
:deep(.incremark-vega .vega-embed) {
370+
width: 100%;
371+
}
372+
373+
:deep(.incremark-vega){
374+
padding: 0;
375+
}
301376
</style>

custom/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"ai": "^6.0.158",
2222
"dompurify": "^3.3.3",
2323
"katex": "^0.16.45",
24-
"marked": "^18.0.0"
24+
"marked": "^18.0.0",
25+
"vega-embed": "^7.1.0"
2526
}
2627
}

0 commit comments

Comments
 (0)