feat(desktop): embed BoosAPI Desktop client as a subproject

Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
This commit is contained in:
boos
2026-06-22 11:32:57 +08:00
parent d9c78ecc73
commit e0a5f57793
2305 changed files with 449355 additions and 0 deletions
@@ -0,0 +1,48 @@
import type { ParsedNode } from 'stream-markdown-parser'
import type { CustomComponentDisplayMode, CustomComponentMap, MarkstreamCustomComponent } from '../customComponents'
import { getHtmlTagFromContent } from 'stream-markdown-parser'
import { getCustomComponentDisplay } from '../customComponents'
export interface ResolvedCustomHtmlTag {
tag: string
isWhitelisted: boolean
component: MarkstreamCustomComponent<any> | null
display: CustomComponentDisplayMode | undefined
}
export function resolveCustomHtmlTag(
node: Pick<ParsedNode, 'type'> & { tag?: string | null, content?: unknown },
customComponents: CustomComponentMap,
customHtmlTags?: readonly string[],
): ResolvedCustomHtmlTag | null {
const normalizedType = String(node.type ?? '').trim().toLowerCase()
const normalizedTags = (customHtmlTags ?? []).map(tag => tag.toLowerCase())
const taggedNode = normalizedType === 'html_inline' || normalizedType === 'html_block'
if (!taggedNode && !normalizedTags.includes(normalizedType))
return null
const tag = taggedNode
? (String(node.tag ?? '').trim().toLowerCase() || getHtmlTagFromContent(node.content))
: (String(node.tag ?? '').trim().toLowerCase() || normalizedType)
if (!tag)
return null
const isWhitelisted = normalizedTags.includes(tag)
const component = isWhitelisted ? (customComponents[tag] ?? customComponents[normalizedType] ?? null) : null
return {
tag,
isWhitelisted,
component,
display: getCustomComponentDisplay(component),
}
}
export function isParagraphBreakingCustomHtmlNode(
node: Pick<ParsedNode, 'type'> & { tag?: string | null, content?: unknown },
customComponents: CustomComponentMap,
customHtmlTags?: readonly string[],
) {
return resolveCustomHtmlTag(node, customComponents, customHtmlTags)?.display === 'block'
}
@@ -0,0 +1,182 @@
import type { ComponentType, ReactNode } from 'react'
import React from 'react'
import {
BLOCKED_HTML_TAGS as BLOCKED_TAGS,
hasCustomHtmlComponents as hasCustomHtmlComponentsBase,
isCustomHtmlComponentTag,
sanitizeHtmlAttrs as sanitizeHtmlAttrsBase,
tokenizeHtml as tokenizeHtmlBase,
} from 'stream-markdown-parser'
export type { HtmlToken } from 'stream-markdown-parser'
function normalizeCssPropName(prop: string) {
if (prop.startsWith('--'))
return prop
return prop.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())
}
function parseInlineStyle(style: string): Record<string, string> | undefined {
const input = style.trim()
if (!input)
return undefined
const out: Record<string, string> = {}
for (const part of input.split(';')) {
const chunk = part.trim()
if (!chunk)
continue
const idx = chunk.indexOf(':')
if (idx === -1)
continue
const key = normalizeCssPropName(chunk.slice(0, idx).trim())
const value = chunk.slice(idx + 1).trim()
if (key)
out[key] = value
}
return Object.keys(out).length ? out : undefined
}
export function normalizeDomAttrs(attrs: Record<string, string>) {
const next: Record<string, any> = { ...attrs }
if (Object.prototype.hasOwnProperty.call(next, 'class')) {
next.className = next.class
delete next.class
}
if (Object.prototype.hasOwnProperty.call(next, 'for')) {
next.htmlFor = next.for
delete next.for
}
if (typeof next.style === 'string') {
const parsed = parseInlineStyle(next.style)
if (parsed)
next.style = parsed
else
delete next.style
}
return next
}
export const tokenizeHtml = tokenizeHtmlBase
export const sanitizeHtmlAttrs = sanitizeHtmlAttrsBase
export function isCustomHtmlComponent(
tagName: string,
customComponents: Record<string, ComponentType<any>>,
) {
return isCustomHtmlComponentTag(tagName, customComponents as Record<string, unknown>)
}
export function hasCustomHtmlComponents(
content: string,
customComponents: Record<string, ComponentType<any>>,
) {
return hasCustomHtmlComponentsBase(content, customComponents as Record<string, unknown>)
}
export function parseHtmlToReactNodes(
content: string,
customComponents: Record<string, ComponentType<any>>,
): ReactNode[] | null {
if (!content)
return []
try {
const tokens = tokenizeHtml(content)
let autoKeySeed = 0
const stack: Array<{ tagName: string, children: ReactNode[], attrs?: Record<string, string>, blocked?: boolean }> = []
const rootNodes: ReactNode[] = []
for (const token of tokens) {
if (token.type === 'text') {
if (stack.length > 0 && stack[stack.length - 1].blocked)
continue
const target = stack.length > 0 ? stack[stack.length - 1].children : rootNodes
target.push(token.content ?? '')
continue
}
if (token.type === 'self_closing') {
if (BLOCKED_TAGS.has(token.tagName.toLowerCase()))
continue
const attrs = sanitizeHtmlAttrs(token.attrs || {})
const explicitKey = (attrs as any).key
const elementKey = explicitKey != null && explicitKey !== '' ? explicitKey : `ms-html-${autoKeySeed++}`
const Comp = isCustomHtmlComponent(token.tagName, customComponents)
? (customComponents[token.tagName] || customComponents[token.tagName.toLowerCase()])
: undefined
const target = stack.length > 0 ? stack[stack.length - 1].children : rootNodes
if (Comp) {
target.push(React.createElement(Comp, { ...attrs, key: elementKey }))
}
else {
target.push(React.createElement(token.tagName, {
...normalizeDomAttrs(attrs),
key: elementKey,
suppressHydrationWarning: true,
}))
}
continue
}
if (token.type === 'tag_open') {
stack.push({
tagName: token.tagName,
children: [],
attrs: token.attrs,
blocked: BLOCKED_TAGS.has(token.tagName.toLowerCase()),
})
continue
}
const opening = stack.pop()
if (!opening || opening.blocked)
continue
const attrs = sanitizeHtmlAttrs(opening.attrs || {})
const explicitKey = (attrs as any).key
const elementKey = explicitKey != null && explicitKey !== '' ? explicitKey : `ms-html-${autoKeySeed++}`
const Comp = isCustomHtmlComponent(opening.tagName, customComponents)
? (customComponents[opening.tagName] || customComponents[opening.tagName.toLowerCase()])
: undefined
const element = Comp
? React.createElement(Comp, { ...attrs, key: elementKey }, ...opening.children)
: React.createElement(opening.tagName, {
...normalizeDomAttrs(attrs),
key: elementKey,
suppressHydrationWarning: true,
}, ...opening.children)
if (stack.length > 0)
stack[stack.length - 1].children.push(element)
else
rootNodes.push(element)
}
while (stack.length > 0) {
const unclosed = stack.pop()
if (!unclosed || unclosed.blocked)
continue
const attrs = sanitizeHtmlAttrs(unclosed.attrs || {})
const explicitKey = (attrs as any).key
const elementKey = explicitKey != null && explicitKey !== '' ? explicitKey : `ms-html-${autoKeySeed++}`
const Comp = isCustomHtmlComponent(unclosed.tagName, customComponents)
? (customComponents[unclosed.tagName] || customComponents[unclosed.tagName.toLowerCase()])
: undefined
const element = Comp
? React.createElement(Comp, { ...attrs, key: elementKey }, ...unclosed.children)
: React.createElement(unclosed.tagName, {
...normalizeDomAttrs(attrs),
key: elementKey,
suppressHydrationWarning: true,
}, ...unclosed.children)
rootNodes.push(element)
}
return rootNodes
}
catch {
return null
}
}
@@ -0,0 +1,177 @@
import CssIcon from '../icon/css.svg?raw'
import HtmlIcon from '../icon/html.svg?raw'
import JsxReactIcon from '../icon/javascript-react.svg?raw'
import JsIcon from '../icon/javascript.svg?raw'
import JsonIcon from '../icon/json.svg?raw'
import MarkdownIcon from '../icon/markdown.svg?raw'
import MermaidIcon from '../icon/mermaid.svg?raw'
import PythonIcon from '../icon/python.svg?raw'
import ShellIcon from '../icon/shell.svg?raw'
import SquareCodeIcon from '../icon/square-code.svg?raw'
import TextIcon from '../icon/text.svg?raw'
import TsReactIcon from '../icon/typescript-react.svg?raw'
import TsIcon from '../icon/typescript.svg?raw'
export type LanguageIconResolver = (lang: string) => string | undefined | null
type LanguageIconMap = Record<string, string>
let userLanguageIconResolver: LanguageIconResolver | null = null
let extendedLanguageIconMap: LanguageIconMap | null = null
let extendedLanguageIconPromise: Promise<LanguageIconMap | null> | null = null
const revisionListeners = new Set<() => void>()
const DEFAULT_LANGUAGE_ICON = SquareCodeIcon
export function setLanguageIconResolver(resolver?: LanguageIconResolver | null) {
userLanguageIconResolver = resolver ?? null
}
const LANGUAGE_ALIAS_MAP: Record<string, string> = {
'': '',
'javascript': 'javascript',
'js': 'javascript',
'mjs': 'javascript',
'cjs': 'javascript',
'typescript': 'typescript',
'ts': 'typescript',
'jsx': 'jsx',
'tsx': 'tsx',
'py': 'python',
'rb': 'ruby',
'sh': 'shell',
'bash': 'shell',
'zsh': 'shell',
'shellscript': 'shell',
'd2lang': 'd2',
'plaintext': 'plain',
'text': 'plain',
'c++': 'cpp',
'md': 'markdown',
}
const CORE_LANGUAGE_ICON_MAP: LanguageIconMap = {
'': TextIcon,
'plain': TextIcon,
'text': TextIcon,
'javascript': JsIcon,
'typescript': TsIcon,
'jsx': JsxReactIcon,
'tsx': TsReactIcon,
'html': HtmlIcon,
'css': CssIcon,
'json': JsonIcon,
'python': PythonIcon,
'shell': ShellIcon,
'markdown': MarkdownIcon,
'mermaid': MermaidIcon,
}
function extractLanguageToken(lang?: string | null): string {
if (!lang)
return ''
const trimmed = lang.trim()
if (!trimmed)
return ''
const [firstToken] = trimmed.split(/\s+/)
const [base] = firstToken.split(':')
return base.toLowerCase()
}
export function normalizeLanguageIdentifier(lang?: string | null): string {
const token = extractLanguageToken(lang)
return LANGUAGE_ALIAS_MAP[token] ?? token
}
export function resolveMonacoLanguageId(lang?: string | null): string {
const canonical = normalizeLanguageIdentifier(lang)
if (!canonical)
return 'plaintext'
switch (canonical) {
case 'plain':
return 'plaintext'
case 'jsx':
return 'javascript'
case 'tsx':
return 'typescript'
default:
return canonical
}
}
function emitLanguageIconsRevision() {
for (const listener of revisionListeners) {
try {
listener()
}
catch {}
}
}
async function loadExtendedLanguageIconMap(): Promise<LanguageIconMap | null> {
if (extendedLanguageIconMap)
return extendedLanguageIconMap
if (!extendedLanguageIconPromise) {
extendedLanguageIconPromise = import('./languageIconExtended')
.then((mod) => {
extendedLanguageIconMap = mod.EXTENDED_LANGUAGE_ICON_MAP
emitLanguageIconsRevision()
return extendedLanguageIconMap
})
.catch(() => null)
}
return extendedLanguageIconPromise
}
export async function preloadExtendedLanguageIcons() {
await loadExtendedLanguageIconMap()
}
export function subscribeLanguageIconsRevision(listener: () => void): () => void {
revisionListeners.add(listener)
return () => {
revisionListeners.delete(listener)
}
}
export function getLanguageIcon(lang: string): string {
if (userLanguageIconResolver) {
const hit = userLanguageIconResolver(lang)
if (hit != null && hit !== '')
return hit
}
const normalized = normalizeLanguageIdentifier(lang)
const coreIcon = CORE_LANGUAGE_ICON_MAP[normalized]
if (coreIcon)
return coreIcon
const extendedIcon = extendedLanguageIconMap?.[normalized]
if (extendedIcon)
return extendedIcon
void loadExtendedLanguageIconMap()
return DEFAULT_LANGUAGE_ICON
}
export const languageMap: Record<string, string> = {
javascript: 'JavaScript',
typescript: 'TypeScript',
jsx: 'JSX',
tsx: 'TSX',
shell: 'Shell',
plain: 'Text',
text: 'Text',
markdown: 'Markdown',
json: 'JSON',
python: 'Python',
cpp: 'C++',
vue: 'Vue',
html: 'HTML',
css: 'CSS',
svg: 'SVG',
mermaid: 'Mermaid',
d2: 'D2',
}
@@ -0,0 +1,9 @@
import CppIcon from '../icon/cpp.svg?raw'
import SvgIcon from '../icon/svg.svg?raw'
import VueIcon from '../icon/vue.svg?raw'
export const EXTENDED_LANGUAGE_ICON_MAP: Record<string, string> = {
cpp: CppIcon,
svg: SvgIcon,
vue: VueIcon,
}
@@ -0,0 +1,8 @@
export function normalizeKaTeXRenderInput(content: string) {
if (!content)
return ''
return content
.replace(/·/g, '⋅')
.replace(/℃/g, '°C')
}
@@ -0,0 +1,139 @@
/**
* Lightweight performance monitoring helper for KaTeX rendering.
* Only used during development to understand Worker/cache benefits.
*/
interface RenderMetrics {
type: 'worker' | 'direct' | 'cache-hit'
duration: number
formulaLength: number
timestamp: number
success: boolean
error?: string
}
class PerformanceMonitor {
private metrics: RenderMetrics[] = []
private enabled = false
private maxMetrics = 1000
enable() {
this.enabled = true
}
disable() {
this.enabled = false
}
recordRender(metrics: RenderMetrics) {
if (!this.enabled)
return
this.metrics.push(metrics)
if (this.metrics.length > this.maxMetrics)
this.metrics.shift()
}
private averages(kind: RenderMetrics['type']) {
const rows = this.metrics.filter(m => m.type === kind && m.success)
if (!rows.length)
return 0
return rows.reduce((sum, cur) => sum + cur.duration, 0) / rows.length
}
getStats() {
if (!this.metrics.length) {
return {
totalRenders: 0,
cacheHits: 0,
cacheHitRate: '0.0',
workerCalls: 0,
directCalls: 0,
averageWorkerTime: '0.00',
averageDirectTime: '0.00',
averageCacheHitTime: '0.000',
workerSavings: '0.00',
recommendation: 'Insufficient data',
}
}
const workerAvg = this.averages('worker')
const directAvg = this.averages('direct')
const cacheHits = this.metrics.filter(m => m.type === 'cache-hit')
const cacheAvg = cacheHits.length
? cacheHits.reduce((sum, cur) => sum + cur.duration, 0) / cacheHits.length
: 0
const totalRenders = this.metrics.length
const estimatedDirect = totalRenders * directAvg
const actualTime = (
this.metrics.reduce((sum, cur) => sum + cur.duration, 0)
)
const workerSavings = estimatedDirect - actualTime
const cacheHitRate = cacheHits.length / totalRenders * 100
let recommendation = '✅ Worker prevents main thread blocking'
if (cacheHitRate > 70 && workerAvg < directAvg * 2)
recommendation = '✅ Worker + cache is highly beneficial'
else if (cacheHitRate > 50)
recommendation = '✅ Worker + cache is beneficial'
else if (workerAvg > directAvg * 3 && directAvg > 0)
recommendation = '⚠️ Worker overhead too high; consider direct rendering'
else if (directAvg > 0 && directAvg < 5)
recommendation = '⚠️ Formulas are trivial; worker may be unnecessary'
return {
totalRenders,
cacheHits: cacheHits.length,
cacheHitRate: cacheHitRate.toFixed(1),
workerCalls: this.metrics.filter(m => m.type === 'worker').length,
directCalls: this.metrics.filter(m => m.type === 'direct').length,
averageWorkerTime: workerAvg.toFixed(2),
averageDirectTime: directAvg.toFixed(2),
averageCacheHitTime: cacheAvg.toFixed(3),
workerSavings: workerSavings.toFixed(2),
recommendation,
}
}
printReport() {
const stats = this.getStats()
console.group('KaTeX Performance Report')
console.log(`Total renders: ${stats.totalRenders}`)
console.log(`Cache hits: ${stats.cacheHits} (${stats.cacheHitRate}%)`)
console.log(`Worker average: ${stats.averageWorkerTime}ms`)
console.log(`Direct average: ${stats.averageDirectTime}ms`)
console.log(`Cache hit average: ${stats.averageCacheHitTime}ms`)
console.log(`Estimated savings: ${stats.workerSavings}ms`)
console.log(`Recommendation: ${stats.recommendation}`)
console.groupEnd()
return stats
}
reset() {
this.metrics = []
}
exportMetrics() {
return {
metrics: [...this.metrics],
stats: this.getStats(),
timestamp: Date.now(),
}
}
}
export const perfMonitor = new PerformanceMonitor()
export function enablePerfMonitoring() {
perfMonitor.enable()
}
export function disablePerfMonitoring() {
perfMonitor.disable()
}
export function getPerfReport() {
return perfMonitor.printReport()
}
if (typeof window !== 'undefined') {
(window as any).__katexPerfMonitor = perfMonitor
;(window as any).__katexPerfReport = getPerfReport
}
@@ -0,0 +1,24 @@
export function safeRaf(cb: FrameRequestCallback) {
try {
if (typeof globalThis !== 'undefined' && typeof (globalThis as any).requestAnimationFrame === 'function')
return (globalThis as any).requestAnimationFrame(cb)
}
catch {}
return (globalThis as any).setTimeout(cb as any, 0) as unknown as number
}
export function safeCancelRaf(id: number | null | undefined) {
if (id == null)
return
try {
if (typeof globalThis !== 'undefined' && typeof (globalThis as any).cancelAnimationFrame === 'function') {
(globalThis as any).cancelAnimationFrame(id)
return
}
}
catch {}
try {
;(globalThis as any).clearTimeout(id)
}
catch {}
}
@@ -0,0 +1,102 @@
export interface ResolveStreamingTextStateOptions {
nextContent: string
previousContent: string
typewriterEnabled: boolean
}
export interface StreamingTextStateResult {
settledContent: string
streamedDelta: string
appended: boolean
}
export interface StreamingRenderState {
settledContent: string
streamedDelta: string
}
export interface ResolveStreamingTextUpdateOptions {
nextContent: string
persistedContent?: string
currentState: StreamingRenderState
typewriterEnabled: boolean
streamRenderVersionChanged?: boolean
}
export function resolveStreamingTextState({
nextContent,
previousContent,
typewriterEnabled,
}: ResolveStreamingTextStateOptions): StreamingTextStateResult {
if (!typewriterEnabled) {
return {
settledContent: nextContent,
streamedDelta: '',
appended: false,
}
}
if (nextContent === previousContent) {
return {
settledContent: nextContent,
streamedDelta: '',
appended: false,
}
}
if (previousContent && nextContent.startsWith(previousContent) && nextContent.length > previousContent.length) {
return {
settledContent: previousContent,
streamedDelta: nextContent.slice(previousContent.length),
appended: true,
}
}
return {
settledContent: nextContent,
streamedDelta: '',
appended: false,
}
}
export function resolveStreamingTextUpdate({
nextContent,
persistedContent,
currentState,
typewriterEnabled,
streamRenderVersionChanged = false,
}: ResolveStreamingTextUpdateOptions): StreamingTextStateResult {
const renderedContent = `${currentState.settledContent}${currentState.streamedDelta}`
if (!typewriterEnabled) {
return {
settledContent: nextContent,
streamedDelta: '',
appended: false,
}
}
// React StrictMode may replay effects with the same props while the delta
// animation is still active. Preserve the current delta instead of settling
// it immediately so the fade remains visible in dev and playgrounds.
if (currentState.streamedDelta && renderedContent === nextContent) {
if (streamRenderVersionChanged) {
return {
settledContent: renderedContent,
streamedDelta: '',
appended: false,
}
}
return {
settledContent: currentState.settledContent,
streamedDelta: currentState.streamedDelta,
appended: false,
}
}
return resolveStreamingTextState({
nextContent,
previousContent: persistedContent ?? renderedContent,
typewriterEnabled,
})
}