@tutti-os/workspace-file-reference 0.0.28 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tutti-os/workspace-file-reference",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -36,10 +36,10 @@
36
36
  "directory": "packages/workspace/file-reference"
37
37
  },
38
38
  "dependencies": {
39
- "@tutti-os/ui-react-hooks": "0.0.28",
40
- "@tutti-os/ui-system": "0.0.28",
41
- "@tutti-os/workspace-file-manager": "0.0.28",
42
- "@tutti-os/workspace-file-preview": "0.0.28",
39
+ "@tutti-os/ui-react-hooks": "0.0.30",
40
+ "@tutti-os/ui-system": "0.0.30",
41
+ "@tutti-os/workspace-file-manager": "0.0.30",
42
+ "@tutti-os/workspace-file-preview": "0.0.30",
43
43
  "valtio": "^2.3.2"
44
44
  },
45
45
  "devDependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/referenceSourceUtils.ts","../src/core/referenceSourceAggregator.ts","../src/core/referenceListSource.ts","../src/core/referenceFilterCategories.ts","../src/core/index.ts"],"sourcesContent":["import type {\n NodeRef,\n ReferenceNode,\n SelectedReference\n} from \"../contracts/referenceSource.ts\";\nimport type { WorkspaceFileReference } from \"../contracts/index.ts\";\n\n/**\n * Reference Source Services 的共享 base 工具(形状处理,非取数)。\n * 设计见 docs/architecture/agent-reference-source-services.md §1 / §2。\n */\n\n/**\n * 「回到源根」哨兵 nodeId。源在 listSidebarGroups 里用它表示一个等价于源根的\n * 二级分组(如本地源的「个人」= 用户 home 根);picker 选中它走 navigateToRoot\n * 而非 ensureChildren,避免把哨兵当真实路径打后端。\n */\nexport const WORKSPACE_ROOT_GROUP_NODE_ID = \"__workspace_root__\";\n\n/** 节点的全局 key,用于 picker 树状态索引与去重。picker 不解析其内部结构。 */\nexport function nodeRefKey(ref: NodeRef): string {\n return `${ref.sourceId}\u0000${ref.nodeId}`;\n}\n\nexport function nodeKey(node: ReferenceNode): string {\n return nodeRefKey(node.ref);\n}\n\n/** 任意输入归一为 \"file\" | \"folder\"。 */\nexport function normalizeReferenceNodeKind(\n kind: string | null | undefined\n): \"file\" | \"folder\" {\n return kind === \"folder\" || kind === \"directory\" ? \"folder\" : \"file\";\n}\n\n/** folder 在前、同类按 displayName 本地化排序。 */\nexport function sortReferenceNodes(\n nodes: readonly ReferenceNode[]\n): ReferenceNode[] {\n return [...nodes].sort((left, right) => {\n if (left.kind !== right.kind) {\n return left.kind === \"folder\" ? -1 : 1;\n }\n return left.displayName.localeCompare(right.displayName, undefined, {\n sensitivity: \"base\"\n });\n });\n}\n\n/** 按 node key 去重,保留首次出现。支持 cursor append 的稳定去重。 */\nexport function dedupeReferenceNodes(\n nodes: readonly ReferenceNode[]\n): ReferenceNode[] {\n const seen = new Set<string>();\n const result: ReferenceNode[] = [];\n for (const node of nodes) {\n const key = nodeKey(node);\n if (seen.has(key)) {\n continue;\n }\n seen.add(key);\n result.push(node);\n }\n return result;\n}\n\n/** 续页累积:把新一页 append 到已有项后并去重(cursor 语义,不重排)。 */\nexport function appendReferencePage(\n current: readonly ReferenceNode[],\n next: readonly ReferenceNode[]\n): ReferenceNode[] {\n return dedupeReferenceNodes([...current, ...next]);\n}\n\n/** SelectedReference 与现有 WorkspaceFileReference 形状兼容,便于下游零改动复用。 */\nexport function selectedReferenceToWorkspaceFileReference(\n selected: SelectedReference\n): WorkspaceFileReference {\n return {\n path: selected.path,\n kind: selected.kind,\n ...(selected.displayName ? { displayName: selected.displayName } : {})\n };\n}\n","import type {\n ListChildrenResult,\n NodeRef,\n ReferenceNode,\n ReferencePreview,\n ReferenceScope,\n ReferenceSourceRegistry,\n ReferenceSourceService,\n SearchInput,\n SearchResult,\n SelectedReference\n} from \"../contracts/referenceSource.ts\";\n\n/**\n * 多源聚合器:根层级 = 每个可用源一个 folder 节点;其余按 node.ref.sourceId 委派。\n * 设计见 docs/architecture/agent-reference-source-services.md §3 / §4.4。\n */\n\n/**\n * 源根节点的 nodeId 哨兵。由聚合器创建源根节点时使用;\n * 任何真实源都不会产出 nodeId 等于此值的子节点(workspace 源 nodeId 是路径,\n * app 源 nodeId 以 \"app:\" 开头),因此路由无歧义。\n */\nexport const SOURCE_ROOT_NODE_ID = \"\u0000source-root\";\n\n/** 顶部 tab 的描述(source 身份 + 能力)。 */\nexport interface ReferenceSourceTab {\n sourceId: string;\n label: string;\n icon?: string;\n capabilities: ReferenceSourceService[\"capabilities\"];\n}\n\nexport interface ReferenceSourceAggregator {\n /** 顶部 tab 列表:每个可用源一个 tab(已过滤 isAvailable、按 order 排序)。 */\n listSources(scope: ReferenceScope): Promise<ReferenceSourceTab[]>;\n /** (备用,单树多根形态)根层级:每个可用源一个 folder 节点。 */\n listRoot(scope: ReferenceScope): Promise<ReferenceNode[]>;\n listChildren(\n scope: ReferenceScope,\n node: NodeRef,\n input?: {\n cursor?: string | null;\n filter?: string | null;\n signal?: AbortSignal;\n }\n ): Promise<ListChildrenResult>;\n search(\n scope: ReferenceScope,\n sourceId: string,\n input: SearchInput\n ): Promise<SearchResult>;\n open(scope: ReferenceScope, node: ReferenceNode): Promise<void>;\n readPreview(\n scope: ReferenceScope,\n node: ReferenceNode\n ): Promise<ReferencePreview | null>;\n resolveSelection(node: ReferenceNode): SelectedReference;\n /**\n * 把语义定位参数解析为某源内从根到目标分组的 NodeRef 路径(root → leaf)。\n * 源未实现 locateTarget / 未找到时返回 null。需先 listSources/listRoot 触发源加载。\n */\n locateTarget(\n scope: ReferenceScope,\n sourceId: string,\n params: Record<string, string>\n ): Promise<NodeRef[] | null>;\n /** 取已加载的源(用于读取 capabilities/metadata)。需先 listRoot 触发加载。 */\n getLoadedSource(sourceId: string): ReferenceSourceService | undefined;\n}\n\nexport function isSourceRootNode(ref: NodeRef): boolean {\n return ref.nodeId === SOURCE_ROOT_NODE_ID;\n}\n\nexport function createReferenceSourceAggregator(\n registry: ReferenceSourceRegistry\n): ReferenceSourceAggregator {\n // 同一 scope 内源稳定;缓存便于 resolveSelection 同步取源。\n const sourceMap = new Map<string, ReferenceSourceService>();\n\n async function loadSources(\n scope: ReferenceScope\n ): Promise<ReferenceSourceService[]> {\n const sources = await registry.getSources(scope);\n sourceMap.clear();\n for (const source of sources) {\n sourceMap.set(source.metadata.id, source);\n }\n return sources;\n }\n\n function requireSource(sourceId: string): ReferenceSourceService {\n const source = sourceMap.get(sourceId);\n if (!source) {\n throw new Error(`reference source not found: ${sourceId}`);\n }\n return source;\n }\n\n return {\n async listSources(scope) {\n const sources = await loadSources(scope);\n return sources.map((source) => ({\n sourceId: source.metadata.id,\n label: source.metadata.label,\n ...(source.metadata.icon ? { icon: source.metadata.icon } : {}),\n capabilities: source.capabilities\n }));\n },\n\n async listRoot(scope) {\n const sources = await loadSources(scope);\n return sources.map((source) => ({\n ref: { sourceId: source.metadata.id, nodeId: SOURCE_ROOT_NODE_ID },\n kind: \"folder\",\n displayName: source.metadata.label,\n hasChildren: true\n }));\n },\n\n async listChildren(scope, node, input) {\n const source = requireSource(node.sourceId);\n return source.listChildren(scope, {\n node: isSourceRootNode(node) ? null : node,\n cursor: input?.cursor ?? null,\n filter: input?.filter ?? null,\n signal: input?.signal\n });\n },\n\n async search(scope, sourceId, input) {\n const source = requireSource(sourceId);\n if (!source.search) {\n return { entries: [], nextCursor: null };\n }\n return source.search(scope, input);\n },\n\n async open(scope, node) {\n const source = requireSource(node.ref.sourceId);\n await source.open?.(scope, node);\n },\n\n async readPreview(scope, node) {\n const source = requireSource(node.ref.sourceId);\n return (await source.readPreview?.(scope, node)) ?? null;\n },\n\n resolveSelection(node) {\n return requireSource(node.ref.sourceId).resolveSelection(node);\n },\n\n async locateTarget(scope, sourceId, params) {\n // 源可能尚未加载(getLoadedSource 依赖先前 listSources/listRoot);此处兜底加载一次。\n if (!sourceMap.has(sourceId)) {\n await loadSources(scope);\n }\n const source = sourceMap.get(sourceId);\n if (!source?.locateTarget) {\n return null;\n }\n return (await source.locateTarget(scope, params)) ?? null;\n },\n\n getLoadedSource(sourceId) {\n return sourceMap.get(sourceId);\n }\n };\n}\n\n/** 静态 registry:持有一组源,按 isAvailable 过滤、按 order 排序。 */\nexport function createStaticReferenceSourceRegistry(\n sources: readonly ReferenceSourceService[]\n): ReferenceSourceRegistry {\n return {\n async getSources(scope) {\n const availability = await Promise.all(\n sources.map(async (source) => {\n try {\n return await source.isAvailable(scope);\n } catch {\n return false;\n }\n })\n );\n return sources\n .filter((_, index) => availability[index])\n .sort((left, right) => left.metadata.order - right.metadata.order);\n }\n };\n}\n","import type {\n ListChildrenResult,\n NodeRef,\n ReferenceHandle,\n ReferenceNode,\n ReferencePreview,\n ReferenceScope,\n ReferenceSourceCapabilities,\n ReferenceSourceService,\n SearchInput,\n SearchResult,\n SelectedReference\n} from \"../contracts/referenceSource.ts\";\nimport type {\n WorkspaceFileReference,\n WorkspaceFileReferenceAdapter\n} from \"../contracts/index.ts\";\n\n/**\n * 统一的「引用列表协议」—— 与 app references 同一套 group/reference/parentGroupId/cursor 形状。\n * 各源(应用 / issue / 任务…)各自实现一个 ReferenceListBackend 把自家数据映射成此协议;\n * createReferenceListSource 把协议 item 映射成 ReferenceNode,各源前端逻辑完全复用。\n * 设计见 docs/architecture/agent-reference-source-services.md。\n */\n\nexport interface ReferenceListGroup {\n type: \"group\";\n /** 不透明分组 id;作为下钻的 parentGroupId 原样回传。 */\n id: string;\n displayName: string;\n /** 可选的展示上下文,用于详情/搜索副标题,避免 UI 泄露不透明 group id。 */\n parentLabel?: string | null;\n referenceCount?: number | null;\n /** 可选分组图标(data URL / 远程 URL),如应用产物源的 app 图标。 */\n iconUrl?: string | null;\n}\n\nexport interface ReferenceListFile {\n /** daemon/host 可解析的文件路径。 */\n path: string;\n displayName?: string | null;\n /**\n * 可选的归属标签(如应用名 / 议题标题),用于搜索结果副标题。\n * 各源在「跨整源搜索拍平」时填入父级上下文;不填则副标题回退展示 nodeId。\n */\n parentLabel?: string | null;\n sizeBytes?: number | null;\n mtimeMs?: number | null;\n mimeType?: string | null;\n}\n\nexport interface ReferenceListReference {\n type: \"reference\";\n reference: ReferenceListFile;\n}\n\nexport type ReferenceListItem = ReferenceListGroup | ReferenceListReference;\n\nexport interface ReferenceListRequest {\n /** null = 根层级。 */\n parentGroupId: string | null;\n cursor?: string | null;\n filter?: string | null;\n signal?: AbortSignal;\n}\n\nexport interface ReferenceListResult {\n items: ReferenceListItem[];\n nextCursor?: string | null;\n}\n\n/** 递归搜索请求(跨整源,非当前层 filter)。 */\nexport interface ReferenceListSearchRequest {\n query: string;\n /** 已选文件类型筛选分类 id(全局统一口径);空数组/缺省 = 不按类型过滤。 */\n filters?: string[];\n cursor?: string | null;\n limit?: number;\n signal?: AbortSignal;\n /**\n * 可选:把搜索限定在某个分组下(协议层不透明 group id,与 list 返回的 group.id 同形)。\n * 缺省/null = 跨整源搜索。由 createReferenceListSource 从 SearchInput.withinNodeId 解码而来。\n */\n withinGroupId?: string | null;\n}\n\n/** 各源自治的取数适配器:把自家数据映射成统一协议。 */\nexport interface ReferenceListBackend {\n list(\n scope: ReferenceScope,\n request: ReferenceListRequest\n ): Promise<ReferenceListResult>;\n /**\n * 可选:递归搜索。实现即代表该源支持全局搜索(对应 capabilities.searchable)。\n * 返回 flat reference items(协议层不返回 group)。\n */\n search?(\n scope: ReferenceScope,\n request: ReferenceListSearchRequest\n ): Promise<ReferenceListResult>;\n /**\n * 可选:把语义定位参数解析为从根到目标分组的「不透明分组 id」路径(root → leaf),\n * 用各源自家的 group id 方案(与 list 返回的 group.id 同形),wrapper 负责编成 NodeRef。\n * 返回 null 表示不支持或未找到。\n */\n locate?(\n scope: ReferenceScope,\n params: Record<string, string>\n ): Promise<string[] | null>;\n /**\n * 可选:把自家「不透明分组 id」解码成可被 agent 解析的领域句柄(见 ReferenceHandle)。\n * navigable 源实现;wrapper 负责先剥掉 GROUP_PREFIX 再调本方法。\n */\n describeHandle?(groupId: string): ReferenceHandle | null;\n}\n\nexport interface CreateReferenceListSourceInput {\n sourceId: string;\n label: string;\n order?: number;\n /** 可选源图标令牌(如 \"issue\"),供 picker 决定二级分组的兜底图标。 */\n icon?: string;\n capabilities: ReferenceSourceCapabilities;\n isAvailable: (scope: ReferenceScope) => boolean | Promise<boolean>;\n backend: ReferenceListBackend;\n /** open/preview 复用 host 链路(协议返回的 path 可被解析打开)。 */\n adapter: WorkspaceFileReferenceAdapter;\n}\n\nconst GROUP_PREFIX = \"g:\";\nconst FILE_PREFIX = \"f:\";\n\nexport function createReferenceListSource(\n input: CreateReferenceListSourceInput\n): ReferenceSourceService {\n const { sourceId, label, capabilities, isAvailable, backend, adapter } =\n input;\n\n function fileReferenceOf(node: ReferenceNode): WorkspaceFileReference {\n return { path: decodeSegment(FILE_PREFIX, node.ref.nodeId), kind: \"file\" };\n }\n\n const service: ReferenceSourceService = {\n metadata: {\n id: sourceId,\n label,\n order: input.order ?? 0,\n ...(input.icon ? { icon: input.icon } : {})\n },\n capabilities,\n isAvailable,\n\n async listChildren(\n scope: ReferenceScope,\n { node, cursor, filter }\n ): Promise<ListChildrenResult> {\n // file 节点没有子节点。\n if (node && node.nodeId.startsWith(FILE_PREFIX)) {\n return { entries: [], nextCursor: null };\n }\n const parentGroupId = node\n ? decodeSegment(GROUP_PREFIX, node.nodeId)\n : null;\n const result = await backend.list(scope, {\n parentGroupId,\n cursor: cursor ?? null,\n filter: filter ?? null\n });\n return {\n entries: result.items.map((item) => itemToNode(sourceId, item)),\n nextCursor: result.nextCursor ?? null\n };\n },\n\n async open(_scope: ReferenceScope, node: ReferenceNode): Promise<void> {\n await adapter.openReference?.(fileReferenceOf(node));\n },\n\n async readPreview(\n scope: ReferenceScope,\n node: ReferenceNode\n ): Promise<ReferencePreview | null> {\n if (!adapter.readReferencePreview) {\n return null;\n }\n return adapter.readReferencePreview({\n workspaceId: scope.workspaceId,\n reference: fileReferenceOf(node)\n });\n },\n\n resolveSelection(node: ReferenceNode): SelectedReference {\n return {\n path: decodeSegment(FILE_PREFIX, node.ref.nodeId),\n kind: \"file\",\n ...(node.displayName ? { displayName: node.displayName } : {})\n };\n }\n };\n\n // 仅当 backend 实现了定位时才暴露 locateTarget;把不透明分组 id 路径编成 NodeRef 路径。\n const backendLocate = backend.locate?.bind(backend);\n if (backendLocate) {\n service.locateTarget = async (\n scope: ReferenceScope,\n params: Record<string, string>\n ): Promise<NodeRef[] | null> => {\n const groupIds = await backendLocate(scope, params);\n if (!groupIds) {\n return null;\n }\n return groupIds.map((groupId) => ({\n sourceId,\n nodeId: GROUP_PREFIX + base64UrlEncode(groupId)\n }));\n };\n }\n\n // 仅当 backend 实现了句柄解码时才暴露 describeReferenceHandle:剥掉 GROUP_PREFIX\n // 得到 backend 自家分组 id,交给 backend 解码成领域句柄。非分组节点(文件/源根)→ null。\n const backendDescribeHandle = backend.describeHandle?.bind(backend);\n if (backendDescribeHandle) {\n service.describeReferenceHandle = (\n node: ReferenceNode\n ): ReferenceHandle | null => {\n if (!node.ref.nodeId.startsWith(GROUP_PREFIX)) {\n return null;\n }\n return backendDescribeHandle(\n decodeSegment(GROUP_PREFIX, node.ref.nodeId)\n );\n };\n }\n\n // 仅当 backend 实现了递归搜索时才暴露 search,与 capabilities.searchable 保持一致。\n const backendSearch = backend.search?.bind(backend);\n if (backendSearch) {\n service.search = async (\n scope: ReferenceScope,\n input: SearchInput\n ): Promise<SearchResult> => {\n // 把左栏选中的分组节点(GROUP_PREFIX nodeId)解码成协议层不透明 group id;\n // 非分组(源根 / 文件节点 / 缺省)→ null,退回跨整源搜索。\n const withinGroupId =\n input.withinNodeId && input.withinNodeId.startsWith(GROUP_PREFIX)\n ? decodeSegment(GROUP_PREFIX, input.withinNodeId)\n : null;\n const result = await backendSearch(scope, {\n query: input.query,\n cursor: input.cursor ?? null,\n ...(input.filters && input.filters.length > 0\n ? { filters: input.filters }\n : {}),\n ...(input.limit == null ? {} : { limit: input.limit }),\n ...(input.signal ? { signal: input.signal } : {}),\n ...(withinGroupId == null ? {} : { withinGroupId })\n });\n return {\n entries: result.items.map((item) => itemToNode(sourceId, item)),\n nextCursor: result.nextCursor ?? null\n };\n };\n }\n\n return service;\n}\n\nfunction itemToNode(sourceId: string, item: ReferenceListItem): ReferenceNode {\n if (item.type === \"group\") {\n return {\n ref: { sourceId, nodeId: GROUP_PREFIX + base64UrlEncode(item.id) },\n kind: \"folder\",\n displayName: item.displayName,\n hasChildren: true,\n ...(item.parentLabel?.trim()\n ? { contextLabel: item.parentLabel.trim() }\n : {}),\n ...(item.referenceCount == null\n ? {}\n : { childCount: item.referenceCount }),\n ...(item.iconUrl ? { iconUrl: item.iconUrl } : {})\n };\n }\n const reference = item.reference;\n return {\n ref: { sourceId, nodeId: FILE_PREFIX + base64UrlEncode(reference.path) },\n kind: \"file\",\n displayName: reference.displayName?.trim() || basename(reference.path),\n ...(reference.parentLabel?.trim()\n ? { contextLabel: reference.parentLabel.trim() }\n : {}),\n ...(reference.sizeBytes == null ? {} : { sizeBytes: reference.sizeBytes }),\n ...(reference.mtimeMs == null ? {} : { mtimeMs: reference.mtimeMs }),\n ...(reference.mimeType ? { mimeType: reference.mimeType } : {})\n };\n}\n\nfunction decodeSegment(prefix: string, nodeId: string): string {\n if (!nodeId.startsWith(prefix)) {\n throw new Error(\n `reference-list nodeId missing ${prefix} prefix: ${nodeId}`\n );\n }\n return base64UrlDecode(nodeId.slice(prefix.length));\n}\n\nfunction basename(path: string): string {\n const trimmed = path.replace(/\\/+$/, \"\");\n const index = trimmed.lastIndexOf(\"/\");\n return index >= 0 ? trimmed.slice(index + 1) : trimmed;\n}\n\n/** UTF-8 安全的 base64url(浏览器/Node 通用)。 */\nexport function base64UrlEncode(value: string): string {\n const bytes = new TextEncoder().encode(value);\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport function base64UrlDecode(value: string): string {\n const normalized = value.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const binary = atob(normalized);\n const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));\n return new TextDecoder().decode(bytes);\n}\n","/**\n * 全局统一「文件类型筛选分类」—— 筛选与搜索在底层是同一能力:search() 接收的\n * filters 即此处的分类 id 数组,各源 daemon 按扩展名/媒体类型真正过滤。\n *\n * 这是分类口径的「单一 TS 来源」。其余各处均为逐字镜像,改一处务必同步全部:\n * - Go 宿主侧:services/tuttid/data/workspace/reference_filter_categories.go\n * - group-chat 应用:apps/server/src/domains/reference-filter-categories.ts\n * - vibe-design 应用:server/src/routes/references-routes.ts\n * - ai-media-canvas 应用:apps/server/src/http/references.ts\n * - app_factory 技能契约:app_factory_reference/references/manifest-contract.md\n *\n * 分类口径:image(图片)/ video(视频)/ document(文档,含表格)/ webpage(网页)/ other(其他)。\n * 音频、代码、压缩包等未单列扩展名,统一归入 \"other\" 兜底。\n */\n\nexport type ReferenceFilterCategoryId =\n | \"image\"\n | \"video\"\n | \"document\"\n | \"webpage\"\n | \"other\";\n\nexport interface ReferenceFilterCategory {\n id: ReferenceFilterCategoryId;\n /** 展示名走 i18n:referencePicker 命名空间下的 copy key,由 UI 层解析。 */\n labelKey: string;\n /** 该分类归属的文件扩展名(小写,不含点)。\"other\" 为空,表示「未收录/无扩展名」兜底。 */\n extensions: readonly string[];\n}\n\n/**\n * 分类定义(展示顺序即数组顺序)。每个分类按文件扩展名归类;\n * 文件夹不参与类型过滤(始终保留以便继续下钻)。\n */\nexport const REFERENCE_FILTER_CATEGORIES: readonly ReferenceFilterCategory[] = [\n {\n id: \"image\",\n labelKey: \"referencePicker.fileTypeImage\",\n extensions: [\n \"png\",\n \"jpg\",\n \"jpeg\",\n \"gif\",\n \"webp\",\n \"svg\",\n \"bmp\",\n \"ico\",\n \"heic\"\n ]\n },\n {\n id: \"video\",\n labelKey: \"referencePicker.fileTypeVideo\",\n extensions: [\"mp4\", \"mov\", \"avi\", \"mkv\", \"webm\"]\n },\n {\n id: \"document\",\n labelKey: \"referencePicker.fileTypeDocument\",\n extensions: [\n \"pdf\",\n \"doc\",\n \"docx\",\n \"txt\",\n \"md\",\n \"markdown\",\n \"rtf\",\n \"odt\",\n \"pages\",\n \"key\",\n \"ppt\",\n \"pptx\",\n \"xls\",\n \"xlsx\",\n \"csv\",\n \"tsv\",\n \"numbers\"\n ]\n },\n {\n id: \"webpage\",\n labelKey: \"referencePicker.fileTypeWebpage\",\n extensions: [\"html\", \"htm\", \"mhtml\", \"url\", \"webloc\"]\n },\n { id: \"other\", labelKey: \"referencePicker.fileTypeOther\", extensions: [] }\n];\n\nconst CATEGORY_BY_EXTENSION: ReadonlyMap<string, ReferenceFilterCategoryId> =\n new Map(\n REFERENCE_FILTER_CATEGORIES.flatMap((category) =>\n category.extensions.map((ext) => [ext, category.id] as const)\n )\n );\n\n/** 从文件名末段推断分类;无扩展名或未收录的扩展名归入「other」。 */\nexport function categoryOfFileName(name: string): ReferenceFilterCategoryId {\n const dotIndex = name.lastIndexOf(\".\");\n if (dotIndex <= 0 || dotIndex === name.length - 1) {\n return \"other\";\n }\n const ext = name.slice(dotIndex + 1).toLowerCase();\n return CATEGORY_BY_EXTENSION.get(ext) ?? \"other\";\n}\n\n/**\n * 分类筛选判定:空 ids = 不筛选(全部通过);文件夹始终通过(保留导航能力);\n * 仅对文件按其分类匹配。供需要在客户端兜底过滤的调用方复用。\n */\nexport function matchesFilterCategories(\n name: string,\n isFolder: boolean,\n filterIds: readonly string[]\n): boolean {\n if (filterIds.length === 0 || isFolder) {\n return true;\n }\n return filterIds.includes(categoryOfFileName(name));\n}\n","import type { WorkspaceFileReference } from \"../contracts/index.ts\";\n\nexport * from \"./referenceSourceUtils.ts\";\nexport * from \"./referenceSourceAggregator.ts\";\nexport * from \"./referenceListSource.ts\";\nexport * from \"./referenceFilterCategories.ts\";\n\nexport function uniqueWorkspaceFileReferences(\n refs: readonly WorkspaceFileReference[]\n): WorkspaceFileReference[] {\n const unique = new Map<string, WorkspaceFileReference>();\n for (const ref of refs) {\n const normalizedPath = ref.path.trim();\n if (!normalizedPath || unique.has(normalizedPath)) {\n continue;\n }\n unique.set(normalizedPath, {\n displayName: ref.displayName?.trim() || undefined,\n kind: ref.kind === \"folder\" ? \"folder\" : \"file\",\n path: normalizedPath,\n ...(ref.mtimeMs === undefined ? {} : { mtimeMs: ref.mtimeMs }),\n ...(ref.sizeBytes === undefined ? {} : { sizeBytes: ref.sizeBytes })\n });\n }\n return [...unique.values()];\n}\n"],"mappings":"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","names":["input"]}