-
-
-
-
+
+
+
+
+
+
![]()
+
+
+
+
diff --git a/packages/stage-ui/package.json b/packages/stage-ui/package.json
index a216c6a32..f93548a23 100644
--- a/packages/stage-ui/package.json
+++ b/packages/stage-ui/package.json
@@ -100,8 +100,10 @@
"pixi-filters": "4",
"pixi-live2d-display": "^0.4.0",
"postprocessing": "^6.37.7",
+ "rehype-katex": "^7.0.1",
"rehype-stringify": "^10.0.1",
"reka-ui": "^2.4.1",
+ "remark-math": "^6.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"shiki": "^3.9.1",
diff --git a/packages/stage-ui/src/composables/llmmarkerParser.ts b/packages/stage-ui/src/composables/llmmarkerParser.ts
index 1e67333fa..3bd4d61f2 100644
--- a/packages/stage-ui/src/composables/llmmarkerParser.ts
+++ b/packages/stage-ui/src/composables/llmmarkerParser.ts
@@ -1,85 +1,68 @@
-enum States {
- Literal = 'literal',
- Special = 'special',
-}
-
-function peek(array: string, index: number, offset: number): string | undefined {
- if (index + offset < 0 || index + offset >= (array.length - 1))
- return ''
-
- return array[index + offset]
-}
-
+/**
+ * A streaming parser for LLM responses that contain special markers (e.g., for tool calls).
+ * This composable is designed to be efficient and robust, using a regular expression
+ * to handle special tags enclosed in `<|...|>`.
+ *
+ * @example
+ * const parser = useLlmmarkerParser({
+ * onLiteral: (text) => console.log('Literal:', text),
+ * onSpecial: (tagContent) => console.log('Special:', tagContent),
+ * });
+ *
+ * await parser.consume('This is some text <|tool_code|> and some more |> text.');
+ * await parser.end();
+ */
export function useLlmmarkerParser(options: {
onLiteral?: (literal: string) => void | Promise
onSpecial?: (special: string) => void | Promise
}) {
- let state = States.Literal
let buffer = ''
+ const tagRegex = /(<\|.*?\|>)/
return {
+ /**
+ * Consumes a chunk of text from the stream.
+ * It processes the internal buffer to find and emit complete literal and special parts.
+ * Incomplete parts are kept in the buffer to be processed with the next chunk.
+ * @param textPart The chunk of text to consume.
+ */
async consume(textPart: string) {
- for (let i = 0; i < textPart.length; i++) {
- let current = textPart[i]
- let newState: States = state
+ buffer += textPart
- // read
- if (current === '<' && peek(textPart, i, 1) === '|') {
- current += peek(textPart, i, 1)
- newState = States.Special
- i++
- }
- else if (current === '|' && peek(textPart, i, 1) === '>') {
- current += peek(textPart, i, 1)
- newState = States.Literal
- i++
- }
- else if (current === '<') {
- newState = States.Special
- }
- else if (current === '>') {
- newState = States.Literal
- }
+ // The regex splits the buffer by tags, keeping the tags in the result array.
+ const parts = buffer.split(tagRegex)
- // handle
- if (state === States.Literal && newState === States.Special) {
- if (buffer !== '') {
- await options.onLiteral?.(buffer)
- buffer = ''
- }
- }
- else if (state === States.Special && newState === States.Literal) {
- if (buffer !== '') {
- buffer += current
- await options.onSpecial?.(buffer)
- buffer = '' // Clear buffer when exiting Special state
- }
- }
+ // The last element of the array is the remainder of the string after the last
+ // complete tag. It could be a partial literal or a partial tag. We keep it
+ // in the buffer for the next consume call.
+ const processableParts = parts.slice(0, -1)
+ buffer = parts[parts.length - 1] || ''
- if (state === States.Literal && newState === States.Literal) {
- await options.onLiteral?.(current)
- buffer = ''
- }
- else if (state === States.Special && newState === States.Literal) {
- buffer = ''
+ for (const part of processableParts) {
+ if (!part)
+ continue // Skip empty strings that can result from the split.
+
+ // Check if the part is a tag or a literal.
+ if (tagRegex.test(part)) {
+ // Extract the content from inside the tag and pass it to the callback.
+ const specialContent = part.slice(2, -2)
+ await options.onSpecial?.(specialContent)
}
else {
- buffer += current
+ await options.onLiteral?.(part)
}
-
- state = newState
}
},
+
+ /**
+ * Finalizes the parsing process.
+ * Any remaining content in the buffer is flushed as a final literal part.
+ * This should be called after the stream has ended.
+ */
async end() {
- if (buffer !== '') {
- if (state === States.Literal) {
- await options.onLiteral?.(buffer)
- }
- else {
- if (buffer.endsWith('|>')) {
- await options.onSpecial?.(buffer)
- }
- }
+ if (buffer) {
+ await options.onLiteral?.(buffer)
+ buffer = ''
}
},
}
diff --git a/packages/stage-ui/src/composables/markdown.ts b/packages/stage-ui/src/composables/markdown.ts
index 71bbd9698..459c11787 100644
--- a/packages/stage-ui/src/composables/markdown.ts
+++ b/packages/stage-ui/src/composables/markdown.ts
@@ -1,38 +1,95 @@
+import type { RehypeShikiOptions } from '@shikijs/rehype'
+import type { BundledLanguage } from 'shiki'
+import type { Processor } from 'unified'
+
import rehypeShiki from '@shikijs/rehype'
+import rehypeKatex from 'rehype-katex'
import RehypeStringify from 'rehype-stringify'
+import remarkMath from 'remark-math'
import RemarkParse from 'remark-parse'
import RemarkRehype from 'remark-rehype'
import { unified } from 'unified'
-async function createProcessor() {
+// Define a specific, compatible type for our processor to ensure type safety.
+type MarkdownProcessor = Processor
+
+const processorCache = new Map>()
+const langRegex = /```(.{2,})\s/g
+
+function extractLangs(markdown: string): BundledLanguage[] {
+ const matches = markdown.matchAll(langRegex)
+ const langs = new Set()
+ langs.add('python')
+ for (const match of matches) {
+ if (match[1])
+ langs.add(match[1] as BundledLanguage)
+ }
+ return [...langs]
+}
+
+async function createProcessor(langs: BundledLanguage[]): Promise {
+ const options: RehypeShikiOptions = {
+ themes: {
+ light: 'github-light',
+ dark: 'github-dark',
+ },
+ langs,
+ defaultLanguage: langs[0] || 'python',
+ }
+
return unified()
.use(RemarkParse)
+ .use(remarkMath)
.use(RemarkRehype)
- .use(rehypeShiki, {
- themes: {
- light: 'github-light',
- dark: 'github-dark',
- },
- })
+ .use(rehypeKatex)
+ .use(rehypeShiki, options)
.use(RehypeStringify)
}
+function getProcessor(langs: BundledLanguage[]): Promise {
+ // The cache key should be consistent, so we sort the languages.
+ const cacheKey = [...langs].sort().join(',')
+
+ if (!processorCache.has(cacheKey)) {
+ const processorPromise = createProcessor(langs)
+ processorCache.set(cacheKey, processorPromise)
+ }
+
+ return processorCache.get(cacheKey)!
+}
+
export function useMarkdown() {
const fallbackProcessor = unified()
.use(RemarkParse)
+ .use(remarkMath)
.use(RemarkRehype)
+ .use(rehypeKatex)
.use(RehypeStringify)
return {
process: async (markdown: string): Promise => {
try {
- const processor = await createProcessor()
+ // A quick check for code fences. If none, use the fast fallback.
+ if (!/`{3,}/.test(markdown))
+ return fallbackProcessor.processSync(markdown).toString()
+
+ const langs = extractLangs(markdown)
+
+ // Always ensure 'python' is loaded as it's our default.
+ const langSet = new Set(langs)
+ langSet.add('python')
+ const languagesToLoad = Array.from(langSet)
+
+ const processor = await getProcessor(languagesToLoad)
const result = await processor.process(markdown)
return result.toString()
}
catch (error) {
- console.warn('Failed to process markdown with syntax highlighting, falling back to basic processing:', error)
+ console.warn(
+ 'Failed to process markdown with syntax highlighting, falling back to basic processing:',
+ error,
+ )
// Fallback to basic processor without highlighting
return fallbackProcessor.processSync(markdown).toString()
}
diff --git a/packages/stage-ui/src/stores/chat.ts b/packages/stage-ui/src/stores/chat.ts
index c569a9ae0..70128d449 100644
--- a/packages/stage-ui/src/stores/chat.ts
+++ b/packages/stage-ui/src/stores/chat.ts
@@ -1,9 +1,9 @@
import type { ChatProvider } from '@xsai-ext/shared-providers'
-import type { Message, SystemMessage } from '@xsai/shared-chat'
+import type { Message, SystemMessage, UserMessagePart } from '@xsai/shared-chat'
+import type { StreamEvent } from '../stores/llm'
import type { ChatAssistantMessage, ChatMessage, ChatSlices } from '../types/chat'
-import { readableStreamToAsyncIterator } from '@moeru/std'
import { defineStore, storeToRefs } from 'pinia'
import { ref, toRaw } from 'vue'
@@ -65,26 +65,57 @@ export const useChatStore = defineStore('chat', () => {
onAssistantResponseEndHooks.value.push(cb)
}
+ // I know this nu uh, better than loading all language on rehypeShiki
+ const codeBlockSystemPrompt = '- For any programming code block, always specify the programming language that supported on @shikijs/rehype on the rendered markdown, eg. ```python ... ```\n'
+ const mathSyntaxSystemPrompt = '- For any math equation, use LaTeX format, eg: $ x^3 $, always escape dollar sign outside math equation\n'
+
const messages = ref>([
{
role: 'system',
- content: systemPrompt.value, // TODO: compose, replace {{ user }} tag, etc
+ content: codeBlockSystemPrompt + mathSyntaxSystemPrompt + systemPrompt.value, // TODO: compose, replace {{ user }} tag, etc
} satisfies SystemMessage,
])
const streamingMessage = ref({ role: 'assistant', content: '', slices: [], tool_results: [] })
- async function send(sendingMessage: string, options: { model: string, chatProvider: ChatProvider, providerConfig?: Record }) {
+ async function send(
+ sendingMessage: string,
+ options: {
+ model: string
+ chatProvider: ChatProvider
+ providerConfig?: Record
+ attachments?: { type: 'image', data: string, mimeType: string }[]
+ },
+ ) {
try {
sending.value = true
- if (!sendingMessage)
+ if (!sendingMessage && !options.attachments?.length)
return
for (const hook of onBeforeMessageComposedHooks.value) {
await hook(sendingMessage)
}
+ const contentParts: UserMessagePart[] = [{ type: 'text', text: sendingMessage }]
+
+ if (options.attachments) {
+ for (const attachment of options.attachments) {
+ if (attachment.type === 'image') {
+ contentParts.push({
+ type: 'image_url',
+ image_url: {
+ url: `data:${attachment.mimeType};base64,${attachment.data}`,
+ },
+ })
+ }
+ }
+ }
+
+ const finalContent = contentParts.length > 1 ? contentParts : sendingMessage
+
+ messages.value.push({ role: 'user', content: finalContent })
+
const parser = useLlmmarkerParser({
onLiteral: async (literal) => {
for (const hook of onTokenLiteralHooks.value) {
@@ -112,14 +143,9 @@ export const useChatStore = defineStore('chat', () => {
},
})
- const slicesQueue = useQueue({
+ const toolCallQueue = useQueue({
handlers: [
- async (ctx) => { // FIXME: it still looks dirty
- if (ctx.data.type === 'text') {
- await parser.consume(ctx.data.text)
- return
- }
-
+ async (ctx) => {
if (ctx.data.type === 'tool-call') {
streamingMessage.value.slices.push(ctx.data)
return
@@ -133,9 +159,7 @@ export const useChatStore = defineStore('chat', () => {
})
streamingMessage.value = { role: 'assistant', content: '', slices: [], tool_results: [] }
- messages.value.push({ role: 'user', content: sendingMessage })
- messages.value.push(streamingMessage.value)
- const newMessages = messages.value.slice(0, messages.value.length - 1).map((msg) => {
+ const newMessages = messages.value.map((msg) => {
if (msg.role === 'assistant') {
const { slices: _, ...rest } = msg // exclude slices
rest.tool_results = toRaw(rest.tool_results)
@@ -152,57 +176,62 @@ export const useChatStore = defineStore('chat', () => {
await hook(sendingMessage)
}
+ let fullText = ''
const headers = (options.providerConfig?.headers || {}) as Record
- const res = await stream(options.model, options.chatProvider, newMessages as Message[], {
+
+ await stream(options.model, options.chatProvider, newMessages as Message[], {
headers,
- onToolCall(toolCall) {
- slicesQueue.add({
- type: 'tool-call',
- toolCall,
- })
- },
- onToolCallResult(toolCallResult) {
- slicesQueue.add({
- type: 'tool-call-result',
- id: toolCallResult.id,
- result: toolCallResult.result,
- })
+ async onStreamEvent(event: StreamEvent) {
+ if (event.type === 'tool-call') {
+ toolCallQueue.add({
+ type: 'tool-call',
+ toolCall: event,
+ })
+ }
+ else if (event.type === 'tool-result') {
+ toolCallQueue.add({
+ type: 'tool-call-result',
+ id: event.toolCallId,
+ result: event.result,
+ })
+ }
+ else if (event.type === 'text-delta') {
+ fullText += event.text
+ await parser.consume(event.text)
+ }
+ else if (event.type === 'finish') {
+ // Finalize the parsing of the actual message content
+ await parser.end()
+
+ // Add the completed message to the history only if it has content
+ if (streamingMessage.value.slices.length > 0)
+ messages.value.push(toRaw(streamingMessage.value))
+
+ // Reset the streaming message for the next turn
+ streamingMessage.value = { role: 'assistant', content: '', slices: [], tool_results: [] }
+
+ // Instruct the TTS pipeline to flush by calling hooks directly
+ const flushSignal = `${TTS_FLUSH_INSTRUCTION}${TTS_FLUSH_INSTRUCTION}`
+ for (const hook of onTokenLiteralHooks.value)
+ await hook(flushSignal)
+
+ // Call the end-of-stream hooks
+ for (const hook of onStreamEndHooks.value)
+ await hook()
+
+ // Call the end-of-response hooks with the full text
+ for (const hook of onAssistantResponseEndHooks.value)
+ await hook(fullText)
+
+ // eslint-disable-next-line no-console
+ console.debug('LLM output:', fullText)
+ }
},
})
for (const hook of onAfterSendHooks.value) {
await hook(sendingMessage)
}
-
- let fullText = ''
-
- for await (const textPart of readableStreamToAsyncIterator(res.textStream)) {
- slicesQueue.add({
- type: 'text',
- text: textPart,
- })
- fullText += textPart
- }
-
- // Instruct the TTS pipeline to flush
- // 2x TTS_FLUSH_INSTRUCTION tokens are used:
- // - One should be left in the grapheme cluster reader
- // - The other should be left in the TTS chunking reader
- // Because both readers will never be closed in the lifecycle of the app
- slicesQueue.add({ type: 'text', text: `${TTS_FLUSH_INSTRUCTION}${TTS_FLUSH_INSTRUCTION}` })
-
- await parser.end()
-
- for (const hook of onStreamEndHooks.value) {
- await hook()
- }
-
- for (const hook of onAssistantResponseEndHooks.value) {
- await hook(fullText)
- }
-
- // eslint-disable-next-line no-console
- console.debug('LLM output:', fullText)
}
catch (error) {
console.error('Error sending message:', error)
diff --git a/packages/stage-ui/src/stores/llm.ts b/packages/stage-ui/src/stores/llm.ts
index e1bf21322..79584cfd7 100644
--- a/packages/stage-ui/src/stores/llm.ts
+++ b/packages/stage-ui/src/stores/llm.ts
@@ -10,13 +10,16 @@ import { ref } from 'vue'
import { debug, mcp } from '../tools'
+export type StreamEvent
+ = | { type: 'text-delta', text: string }
+ | ({ type: 'finish' } & any)
+ | ({ type: 'tool-call' } & CompletionToolCall)
+ | { type: 'tool-result', toolCallId: string, result?: string | ToolMessagePart[] }
+ | { type: 'error', error: any }
+
export interface StreamOptions {
headers?: Record
- onToolCall?: (toolCall: CompletionToolCall) => void
- onToolCallResult?: (toolCallResult: {
- id: string
- result?: string | ToolMessagePart[]
- }) => void
+ onStreamEvent?: (event: StreamEvent) => void | Promise
toolsCompatibility?: Map
supportsTools?: boolean
}
@@ -42,12 +45,7 @@ async function streamFrom(model: string, chatProvider: ChatProvider, messages: M
]
: undefined,
onEvent(event) {
- if (event.type === 'tool-call') {
- options?.onToolCall?.(event)
- }
- else if (event.type === 'tool-result') {
- options?.onToolCallResult?.({ id: event.toolCallId, result: event.result })
- }
+ options?.onStreamEvent?.(event as StreamEvent)
},
})
}
diff --git a/packages/ui/src/components/Form/Textarea/Basic.vue b/packages/ui/src/components/Form/Textarea/Basic.vue
index 5acddf8a7..f61280a7f 100644
--- a/packages/ui/src/components/Form/Textarea/Basic.vue
+++ b/packages/ui/src/components/Form/Textarea/Basic.vue
@@ -7,6 +7,7 @@ const props = defineProps<{
const events = defineEmits<{
(event: 'submit', message: string): void
+ (event: 'pasteFile', files: File[]): void
}>()
const input = defineModel({
@@ -23,6 +24,17 @@ function onKeyDown(e: KeyboardEvent) {
}
}
+function onPaste(e: ClipboardEvent) {
+ if (!e.clipboardData)
+ return
+
+ const { files } = e.clipboardData
+ if (files.length > 0) {
+ e.preventDefault()
+ events('pasteFile', Array.from(files))
+ }
+}
+
// javascript - Creating a textarea with auto-resize - Stack Overflow
// https://stackoverflow.com/questions/454202/creating-a-textarea-with-auto-resize
watch(input, () => {
@@ -46,5 +58,6 @@ watch(input, () => {
v-model="input"
:style="{ height: textareaHeight }"
@keydown="onKeyDown"
+ @paste="onPaste"
/>
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e85b10258..c8feda2fb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1131,7 +1131,7 @@ importers:
version: 0.1.3
unplugin-yaml:
specifier: ^3.0.2
- version: 3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(astro@5.10.1(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0))(esbuild@0.25.8)(rolldown@1.0.0-beta.30)(rollup@4.46.2)(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
+ version: 3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(astro@5.10.1(@types/node@24.1.0)(encoding@0.1.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0))(esbuild@0.25.8)(rolldown@1.0.0-beta.30)(rollup@4.46.2)(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
vitepress:
specifier: ^2.0.0-alpha.9
version: 2.0.0-alpha.9(@types/node@24.1.0)(change-case@5.4.4)(fuse.js@7.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(nprogress@0.2.0)(postcss@8.5.6)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0)
@@ -1174,7 +1174,7 @@ importers:
devDependencies:
unplugin-yaml:
specifier: ^3.0.2
- version: 3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(astro@5.10.1(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0))(esbuild@0.25.8)(rolldown@1.0.0-beta.30)(rollup@4.46.2)(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
+ version: 3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(astro@5.10.1(@types/node@24.1.0)(encoding@0.1.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0))(esbuild@0.25.8)(rolldown@1.0.0-beta.30)(rollup@4.46.2)(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
packages/memory-pgvector:
dependencies:
@@ -1416,12 +1416,18 @@ importers:
postprocessing:
specifier: ^6.37.7
version: 6.37.7(three@0.179.0)
+ rehype-katex:
+ specifier: ^7.0.1
+ version: 7.0.1
rehype-stringify:
specifier: ^10.0.1
version: 10.0.1
reka-ui:
specifier: ^2.4.1
version: 2.4.1(typescript@5.9.2)(vue@3.5.18(typescript@5.9.2))
+ remark-math:
+ specifier: ^6.0.0
+ version: 6.0.0
remark-parse:
specifier: ^11.0.0
version: 11.0.0
@@ -1563,7 +1569,7 @@ importers:
version: 1.2.4(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(esbuild@0.25.8)(rollup@4.46.2)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
unplugin-yaml:
specifier: ^3.0.2
- version: 3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(astro@5.10.1(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0))(esbuild@0.25.8)(rolldown@1.0.0-beta.30)(rollup@4.46.2)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
+ version: 3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(astro@5.10.1(@types/node@24.1.0)(encoding@0.1.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0))(esbuild@0.25.8)(rolldown@1.0.0-beta.30)(rollup@4.46.2)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
vite:
specifier: ^6.3.5
version: 6.3.5(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
@@ -5920,6 +5926,9 @@ packages:
'@types/jsonfile@6.1.4':
resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==}
+ '@types/katex@0.16.7':
+ resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
+
'@types/linkify-it@5.0.0':
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
@@ -7523,6 +7532,10 @@ packages:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
+ commander@8.3.0:
+ resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
+ engines: {node: '>= 12'}
+
commander@9.5.0:
resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
engines: {node: ^12.20.0 || >=14}
@@ -9027,6 +9040,12 @@ packages:
hast-util-embedded@3.0.0:
resolution: {integrity: sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==}
+ hast-util-from-dom@5.0.1:
+ resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==}
+
+ hast-util-from-html-isomorphic@2.0.0:
+ resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==}
+
hast-util-from-html@2.0.3:
resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==}
@@ -9610,6 +9629,10 @@ packages:
jws@4.0.0:
resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==}
+ katex@0.16.22:
+ resolution: {integrity: sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==}
+ hasBin: true
+
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -9987,6 +10010,9 @@ packages:
mdast-util-gfm@3.1.0:
resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==}
+ mdast-util-math@3.0.0:
+ resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==}
+
mdast-util-phrasing@4.1.0:
resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
@@ -10064,6 +10090,9 @@ packages:
micromark-extension-gfm@3.0.0:
resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
+ micromark-extension-math@3.1.0:
+ resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==}
+
micromark-factory-destination@2.0.1:
resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
@@ -11352,6 +11381,9 @@ packages:
resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==}
hasBin: true
+ rehype-katex@7.0.1:
+ resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==}
+
rehype-minify-whitespace@6.0.2:
resolution: {integrity: sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw==}
@@ -11381,6 +11413,9 @@ packages:
remark-gfm@4.0.1:
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
+ remark-math@6.0.0:
+ resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==}
+
remark-parse@11.0.0:
resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
@@ -13775,7 +13810,7 @@ snapshots:
remark-parse: 11.0.0
remark-rehype: 11.1.2
remark-smartypants: 3.0.2
- shiki: 3.9.1
+ shiki: 3.11.0
smol-toml: 1.4.1
unified: 11.0.5
unist-util-remove-position: 5.0.0
@@ -17836,6 +17871,8 @@ snapshots:
dependencies:
'@types/node': 24.1.0
+ '@types/katex@0.16.7': {}
+
'@types/linkify-it@5.0.0': {}
'@types/long@4.0.2': {}
@@ -19563,7 +19600,7 @@ snapshots:
prompts: 2.4.2
rehype: 13.0.2
semver: 7.7.2
- shiki: 3.9.1
+ shiki: 3.11.0
tinyexec: 0.3.2
tinyglobby: 0.2.14
tsconfck: 3.1.6(typescript@5.9.2)
@@ -19664,108 +19701,7 @@ snapshots:
prompts: 2.4.2
rehype: 13.0.2
semver: 7.7.2
- shiki: 3.9.1
- tinyexec: 0.3.2
- tinyglobby: 0.2.14
- tsconfck: 3.1.6(typescript@5.9.2)
- ultrahtml: 1.6.0
- unifont: 0.5.2
- unist-util-visit: 5.0.0
- unstorage: 1.16.0
- vfile: 6.0.3
- vite: 6.3.5(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
- vitefu: 1.0.7(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
- xxhash-wasm: 1.1.0
- yargs-parser: 21.1.1
- yocto-spinner: 0.2.3
- zod: 3.25.76
- zod-to-json-schema: 3.24.6(zod@3.25.76)
- zod-to-ts: 1.2.0(typescript@5.9.2)(zod@3.25.76)
- optionalDependencies:
- sharp: 0.33.5
- transitivePeerDependencies:
- - '@azure/app-configuration'
- - '@azure/cosmos'
- - '@azure/data-tables'
- - '@azure/identity'
- - '@azure/keyvault-secrets'
- - '@azure/storage-blob'
- - '@capacitor/preferences'
- - '@deno/kv'
- - '@netlify/blobs'
- - '@planetscale/database'
- - '@types/node'
- - '@upstash/redis'
- - '@vercel/blob'
- - '@vercel/kv'
- - aws4fetch
- - db0
- - encoding
- - idb-keyval
- - ioredis
- - jiti
- - less
- - lightningcss
- - rollup
- - sass
- - sass-embedded
- - stylus
- - sugarss
- - supports-color
- - terser
- - tsx
- - typescript
- - uploadthing
- - yaml
- optional: true
-
- astro@5.10.1(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0):
- dependencies:
- '@astrojs/compiler': 2.12.2
- '@astrojs/internal-helpers': 0.6.1
- '@astrojs/markdown-remark': 6.3.2
- '@astrojs/telemetry': 3.3.0
- '@capsizecss/unpack': 2.4.0(encoding@0.1.13)
- '@oslojs/encoding': 1.1.0
- '@rollup/pluginutils': 5.2.0(rollup@4.46.2)
- acorn: 8.15.0
- aria-query: 5.3.2
- axobject-query: 4.1.0
- boxen: 8.0.1
- ci-info: 4.3.0
- clsx: 2.1.1
- common-ancestor-path: 1.0.1
- cookie: 1.0.2
- cssesc: 3.0.0
- debug: 4.4.1
- deterministic-object-hash: 2.0.2
- devalue: 5.1.1
- diff: 5.2.0
- dlv: 1.1.3
- dset: 3.1.4
- es-module-lexer: 1.7.0
- esbuild: 0.25.8
- estree-walker: 3.0.3
- flattie: 1.1.1
- fontace: 0.3.0
- github-slugger: 2.0.0
- html-escaper: 3.0.3
- http-cache-semantics: 4.2.0
- import-meta-resolve: 4.1.0
- js-yaml: 4.1.0
- kleur: 4.1.5
- magic-string: 0.30.17
- magicast: 0.3.5
- mrmime: 2.0.1
- neotraverse: 0.6.18
- p-limit: 6.2.0
- p-queue: 8.1.0
- package-manager-detector: 1.3.0
- picomatch: 4.0.3
- prompts: 2.4.2
- rehype: 13.0.2
- semver: 7.7.2
- shiki: 3.9.1
+ shiki: 3.11.0
tinyexec: 0.3.2
tinyglobby: 0.2.14
tsconfck: 3.1.6(typescript@5.9.2)
@@ -20293,6 +20229,8 @@ snapshots:
commander@4.1.1: {}
+ commander@8.3.0: {}
+
commander@9.5.0: {}
comment-parser@1.4.1: {}
@@ -22021,6 +21959,19 @@ snapshots:
'@types/hast': 3.0.4
hast-util-is-element: 3.0.0
+ hast-util-from-dom@5.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ hastscript: 9.0.0
+ web-namespaces: 2.0.1
+
+ hast-util-from-html-isomorphic@2.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-from-dom: 5.0.1
+ hast-util-from-html: 2.0.3
+ unist-util-remove-position: 5.0.0
+
hast-util-from-html@2.0.3:
dependencies:
'@types/hast': 3.0.4
@@ -22715,6 +22666,10 @@ snapshots:
jwa: 2.0.0
safe-buffer: '@nolyfill/safe-buffer@1.0.44'
+ katex@0.16.22:
+ dependencies:
+ commander: 8.3.0
+
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@@ -23171,6 +23126,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ mdast-util-math@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ longest-streak: 3.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ unist-util-remove-position: 5.0.0
+ transitivePeerDependencies:
+ - supports-color
+
mdast-util-phrasing@4.1.0:
dependencies:
'@types/mdast': 4.0.4
@@ -23310,6 +23277,16 @@ snapshots:
micromark-util-combine-extensions: 2.0.1
micromark-util-types: 2.0.1
+ micromark-extension-math@3.1.0:
+ dependencies:
+ '@types/katex': 0.16.7
+ devlop: 1.1.0
+ katex: 0.16.22
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.1
+
micromark-factory-destination@2.0.1:
dependencies:
micromark-util-character: 2.1.1
@@ -24948,6 +24925,16 @@ snapshots:
dependencies:
jsesc: 3.0.2
+ rehype-katex@7.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/katex': 0.16.7
+ hast-util-from-html-isomorphic: 2.0.0
+ hast-util-to-text: 4.0.2
+ katex: 0.16.22
+ unist-util-visit-parents: 6.0.1
+ vfile: 6.0.3
+
rehype-minify-whitespace@6.0.2:
dependencies:
'@types/hast': 3.0.4
@@ -25023,6 +25010,15 @@ snapshots:
- supports-color
optional: true
+ remark-math@6.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-math: 3.0.0
+ micromark-extension-math: 3.1.0
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
remark-parse@11.0.0:
dependencies:
'@types/mdast': 4.0.4
@@ -26311,7 +26307,6 @@ snapshots:
dependencies:
'@types/unist': 3.0.0
unist-util-visit: 5.0.0
- optional: true
unist-util-remove@4.0.0:
dependencies:
@@ -26698,7 +26693,7 @@ snapshots:
rollup: 4.46.2
vite: rolldown-vite@7.0.12(@types/node@24.1.0)(esbuild@0.25.8)(jiti@2.5.1)(less@4.4.0)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
- unplugin-yaml@3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(astro@5.10.1(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0))(esbuild@0.25.8)(rolldown@1.0.0-beta.30)(rollup@4.46.2)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)):
+ unplugin-yaml@3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(astro@5.10.1(@types/node@24.1.0)(encoding@0.1.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0))(esbuild@0.25.8)(rolldown@1.0.0-beta.30)(rollup@4.46.2)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)):
dependencies:
'@rollup/pluginutils': 5.2.0(rollup@4.46.2)
unplugin: 2.3.5
@@ -26706,13 +26701,13 @@ snapshots:
optionalDependencies:
'@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.46.2)
'@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.46.2)
- astro: 5.10.1(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0)
+ astro: 5.10.1(@types/node@24.1.0)(encoding@0.1.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0)
esbuild: 0.25.8
rolldown: 1.0.0-beta.30
rollup: 4.46.2
vite: 6.3.5(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
- unplugin-yaml@3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(astro@5.10.1(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0))(esbuild@0.25.8)(rolldown@1.0.0-beta.30)(rollup@4.46.2)(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)):
+ unplugin-yaml@3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.46.2))(astro@5.10.1(@types/node@24.1.0)(encoding@0.1.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0))(esbuild@0.25.8)(rolldown@1.0.0-beta.30)(rollup@4.46.2)(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)):
dependencies:
'@rollup/pluginutils': 5.2.0(rollup@4.46.2)
unplugin: 2.3.5
@@ -26720,7 +26715,7 @@ snapshots:
optionalDependencies:
'@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.46.2)
'@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.46.2)
- astro: 5.10.1(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0)
+ astro: 5.10.1(@types/node@24.1.0)(encoding@0.1.13)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.46.2)(terser@5.43.1)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.0)
esbuild: 0.25.8
rolldown: 1.0.0-beta.30
rollup: 4.46.2