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,93 @@
import type { ReactNode } from 'react'
import type { ParsedNode } from 'stream-markdown-parser'
import type { RenderContext, RenderNodeFn } from '../types'
export const BLOCK_LEVEL_TYPES = new Set([
'table',
'code_block',
'html_block',
'blockquote',
'list',
'list_item',
'definition_list',
'footnote',
'footnote_reference',
'footnote_anchor',
'admonition',
'thematic_break',
'math_block',
'thinking',
'vmr_container',
])
export function tokenAttrsToProps(attrs?: [string, string | null][]) {
if (!Array.isArray(attrs) || attrs.length === 0)
return undefined
return attrs.reduce<Record<string, string | true>>((acc, [name, value]) => {
if (!name)
return acc
const attrName = name === 'for'
? 'htmlFor'
: name === 'class'
? 'className'
: name
acc[attrName] = value ?? true
return acc
}, {})
}
export function renderNodeChildren(
children: ParsedNode[] | undefined,
ctx: RenderContext,
prefix: string,
renderNode: RenderNodeFn,
) {
if (!Array.isArray(children) || children.length === 0)
return null
const result: ReactNode[] = []
for (let idx = 0; idx < children.length; idx++) {
const child = children[idx] as ParsedNode & { attrs?: [string, string | null][] }
if (!child)
continue
if (child.type === 'label_open') {
const labelChildren: ParsedNode[] = []
idx++
while (idx < children.length) {
const segment = children[idx]
if (segment?.type === 'label_close')
break
if (segment)
labelChildren.push(segment)
idx++
}
const key = `${prefix}-label-${idx}`
result.push(
<label key={key} {...tokenAttrsToProps(child.attrs)}>
{renderNodeChildren(labelChildren, ctx, `${key}-child`, renderNode)}
</label>,
)
continue
}
if (child.type === 'label_close')
continue
result.push(renderNode(child, `${prefix}-${idx}`, ctx))
}
return result
}
export function renderInline(
children: ParsedNode[] | undefined,
ctx: RenderContext,
prefix: string,
renderNode: RenderNodeFn,
) {
return (
<>
{renderNodeChildren(children, ctx, `${prefix}-inline`, renderNode)}
</>
)
}
@@ -0,0 +1,370 @@
import type { ParsedNode } from 'stream-markdown-parser'
import type { RenderContext } from '../types'
import React from 'react'
import { getHtmlTagFromContent, shouldRenderUnknownHtmlTagAsText, stripCustomHtmlWrapper } from 'stream-markdown-parser'
import { AdmonitionNode } from '../components/AdmonitionNode/AdmonitionNode'
import { BlockquoteNode } from '../components/BlockquoteNode/BlockquoteNode'
import { CheckboxNode } from '../components/CheckboxNode/CheckboxNode'
import { CodeBlockNode as MonacoCodeBlockNode } from '../components/CodeBlockNode/CodeBlockNode'
import { PreCodeNode } from '../components/CodeBlockNode/PreCodeNode'
import { D2BlockNode } from '../components/D2BlockNode/D2BlockNode'
import { DefinitionListNode } from '../components/DefinitionListNode/DefinitionListNode'
import { EmojiNode } from '../components/EmojiNode/EmojiNode'
import { EmphasisNode } from '../components/EmphasisNode/EmphasisNode'
import { FootnoteAnchorNode } from '../components/FootnoteAnchorNode/FootnoteAnchorNode'
import { FootnoteNode } from '../components/FootnoteNode/FootnoteNode'
import { FootnoteReferenceNode } from '../components/FootnoteReferenceNode/FootnoteReferenceNode'
import { HardBreakNode } from '../components/HardBreakNode/HardBreakNode'
import { HeadingNode } from '../components/HeadingNode/HeadingNode'
import { HighlightNode } from '../components/HighlightNode/HighlightNode'
import { HtmlBlockNode } from '../components/HtmlBlockNode/HtmlBlockNode'
import { HtmlInlineNode } from '../components/HtmlInlineNode/HtmlInlineNode'
import { ImageNode } from '../components/ImageNode/ImageNode'
import { clampInfographicPreviewHeight, estimateInfographicPreviewHeight, parsePositiveNumber as parsePositiveInfographicNumber } from '../components/InfographicBlockNode/height'
import { InfographicBlockNode } from '../components/InfographicBlockNode/InfographicBlockNode'
import { InlineCodeNode } from '../components/InlineCodeNode/InlineCodeNode'
import { InsertNode } from '../components/InsertNode/InsertNode'
import { LinkNode } from '../components/LinkNode/LinkNode'
import { ListItemNode } from '../components/ListItemNode/ListItemNode'
import { ListNode } from '../components/ListNode/ListNode'
import { MathBlockNode } from '../components/MathBlockNode/MathBlockNode'
import { MathInlineNode } from '../components/MathInlineNode/MathInlineNode'
import { clampMermaidPreviewHeight, estimateMermaidPreviewHeight, parsePositiveNumber as parsePositiveMermaidNumber } from '../components/MermaidBlockNode/height'
import { MermaidBlockNode } from '../components/MermaidBlockNode/MermaidBlockNode'
import { FallbackComponent } from '../components/NodeRenderer/FallbackComponent'
import { ParagraphNode } from '../components/ParagraphNode/ParagraphNode'
import { ReferenceNode } from '../components/ReferenceNode/ReferenceNode'
import { StrikethroughNode } from '../components/StrikethroughNode/StrikethroughNode'
import { StrongNode } from '../components/StrongNode/StrongNode'
import { SubscriptNode } from '../components/SubscriptNode/SubscriptNode'
import { SuperscriptNode } from '../components/SuperscriptNode/SuperscriptNode'
import { TableNode } from '../components/TableNode/TableNode'
import { TextNode } from '../components/TextNode/TextNode'
import { ThematicBreakNode } from '../components/ThematicBreakNode/ThematicBreakNode'
import { VmrContainerNode } from '../components/VmrContainerNode/VmrContainerNode'
import { getCustomNodeComponents } from '../customComponents'
import { resolveCustomHtmlTag } from '../utils/customHtmlTag'
import { normalizeLanguageIdentifier } from '../utils/languageIcon'
import { renderNodeChildren } from './renderChildren'
function getRawCodeBlockLanguage(node: any) {
const trimmed = String(node?.language || '').trim()
if (!trimmed)
return ''
const [firstToken] = trimmed.split(/\s+/)
const [base] = firstToken.split(':')
return base.toLowerCase()
}
function renderCustomCodeBlockComponent(
component: any,
node: any,
key: React.Key,
ctx: RenderContext,
) {
return React.createElement(component, {
key,
node,
customId: ctx.customId,
isDark: ctx.isDark,
ctx,
renderNode,
indexKey: key,
typewriter: ctx.typewriter,
})
}
function getMermaidRenderProps(node: any, ctx: RenderContext) {
const next = { ...(ctx.mermaidProps || {}) } as Record<string, any>
if (parsePositiveMermaidNumber(next.estimatedPreviewHeightPx) == null) {
next.estimatedPreviewHeightPx = clampMermaidPreviewHeight(
estimateMermaidPreviewHeight(String(node?.code ?? '')),
undefined,
next.maxHeight === 'none' ? null : (parsePositiveMermaidNumber(next.maxHeight) ?? undefined),
)
}
return next
}
function getInfographicRenderProps(node: any, ctx: RenderContext) {
const next = { ...(ctx.infographicProps || {}) } as Record<string, any>
if (parsePositiveInfographicNumber(next.estimatedPreviewHeightPx) == null) {
next.estimatedPreviewHeightPx = clampInfographicPreviewHeight(
estimateInfographicPreviewHeight(String(node?.code ?? '')),
undefined,
next.maxHeight === 'none' ? null : (parsePositiveInfographicNumber(next.maxHeight) ?? undefined),
)
}
return next
}
function renderCodeBlock(
node: any,
key: React.Key,
ctx: RenderContext,
customComponents: Record<string, any>,
) {
const rawLanguage = getRawCodeBlockLanguage(node)
const language = normalizeLanguageIdentifier(rawLanguage)
const customForLanguage = rawLanguage ? customComponents[rawLanguage] : null
if (language === 'mermaid') {
const mermaidProps = getMermaidRenderProps(node, ctx)
const customMermaid = customForLanguage || customComponents.mermaid
if (customMermaid)
return React.createElement(customMermaid as any, { key, node, isDark: ctx.isDark, ...mermaidProps })
if (!ctx.renderCodeBlocksAsPre) {
return (
<MermaidBlockNode
key={key}
node={node as any}
isDark={ctx.isDark}
loading={Boolean(node.loading)}
{...mermaidProps}
/>
)
}
}
if (language === 'infographic') {
const infographicProps = getInfographicRenderProps(node, ctx)
const customInfographic = customForLanguage || customComponents.infographic
if (customInfographic)
return React.createElement(customInfographic as any, { key, node, isDark: ctx.isDark, ...infographicProps })
return (
<InfographicBlockNode
key={key}
node={node as any}
isDark={ctx.isDark}
loading={Boolean(node.loading)}
{...infographicProps}
/>
)
}
if (language === 'd2' || language === 'd2lang') {
const customD2 = customForLanguage || customComponents.d2
if (customD2)
return React.createElement(customD2 as any, { key, node, isDark: ctx.isDark, ...(ctx.d2Props || {}) })
return (
<D2BlockNode
key={key}
node={node as any}
isDark={ctx.isDark}
loading={Boolean(node.loading)}
{...(ctx.d2Props || {})}
/>
)
}
if (customForLanguage)
return renderCustomCodeBlockComponent(customForLanguage, node, key, ctx)
const customCodeBlock = customComponents.code_block
if (customCodeBlock)
return renderCustomCodeBlockComponent(customCodeBlock, node, key, ctx)
if (ctx.renderCodeBlocksAsPre || language === 'mermaid') {
return <PreCodeNode key={key} node={node} />
}
return (
<MonacoCodeBlockNode
key={key}
node={node}
loading={Boolean(node.loading)}
stream={ctx.codeBlockStream}
monacoOptions={ctx.codeBlockThemes?.monacoOptions}
themes={ctx.codeBlockThemes?.themes}
darkTheme={ctx.codeBlockThemes?.darkTheme}
lightTheme={ctx.codeBlockThemes?.lightTheme}
minWidth={ctx.codeBlockThemes?.minWidth}
maxWidth={ctx.codeBlockThemes?.maxWidth}
isDark={ctx.isDark}
onCopy={ctx.events.onCopy}
{...(ctx.codeBlockProps || {})}
/>
)
}
export function renderNode(node: ParsedNode, key: React.Key, ctx: RenderContext) {
const customComponents = ctx.customComponents ?? getCustomNodeComponents(ctx.customId)
const custom = node.type === 'code_block'
? null
: (customComponents as Record<string, any>)[node.type]
if (custom) {
return React.createElement(custom, {
key,
node,
customId: ctx.customId,
isDark: ctx.isDark,
ctx,
renderNode,
indexKey: key,
typewriter: ctx.typewriter,
})
}
if (node.type === 'html_block' || node.type === 'html_inline') {
const resolvedCustomTag = resolveCustomHtmlTag(node as any, customComponents as any, ctx.customHtmlTags)
const fallbackTag = String((node as any).tag ?? '').trim().toLowerCase() || getHtmlTagFromContent((node as any).content)
const tag = resolvedCustomTag?.tag ?? fallbackTag
if (tag) {
const customForTag = resolvedCustomTag?.component ?? (customComponents as Record<string, any>)[tag]
const isWhitelisted = resolvedCustomTag?.isWhitelisted ?? (ctx.customHtmlTags ?? []).some((t: string) => t.toLowerCase() === tag)
if (isWhitelisted && customForTag) {
const coerced = {
...(node as any),
type: tag,
tag,
content: stripCustomHtmlWrapper((node as any).content, tag),
}
return React.createElement(customForTag as any, {
key,
node: coerced,
customId: ctx.customId,
isDark: ctx.isDark,
ctx,
renderNode,
indexKey: key,
typewriter: ctx.typewriter,
})
}
const rawContent = String((node as any).content ?? (node as any).raw ?? '')
if (!isWhitelisted && shouldRenderUnknownHtmlTagAsText(rawContent, tag)) {
if (node.type === 'html_inline') {
return <TextNode key={key} node={{ type: 'text', content: rawContent, raw: rawContent } as any} ctx={ctx} indexKey={key} typewriter={ctx.typewriter} />
}
else {
return <ParagraphNode key={key} node={{ type: 'paragraph', children: [{ type: 'text', content: rawContent, raw: rawContent }], raw: rawContent } as any} ctx={ctx} renderNode={renderNode} indexKey={key} typewriter={ctx.typewriter} />
}
}
}
}
switch (node.type) {
case 'text':
return <TextNode key={key} node={node as any} ctx={ctx} indexKey={key} typewriter={ctx.typewriter} />
case 'text_special':
return <TextNode key={key} node={{ type: 'text', content: (node as any).content ?? '', center: (node as any).center } as any} ctx={ctx} indexKey={key} typewriter={ctx.typewriter} />
case 'paragraph':
return <ParagraphNode key={key} node={node as any} ctx={ctx} renderNode={renderNode} indexKey={key} typewriter={ctx.typewriter} />
case 'heading':
return <HeadingNode key={key} node={node as any} ctx={ctx} renderNode={renderNode} indexKey={key} typewriter={ctx.typewriter} />
case 'blockquote':
return <BlockquoteNode key={key} node={node as any} ctx={ctx} renderNode={renderNode} indexKey={key} typewriter={ctx.typewriter} />
case 'list':
return <ListNode key={key} node={node as any} ctx={ctx} renderNode={renderNode} indexKey={key} typewriter={ctx.typewriter} />
case 'list_item':
return <ListItemNode key={key} node={node as any} ctx={ctx} renderNode={renderNode} indexKey={key} typewriter={ctx.typewriter} />
case 'table':
return <TableNode key={key} node={node as any} ctx={ctx} renderNode={renderNode} indexKey={key} typewriter={ctx.typewriter} />
case 'definition_list':
return <DefinitionListNode key={key} node={node as any} ctx={ctx} renderNode={renderNode} indexKey={key} typewriter={ctx.typewriter} />
case 'footnote':
return <FootnoteNode key={key} node={node as any} ctx={ctx} renderNode={renderNode} indexKey={key} typewriter={ctx.typewriter} />
case 'footnote_reference':
return <FootnoteReferenceNode key={key} node={node as any} />
case 'footnote_anchor':
return <FootnoteAnchorNode key={key} node={node as any} />
case 'admonition':
return <AdmonitionNode key={key} node={node as any} ctx={ctx} renderNode={renderNode} indexKey={key} isDark={ctx.isDark} typewriter={ctx.typewriter} />
case 'hardbreak':
return <HardBreakNode key={key} node={node as any} typewriter={ctx.typewriter} />
case 'link':
return (
<LinkNode
key={key}
node={node as any}
ctx={ctx}
renderNode={renderNode}
indexKey={key}
isDark={ctx.isDark}
showTooltip={typeof ctx.showTooltips === 'boolean' ? ctx.showTooltips : undefined}
typewriter={ctx.typewriter}
/>
)
case 'image':
return (
<ImageNode
key={key}
node={node as any}
/>
)
case 'inline_code':
return <InlineCodeNode key={key} node={node as any} ctx={ctx} indexKey={key} typewriter={ctx.typewriter} />
case 'code_block':
return renderCodeBlock(node, key, ctx, customComponents)
case 'strong':
return (
<StrongNode
key={key}
node={node as any}
>
{renderNodeChildren((node as any).children, ctx, `${String(key)}-strong`, renderNode)}
</StrongNode>
)
case 'emphasis':
return (
<EmphasisNode key={key} node={node as any}>
{renderNodeChildren((node as any).children, ctx, `${String(key)}-em`, renderNode)}
</EmphasisNode>
)
case 'strikethrough':
return (
<StrikethroughNode key={key} node={node as any}>
{renderNodeChildren((node as any).children, ctx, `${String(key)}-strike`, renderNode)}
</StrikethroughNode>
)
case 'highlight':
return (
<HighlightNode key={key} node={node as any}>
{renderNodeChildren((node as any).children, ctx, `${String(key)}-highlight`, renderNode)}
</HighlightNode>
)
case 'insert':
return (
<InsertNode key={key} node={node as any}>
{renderNodeChildren((node as any).children, ctx, `${String(key)}-insert`, renderNode)}
</InsertNode>
)
case 'subscript':
return (
<SubscriptNode key={key} node={node as any}>
{renderNodeChildren((node as any).children, ctx, `${String(key)}-sub`, renderNode)}
</SubscriptNode>
)
case 'superscript':
return (
<SuperscriptNode key={key} node={node as any}>
{renderNodeChildren((node as any).children, ctx, `${String(key)}-sup`, renderNode)}
</SuperscriptNode>
)
case 'checkbox':
case 'checkbox_input':
return <CheckboxNode key={key} node={node as any} typewriter={ctx.typewriter} />
case 'emoji':
return <EmojiNode key={key} node={node as any} typewriter={ctx.typewriter} />
case 'thematic_break':
return <ThematicBreakNode key={key} />
case 'math_inline':
return <MathInlineNode key={key} node={node as any} />
case 'math_block':
return <MathBlockNode key={key} node={node as any} />
case 'reference':
return <ReferenceNode key={key} node={node as any} ctx={ctx} typewriter={ctx.typewriter} />
case 'html_block':
case 'html_inline':
return node.type === 'html_block'
? <HtmlBlockNode key={key} node={node as any} ctx={ctx} renderNode={renderNode} indexKey={key} typewriter={ctx.typewriter} customId={ctx.customId} />
: <HtmlInlineNode key={key} node={node as any} typewriter={ctx.typewriter} customId={ctx.customId} />
case 'vmr_container':
return <VmrContainerNode key={key} node={node as any} ctx={ctx} renderNode={renderNode} indexKey={key} typewriter={ctx.typewriter} />
case 'label_open':
case 'label_close':
return null
default:
return <FallbackComponent key={key} node={node as any} />
}
}