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:
@@ -0,0 +1,212 @@
|
||||
import { getMarkdown, parseMarkdownToStructure } from 'stream-markdown-parser'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
function expectDetailsContainer(markdown: string, name: string) {
|
||||
for (const final of [false, true]) {
|
||||
const md = getMarkdown(`${name}-${final}`)
|
||||
const nodes = parseMarkdownToStructure(markdown, md, { final }) as any[]
|
||||
|
||||
expect(nodes).toHaveLength(1)
|
||||
expect(nodes[0]?.type).toBe('html_block')
|
||||
expect(nodes[0]?.tag).toBe('details')
|
||||
expect(String(nodes[0]?.raw ?? '')).toBe(markdown)
|
||||
}
|
||||
}
|
||||
|
||||
describe('html block details regression', () => {
|
||||
it('keeps details as one container node while rendering trailing blocks into html children content', () => {
|
||||
const markdown = `<details style="color:gray;background-color: #f8f8f8;padding: 8px;border-radius: 4px;" open> <summary> Thinking... </summary>
|
||||
好的,我需要帮用户生成首次病程记录。首先得仔细阅读提供的入院记录内容,确保所有信息都被正确提取。这里是详情
|
||||
|
||||
首先。。。。
|
||||
</details>
|
||||
`
|
||||
|
||||
expectDetailsContainer(markdown, 'html-block-details-regression')
|
||||
|
||||
const md = getMarkdown('html-block-details-regression-structure')
|
||||
const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[]
|
||||
const details = nodes[0]
|
||||
const summary = details?.children?.[0]
|
||||
const introParagraph = details?.children?.[1]
|
||||
|
||||
expect(String(details?.content ?? '')).toContain('<p>首先。。。。</p>')
|
||||
expect(details?.attrs).toEqual([
|
||||
['style', 'color:gray;background-color: #f8f8f8;padding: 8px;border-radius: 4px;'],
|
||||
['open', ''],
|
||||
])
|
||||
expect(details?.children).toHaveLength(3)
|
||||
expect(summary?.type).toBe('html_block')
|
||||
expect(summary?.tag).toBe('summary')
|
||||
expect(summary?.attrs).toBeUndefined()
|
||||
expect(summary?.children?.[0]?.type).toBe('paragraph')
|
||||
expect(summary?.children?.[0]?.children?.[0]?.content).toContain('Thinking')
|
||||
expect(introParagraph?.type).toBe('paragraph')
|
||||
expect(introParagraph?.children?.[0]?.content).toContain('这里是详情')
|
||||
expect(details?.children?.[2]?.type).toBe('paragraph')
|
||||
expect(details?.children?.[2]?.children?.[0]?.content).toBe('首先。。。。')
|
||||
})
|
||||
|
||||
it('keeps the blank line paragraph inside details while streaming before the closing tag arrives', () => {
|
||||
const chunks = [
|
||||
`<details style="color:gray;background-color: #f8f8f8;padding: 8px;border-radius: 4px;" open> <summary> Thinking... </summary>
|
||||
好的,我需要帮用户生成首次病程记录。首先得仔细阅读提供的入院记录内容,确保所有信息都被正确提取。这里是详情
|
||||
|
||||
`,
|
||||
`首先。。。。
|
||||
`,
|
||||
'</details>',
|
||||
]
|
||||
|
||||
const md = getMarkdown('html-block-details-streaming')
|
||||
let markdown = ''
|
||||
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
markdown += chunks[i]
|
||||
const final = i === chunks.length - 1
|
||||
const nodes = parseMarkdownToStructure(markdown, md, { final }) as any[]
|
||||
const hasTrailingParagraph = markdown.includes('首先。。。。')
|
||||
|
||||
expect(nodes).toHaveLength(1)
|
||||
expect(nodes[0]?.type).toBe('html_block')
|
||||
expect(nodes[0]?.tag).toBe('details')
|
||||
|
||||
if (!final) {
|
||||
expect(nodes[0]?.loading).toBe(true)
|
||||
if (hasTrailingParagraph) {
|
||||
expect(String(nodes[0]?.content ?? '')).toContain('<p>首先。。。。</p>')
|
||||
expect(nodes[0]?.children?.[0]?.tag).toBe('summary')
|
||||
expect(nodes[0]?.children?.[1]?.type).toBe('paragraph')
|
||||
expect(nodes[0]?.children?.[2]?.type).toBe('paragraph')
|
||||
}
|
||||
else {
|
||||
expect(String(nodes[0]?.content ?? '')).not.toContain('<p>首先。。。。</p>')
|
||||
expect(nodes[0]?.children?.[0]?.tag).toBe('summary')
|
||||
expect(nodes[0]?.children?.[1]?.type).toBe('paragraph')
|
||||
expect(nodes[0]?.children ?? []).toHaveLength(2)
|
||||
}
|
||||
}
|
||||
|
||||
if (final) {
|
||||
expect(nodes[0]?.loading).toBe(false)
|
||||
expect(String(nodes[0]?.content ?? '')).toContain('<p>首先。。。。</p>')
|
||||
expect(String(nodes[0]?.raw ?? '')).toBe(markdown)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps nested details blocks grouped under the outer details container', () => {
|
||||
const markdown = `<details open>
|
||||
<summary>outer</summary>
|
||||
outer start
|
||||
|
||||
<details>
|
||||
<summary>inner</summary>
|
||||
|
||||
inner body
|
||||
</details>
|
||||
|
||||
after inner
|
||||
</details>`
|
||||
|
||||
expectDetailsContainer(markdown, 'html-block-details-nested')
|
||||
|
||||
const md = getMarkdown('html-block-details-nested-structure')
|
||||
const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[]
|
||||
const outer = nodes[0]
|
||||
const outerSummary = outer?.children?.find((child: any) => child?.type === 'html_block' && child?.tag === 'summary')
|
||||
const inner = outer?.children?.find((child: any) => child?.type === 'html_block' && child?.tag === 'details')
|
||||
const trailingParagraph = outer?.children?.find((child: any) => child?.type === 'paragraph' && child?.children?.[0]?.content === 'after inner')
|
||||
|
||||
expect(outerSummary).toBeDefined()
|
||||
expect(inner).toBeDefined()
|
||||
expect(inner?.children?.[0]?.tag).toBe('summary')
|
||||
expect(String(inner?.content ?? '')).toContain('<p>inner body</p>')
|
||||
expect(trailingParagraph?.children?.[0]?.content).toBe('after inner')
|
||||
})
|
||||
|
||||
it('keeps complex markdown children inside details and preserves the trailing closing newline', () => {
|
||||
const markdown = `<details open>
|
||||
<summary>sum</summary>
|
||||
|
||||
- a
|
||||
- b
|
||||
|
||||
> q
|
||||
|
||||
\`\`\`js
|
||||
console.log(1)
|
||||
\`\`\`
|
||||
|
||||
tail
|
||||
</details>
|
||||
`
|
||||
|
||||
const md = getMarkdown('html-block-details-complex')
|
||||
const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[]
|
||||
const details = nodes[0]
|
||||
|
||||
expect(nodes).toHaveLength(1)
|
||||
expect(details?.tag).toBe('details')
|
||||
expect(String(details?.raw ?? '')).toBe(markdown)
|
||||
expect(String(details?.content ?? '')).toContain('<ul>')
|
||||
expect(String(details?.content ?? '')).toContain('<blockquote>')
|
||||
expect(String(details?.content ?? '')).toContain('data-lang="js"')
|
||||
expect(String(details?.content ?? '')).toContain('<p>tail</p>')
|
||||
expect(details?.children?.map((child: any) => child?.type === 'html_block' ? `${child?.type}:${child?.tag}` : child?.type))
|
||||
.toEqual(['html_block:summary', 'list', 'blockquote', 'code_block', 'paragraph'])
|
||||
})
|
||||
|
||||
it('parses markdown inside summary into summary children', () => {
|
||||
const markdown = `<details open>
|
||||
<summary>**sum** _x_</summary>
|
||||
|
||||
body
|
||||
</details>
|
||||
`
|
||||
|
||||
const md = getMarkdown('html-block-details-summary-markdown')
|
||||
const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[]
|
||||
const details = nodes[0]
|
||||
const summary = details?.children?.[0]
|
||||
const summaryParagraph = summary?.children?.[0]
|
||||
|
||||
expect(nodes).toHaveLength(1)
|
||||
expect(details?.tag).toBe('details')
|
||||
expect(summary?.tag).toBe('summary')
|
||||
expect(details?.attrs).toEqual([['open', '']])
|
||||
expect(summaryParagraph?.type).toBe('paragraph')
|
||||
expect(summaryParagraph?.children?.[0]?.type).toBe('strong')
|
||||
expect(summaryParagraph?.children?.[0]?.children?.[0]?.content).toBe('sum')
|
||||
expect(summaryParagraph?.children?.[2]?.type).toBe('emphasis')
|
||||
expect(summaryParagraph?.children?.[2]?.children?.[0]?.content).toBe('x')
|
||||
expect(details?.children?.[1]?.children?.[0]?.content).toBe('body')
|
||||
})
|
||||
|
||||
it('keeps opener content before and after summary as structured children', () => {
|
||||
const markdown = `<details open>
|
||||
lead
|
||||
<summary>sum</summary>
|
||||
text
|
||||
<div>inner</div>
|
||||
|
||||
para
|
||||
</details>
|
||||
`
|
||||
|
||||
const md = getMarkdown('html-block-details-opener-mixed')
|
||||
const nodes = parseMarkdownToStructure(markdown, md, { final: true }) as any[]
|
||||
const details = nodes[0]
|
||||
|
||||
expect(nodes).toHaveLength(1)
|
||||
expect(details?.tag).toBe('details')
|
||||
expect(details?.attrs).toEqual([['open', '']])
|
||||
expect(details?.children?.map((child: any) => child?.type === 'html_block' ? `${child?.type}:${child?.tag}` : child?.type))
|
||||
.toEqual(['paragraph', 'html_block:summary', 'paragraph', 'html_block:div', 'paragraph'])
|
||||
expect(details?.children?.[0]?.children?.[0]?.content).toBe('lead')
|
||||
expect(details?.children?.[1]?.children?.[0]?.children?.[0]?.content).toBe('sum')
|
||||
expect(details?.children?.[2]?.children?.[0]?.content).toBe('text')
|
||||
expect(details?.children?.[3]?.tag).toBe('div')
|
||||
expect(details?.children?.[4]?.children?.[0]?.content).toBe('para')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user