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,123 @@
import { describe, expect, it } from 'vitest'
import { resolveParsedNodes } from '../packages/markstream-angular/src/components/shared/node-helpers'
import {
clearGlobalCustomComponents,
getCustomNodeComponents,
removeCustomComponents,
setCustomComponents,
} from '../packages/markstream-angular/src/customComponents'
import {
disableKatex,
enableKatex,
isKatexEnabled,
setKatexLoader,
} from '../packages/markstream-angular/src/optional/katex'
import {
disableMermaid,
enableMermaid,
isMermaidEnabled,
setMermaidLoader,
} from '../packages/markstream-angular/src/optional/mermaid'
import { buildKaTeXCDNWorkerSource } from '../packages/markstream-angular/src/workers/katexCdnWorker'
import { buildMermaidCDNWorkerSource } from '../packages/markstream-angular/src/workers/mermaidCdnWorker'
describe('markstream-angular api parity helpers', () => {
it('merges global and scoped custom components like react/vue2', () => {
const GlobalCode = class {}
const ScopedThinking = class {}
clearGlobalCustomComponents()
setCustomComponents({ code_block: GlobalCode as any })
setCustomComponents('scope-a', { thinking: ScopedThinking as any })
expect(getCustomNodeComponents()).toMatchObject({
code_block: GlobalCode,
})
expect(getCustomNodeComponents('scope-a')).toMatchObject({
code_block: GlobalCode,
thinking: ScopedThinking,
})
removeCustomComponents('scope-a')
clearGlobalCustomComponents()
expect(getCustomNodeComponents('scope-a')).toEqual({})
})
it('supports toggling katex and mermaid loaders', () => {
const katexLoader = () => ({ renderToString: () => '<span />' })
const mermaidLoader = () => ({
render: async () => ({ svg: '<svg />' }),
initialize() {},
})
disableKatex()
disableMermaid()
expect(isKatexEnabled()).toBe(false)
expect(isMermaidEnabled()).toBe(false)
enableKatex(katexLoader)
enableMermaid(mermaidLoader)
expect(isKatexEnabled()).toBe(true)
expect(isMermaidEnabled()).toBe(true)
setKatexLoader(null)
setMermaidLoader(null)
expect(isKatexEnabled()).toBe(false)
expect(isMermaidEnabled()).toBe(false)
enableKatex()
enableMermaid()
})
it('passes customHtmlTags through top-level streaming parsing', () => {
const nodes = resolveParsedNodes({
content: '<thinking>我们需要给出一个全面的回答',
final: false,
customHtmlTags: ['thinking'],
})
expect(nodes).toHaveLength(1)
expect((nodes[0] as any)?.type).toBe('thinking')
expect((nodes[0] as any)?.loading).toBe(true)
expect(String((nodes[0] as any)?.content || '')).toContain('全面的回答')
})
it('keeps streaming thinking content grouped across mixed markdown prefixes', () => {
const source = `<thinking>
我们需要给出一个全面的回答,可能包括不同的实现方法及其分析。
$x = a$
- 递归
- 动态规划
`
for (const size of [16, 36, 72, source.length]) {
const nodes = resolveParsedNodes({
content: source.slice(0, size),
final: false,
customHtmlTags: ['thinking'],
})
expect(nodes).toHaveLength(1)
expect((nodes[0] as any)?.type).toBe('thinking')
expect(String((nodes[0] as any)?.content || '')).not.toContain('<thinking>')
}
})
it('exposes worker helper source builders for katex and mermaid parity', () => {
const katexSource = buildKaTeXCDNWorkerSource({
katexUrl: 'https://cdn.example.com/katex.mjs',
mode: 'module',
})
const mermaidSource = buildMermaidCDNWorkerSource({
mermaidUrl: 'https://cdn.example.com/mermaid.mjs',
mode: 'module',
})
expect(katexSource).toContain('renderToString')
expect(katexSource).toContain('postMessage')
expect(mermaidSource).toContain('findPrefix')
expect(mermaidSource).toContain('canParse')
})
})