feat(workspace): 完成图片资产存储与引用

This commit is contained in:
2026-09-13 21:52:14 +08:00
parent c87b56a13f
commit 9c35f54560
16 changed files with 655 additions and 10 deletions
+46
View File
@@ -12,6 +12,17 @@ import * as noteService from './noteService'
import { splitNoteMetadata } from '@/utils/noteMetadata'
import { contentHash, hostInvoke, isDesktop, nativePath, nativeTree, type HostDocument, type HostEntry, type HostVault } from './platform/desktop'
export interface WorkspaceAsset {
asset_id: string
path: string
content_hash: string
media_type: string
size: number
original_name: string
}
export type WorkspaceAssetSource = 'paste' | 'drop' | 'upload'
/** Web 联调只连接 AI Core 配置的单一 Vault;多 Vault 选择由 Tauri Host 接管。 */
export interface VaultInfo {
vault_id: string
@@ -147,6 +158,41 @@ export async function readFileContent(filePath: string): Promise<string> {
return note.markdown
}
/** 将 Vault 根路径转换为相对当前笔记的可移植 Markdown 引用。 */
export function workspaceAssetReference(notePath: string, assetPath: string): string {
const noteParts = relativePath(notePath).split('/').filter(Boolean)
noteParts.pop()
return `${'../'.repeat(noteParts.length)}${relativePath(assetPath)}`
}
/** 将笔记内的相对附件引用还原为 Vault 根路径。 */
export function resolveWorkspaceAssetPath(notePath: string, reference: string): string | null {
if (/^(?:[a-z]+:|\/\/|#)/i.test(reference)) return null
const parts = [...relativePath(notePath).split('/').slice(0, -1)]
for (const part of reference.replace(/\\/g, '/').split('/')) {
if (!part || part === '.') continue
if (part === '..') { if (!parts.length) return null; parts.pop() }
else parts.push(part)
}
const path = parts.join('/')
return path.startsWith('attachments/') ? path : null
}
export async function storeWorkspaceImage(
file: Blob & { name?: string }, source: WorkspaceAssetSource,
notePath: string, noteId: string | null,
): Promise<WorkspaceAsset & { reference: string }> {
const asset = await apiClient.postBinary<WorkspaceAsset>('/api/workspace/assets', file, { 'Content-Type': 'application/octet-stream' }, {
filename: file.name || 'image', note_id: noteId || '', note_path: notePath, source,
})
return { ...asset, reference: workspaceAssetReference(notePath, asset.path) }
}
export async function loadWorkspaceImage(path: string, notePath = '', noteId: string | null = null): Promise<Blob> {
const response = await apiClient.get<Response>('/api/workspace/assets/content', { params: { path, note_path: notePath, note_id: noteId || '' } })
return response.blob()
}
/** 解析已与工作空间路径关联的后端笔记标识。 */
export async function getNoteId(filePath: string): Promise<string> {
return requireNoteId(filePath)