@tutti-os/workspace-file-reference 0.0.28 → 0.0.29
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/dist/{chunk-C3G4ADEJ.js → chunk-CT42WDOK.js} +2 -2
- package/dist/{chunk-XA5U53VP.js → chunk-LAYTHZN7.js} +8 -3
- package/dist/chunk-LAYTHZN7.js.map +1 -0
- package/dist/contracts/index.d.ts +3 -0
- package/dist/core/index.js +1 -1
- package/dist/index.js +1 -1
- package/dist/react/index.js +2 -2
- package/dist/ui/index.d.ts +3 -2
- package/dist/ui/index.js +63 -16
- package/dist/ui/index.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-XA5U53VP.js.map +0 -1
- /package/dist/{chunk-C3G4ADEJ.js.map → chunk-CT42WDOK.js.map} +0 -0
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
nodeRefKey,
|
|
5
5
|
sortReferenceNodes,
|
|
6
6
|
uniqueWorkspaceFileReferences
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-LAYTHZN7.js";
|
|
8
8
|
|
|
9
9
|
// src/react/internal/reference/WorkspaceFileReferencePickerState.ts
|
|
10
10
|
var workspaceFileReferenceDefaultExpandedDepth = 4;
|
|
@@ -1610,4 +1610,4 @@ export {
|
|
|
1610
1610
|
ROOT_CHILDREN_KEY,
|
|
1611
1611
|
createReferenceSourcePickerController
|
|
1612
1612
|
};
|
|
1613
|
-
//# sourceMappingURL=chunk-
|
|
1613
|
+
//# sourceMappingURL=chunk-CT42WDOK.js.map
|
|
@@ -39,7 +39,8 @@ function selectedReferenceToWorkspaceFileReference(selected) {
|
|
|
39
39
|
return {
|
|
40
40
|
path: selected.path,
|
|
41
41
|
kind: selected.kind,
|
|
42
|
-
...selected.displayName ? { displayName: selected.displayName } : {}
|
|
42
|
+
...selected.displayName ? { displayName: selected.displayName } : {},
|
|
43
|
+
...selected.sourceId ? { sourceId: selected.sourceId } : {}
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
46
|
|
|
@@ -109,7 +110,11 @@ function createReferenceSourceAggregator(registry) {
|
|
|
109
110
|
return await source.readPreview?.(scope, node) ?? null;
|
|
110
111
|
},
|
|
111
112
|
resolveSelection(node) {
|
|
112
|
-
|
|
113
|
+
const selected = requireSource(node.ref.sourceId).resolveSelection(node);
|
|
114
|
+
return {
|
|
115
|
+
...selected,
|
|
116
|
+
sourceId: selected.sourceId ?? node.ref.sourceId
|
|
117
|
+
};
|
|
113
118
|
},
|
|
114
119
|
async locateTarget(scope, sourceId, params) {
|
|
115
120
|
if (!sourceMap.has(sourceId)) {
|
|
@@ -402,4 +407,4 @@ export {
|
|
|
402
407
|
matchesFilterCategories,
|
|
403
408
|
uniqueWorkspaceFileReferences
|
|
404
409
|
};
|
|
405
|
-
//# sourceMappingURL=chunk-
|
|
410
|
+
//# sourceMappingURL=chunk-LAYTHZN7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 ...(selected.sourceId ? { sourceId: selected.sourceId } : {})\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 const selected = requireSource(node.ref.sourceId).resolveSelection(node);\n return {\n ...selected,\n sourceId: selected.sourceId ?? node.ref.sourceId\n };\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"]}
|
|
@@ -115,6 +115,8 @@ interface SelectedReference {
|
|
|
115
115
|
path: string;
|
|
116
116
|
kind: "file" | "folder";
|
|
117
117
|
displayName?: string;
|
|
118
|
+
/** 保留引用来源,供上层区分 workspace 文件与 host 本地文件等同形 path。 */
|
|
119
|
+
sourceId?: string;
|
|
118
120
|
}
|
|
119
121
|
interface ReferenceSourceMetadata {
|
|
120
122
|
id: string;
|
|
@@ -179,6 +181,7 @@ interface WorkspaceFileReference {
|
|
|
179
181
|
mtimeMs?: number | null;
|
|
180
182
|
path: string;
|
|
181
183
|
sizeBytes?: number | null;
|
|
184
|
+
sourceId?: string;
|
|
182
185
|
}
|
|
183
186
|
interface WorkspaceFileReferenceDirectoryListing {
|
|
184
187
|
directoryPath: string;
|
package/dist/core/index.js
CHANGED
package/dist/index.js
CHANGED
package/dist/react/index.js
CHANGED
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
prefetchReferenceTree,
|
|
10
10
|
useWorkspaceFileReferencePickerView,
|
|
11
11
|
workspaceFileReferenceDefaultExpandedDepth
|
|
12
|
-
} from "../chunk-
|
|
13
|
-
import "../chunk-
|
|
12
|
+
} from "../chunk-CT42WDOK.js";
|
|
13
|
+
import "../chunk-LAYTHZN7.js";
|
|
14
14
|
export {
|
|
15
15
|
ROOT_CHILDREN_KEY,
|
|
16
16
|
collectVisibleTreeEntries,
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { WorkspaceFileReferenceCopy, WorkspaceFileReferenceAdapter, WorkspaceFileReference, ReferenceHandle, ReferenceLocateTarget } from '../contracts/index.js';
|
|
2
|
+
import { WorkspaceFileReferenceCopy, WorkspaceFileReferenceAdapter, WorkspaceFileReference, ReferenceHandle, ReferenceLocateTarget, ReferenceNode } from '../contracts/index.js';
|
|
3
3
|
import { JSX } from 'react';
|
|
4
4
|
import { R as ReferenceSourceAggregator } from '../referenceSourceAggregator-B8ATnJOZ.js';
|
|
5
5
|
|
|
@@ -45,6 +45,7 @@ interface ReferenceSourcePickerProps {
|
|
|
45
45
|
copy: WorkspaceFileReferenceCopy;
|
|
46
46
|
/** 可选:打开时直达某事项/应用分组(展开并聚焦)。 */
|
|
47
47
|
initialTarget?: ReferenceLocateTarget | null;
|
|
48
|
+
isNodeSelectable?: (node: ReferenceNode) => boolean;
|
|
48
49
|
onClose: () => void;
|
|
49
50
|
onConfirm: (refs: WorkspaceFileReference[]) => void;
|
|
50
51
|
/**
|
|
@@ -55,6 +56,6 @@ interface ReferenceSourcePickerProps {
|
|
|
55
56
|
open: boolean;
|
|
56
57
|
workspaceId: string;
|
|
57
58
|
}
|
|
58
|
-
declare function ReferenceSourcePicker({ aggregator, copy, initialTarget, onClose, onConfirm, onConfirmBundles, open, workspaceId }: ReferenceSourcePickerProps): JSX.Element | null;
|
|
59
|
+
declare function ReferenceSourcePicker({ aggregator, copy, initialTarget, isNodeSelectable, onClose, onConfirm, onConfirmBundles, open, workspaceId }: ReferenceSourcePickerProps): JSX.Element | null;
|
|
59
60
|
|
|
60
61
|
export { type ReferenceBundleSelection, type ReferenceGroupedSelection, ReferenceSourcePicker, type ReferenceSourcePickerProps, WorkspaceFileReferencePicker, type WorkspaceFileReferencePickerProps };
|
package/dist/ui/index.js
CHANGED
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
createReferenceSourcePickerController,
|
|
4
4
|
normalizeDirectoryPath,
|
|
5
5
|
useWorkspaceFileReferencePickerView
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-CT42WDOK.js";
|
|
7
7
|
import {
|
|
8
8
|
REFERENCE_FILTER_CATEGORIES,
|
|
9
9
|
WORKSPACE_ROOT_GROUP_NODE_ID,
|
|
10
10
|
nodeRefKey,
|
|
11
11
|
selectedReferenceToWorkspaceFileReference
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-LAYTHZN7.js";
|
|
13
13
|
|
|
14
14
|
// src/ui/internal/reference/WorkspaceFileReferencePicker.tsx
|
|
15
15
|
import {
|
|
@@ -757,6 +757,7 @@ function useReferenceSourcePickerView({
|
|
|
757
757
|
initialTarget = null,
|
|
758
758
|
onClose,
|
|
759
759
|
onConfirm,
|
|
760
|
+
isNodeSelectable,
|
|
760
761
|
onConfirmBundles
|
|
761
762
|
}) {
|
|
762
763
|
const readSnapshot = useSnapshot;
|
|
@@ -1027,16 +1028,28 @@ function useReferenceSourcePickerView({
|
|
|
1027
1028
|
[controller, snapshot.activeSourceId, navigateToRoot]
|
|
1028
1029
|
);
|
|
1029
1030
|
const isSelected = useCallback(
|
|
1030
|
-
(node) => snapshot.selection.some(
|
|
1031
|
+
(node) => (isNodeSelectable?.(node) ?? true) && snapshot.selection.some(
|
|
1031
1032
|
(item) => nodeRefKey(item.ref) === nodeRefKey(node.ref)
|
|
1032
1033
|
),
|
|
1033
|
-
[snapshot.selection]
|
|
1034
|
+
[isNodeSelectable, snapshot.selection]
|
|
1035
|
+
);
|
|
1036
|
+
const isSelectable = useCallback(
|
|
1037
|
+
(node) => isNodeSelectable?.(node) ?? true,
|
|
1038
|
+
[isNodeSelectable]
|
|
1039
|
+
);
|
|
1040
|
+
const selectableSelection = useMemo(
|
|
1041
|
+
() => snapshot.selection.filter(isSelectable),
|
|
1042
|
+
[isSelectable, snapshot.selection]
|
|
1034
1043
|
);
|
|
1035
1044
|
const [isConfirming, setIsConfirming] = useState3(false);
|
|
1036
1045
|
const confirm = useCallback(async () => {
|
|
1037
1046
|
if (isConfirming) {
|
|
1038
1047
|
return;
|
|
1039
1048
|
}
|
|
1049
|
+
if (selectableSelection.length === 0) {
|
|
1050
|
+
controller.clearSelection();
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1040
1053
|
setIsConfirming(true);
|
|
1041
1054
|
try {
|
|
1042
1055
|
if (onConfirmBundles) {
|
|
@@ -1061,7 +1074,14 @@ function useReferenceSourcePickerView({
|
|
|
1061
1074
|
} finally {
|
|
1062
1075
|
setIsConfirming(false);
|
|
1063
1076
|
}
|
|
1064
|
-
}, [
|
|
1077
|
+
}, [
|
|
1078
|
+
controller,
|
|
1079
|
+
isConfirming,
|
|
1080
|
+
onClose,
|
|
1081
|
+
onConfirm,
|
|
1082
|
+
onConfirmBundles,
|
|
1083
|
+
selectableSelection.length
|
|
1084
|
+
]);
|
|
1065
1085
|
const [previewState, setPreviewState] = useState3({
|
|
1066
1086
|
status: "empty"
|
|
1067
1087
|
});
|
|
@@ -1205,8 +1225,8 @@ function useReferenceSourcePickerView({
|
|
|
1205
1225
|
// 底部「加载更多」在途(查询态 = 增长重查;浏览态 = cursor append)。
|
|
1206
1226
|
isLoadingMore: isQuery ? activeTabState?.isSearchLoadingMore ?? false : currentChildren?.loading ?? false,
|
|
1207
1227
|
focusedNode,
|
|
1208
|
-
selection:
|
|
1209
|
-
selectionCount:
|
|
1228
|
+
selection: selectableSelection,
|
|
1229
|
+
selectionCount: selectableSelection.length,
|
|
1210
1230
|
setActiveSource,
|
|
1211
1231
|
enterFolder,
|
|
1212
1232
|
selectGroup,
|
|
@@ -1215,9 +1235,23 @@ function useReferenceSourcePickerView({
|
|
|
1215
1235
|
setFocusedNode,
|
|
1216
1236
|
setSearchQuery: (query) => controller.setSearchQuery(query, searchScopeNodeId),
|
|
1217
1237
|
setFilters: (filters) => controller.setSearchFilters(filters, searchScopeNodeId),
|
|
1218
|
-
toggleSelection: (node) =>
|
|
1219
|
-
|
|
1238
|
+
toggleSelection: (node) => {
|
|
1239
|
+
if (isSelectable(node)) {
|
|
1240
|
+
controller.toggleSelection(node);
|
|
1241
|
+
}
|
|
1242
|
+
},
|
|
1243
|
+
toggleSingleSelectionAndExpand: (node) => {
|
|
1244
|
+
if (isSelectable(node)) {
|
|
1245
|
+
controller.toggleSingleSelectionAndExpand(node);
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
controller.clearSelection();
|
|
1249
|
+
if (node.kind === "folder") {
|
|
1250
|
+
controller.toggleNode(node);
|
|
1251
|
+
}
|
|
1252
|
+
},
|
|
1220
1253
|
loadMore: () => isQuery ? controller.loadMoreSearch() : controller.loadMore(currentNode),
|
|
1254
|
+
isSelectable,
|
|
1221
1255
|
isSelected,
|
|
1222
1256
|
confirm,
|
|
1223
1257
|
isConfirming
|
|
@@ -1274,6 +1308,7 @@ function ReferenceSourcePicker({
|
|
|
1274
1308
|
aggregator,
|
|
1275
1309
|
copy,
|
|
1276
1310
|
initialTarget,
|
|
1311
|
+
isNodeSelectable,
|
|
1277
1312
|
onClose,
|
|
1278
1313
|
onConfirm,
|
|
1279
1314
|
onConfirmBundles,
|
|
@@ -1287,6 +1322,7 @@ function ReferenceSourcePicker({
|
|
|
1287
1322
|
open,
|
|
1288
1323
|
workspaceRootGroupLabel: copy.t("referencePicker.workspaceRootGroup"),
|
|
1289
1324
|
initialTarget,
|
|
1325
|
+
isNodeSelectable,
|
|
1290
1326
|
onClose,
|
|
1291
1327
|
onConfirm,
|
|
1292
1328
|
onConfirmBundles
|
|
@@ -1461,6 +1497,7 @@ function ReferenceSourcePicker({
|
|
|
1461
1497
|
node,
|
|
1462
1498
|
selected: view.isSelected(node),
|
|
1463
1499
|
onFocus: view.setFocusedNode,
|
|
1500
|
+
selectable: view.isSelectable(node),
|
|
1464
1501
|
onSingleSelect: view.toggleSingleSelectionAndExpand,
|
|
1465
1502
|
onToggle: view.toggleSelection
|
|
1466
1503
|
},
|
|
@@ -1687,18 +1724,20 @@ function SearchResultRow({
|
|
|
1687
1724
|
node,
|
|
1688
1725
|
focused,
|
|
1689
1726
|
selected,
|
|
1727
|
+
selectable,
|
|
1690
1728
|
onFocus,
|
|
1691
1729
|
onSingleSelect,
|
|
1692
1730
|
onToggle
|
|
1693
1731
|
}) {
|
|
1694
1732
|
const isFolder = node.kind === "folder";
|
|
1695
1733
|
const contextLabel = node.contextLabel ?? node.ref.nodeId;
|
|
1734
|
+
const active = selected || focused && selectable;
|
|
1696
1735
|
return /* @__PURE__ */ jsxs4(
|
|
1697
1736
|
"div",
|
|
1698
1737
|
{
|
|
1699
1738
|
className: cn3(
|
|
1700
1739
|
"grid cursor-pointer grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-[6px] border py-2.5 pr-1 pl-3 transition-colors",
|
|
1701
|
-
|
|
1740
|
+
active ? "border-border bg-transparency-block" : "border-transparent bg-transparent hover:border-border/70 hover:bg-transparency-block"
|
|
1702
1741
|
),
|
|
1703
1742
|
onClick: () => {
|
|
1704
1743
|
onFocus(node);
|
|
@@ -1712,7 +1751,7 @@ function SearchResultRow({
|
|
|
1712
1751
|
/* @__PURE__ */ jsx4(FullTextTooltip, { content: contextLabel, children: /* @__PURE__ */ jsx4("span", { className: "block truncate text-[11px] text-[var(--text-secondary)]", children: contextLabel }) })
|
|
1713
1752
|
] })
|
|
1714
1753
|
] }),
|
|
1715
|
-
/* @__PURE__ */ jsx4(
|
|
1754
|
+
selectable ? /* @__PURE__ */ jsx4(
|
|
1716
1755
|
Button3,
|
|
1717
1756
|
{
|
|
1718
1757
|
"aria-label": node.displayName,
|
|
@@ -1727,7 +1766,7 @@ function SearchResultRow({
|
|
|
1727
1766
|
},
|
|
1728
1767
|
children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(AddLinedIcon2, { className: "text-[var(--text-secondary)]", size: 16 })
|
|
1729
1768
|
}
|
|
1730
|
-
)
|
|
1769
|
+
) : null
|
|
1731
1770
|
]
|
|
1732
1771
|
}
|
|
1733
1772
|
);
|
|
@@ -2129,7 +2168,9 @@ function TreeNodeRow({
|
|
|
2129
2168
|
const childState = view.childrenByKey[key];
|
|
2130
2169
|
const childEntries = view.sortNodes(childState?.entries ?? []);
|
|
2131
2170
|
const selected = view.isSelected(node);
|
|
2171
|
+
const selectable = view.isSelectable(node);
|
|
2132
2172
|
const focused = isFocused(view.focusedNode, node);
|
|
2173
|
+
const active = selected || focused && selectable;
|
|
2133
2174
|
const [shouldRenderChildContent, setShouldRenderChildContent] = useState4(expanded);
|
|
2134
2175
|
useEffect3(() => {
|
|
2135
2176
|
if (expanded) {
|
|
@@ -2175,7 +2216,7 @@ function TreeNodeRow({
|
|
|
2175
2216
|
{
|
|
2176
2217
|
className: cn3(
|
|
2177
2218
|
"flex cursor-pointer items-center gap-2 rounded-[6px] py-1.5 pr-1 transition-colors",
|
|
2178
|
-
|
|
2219
|
+
active ? "bg-transparency-block" : "hover:bg-transparency-block"
|
|
2179
2220
|
),
|
|
2180
2221
|
style: { paddingLeft: `${depth * TREE_INDENT + 8}px` },
|
|
2181
2222
|
onClick: () => {
|
|
@@ -2214,7 +2255,7 @@ function TreeNodeRow({
|
|
|
2214
2255
|
children: node.displayName
|
|
2215
2256
|
}
|
|
2216
2257
|
) }),
|
|
2217
|
-
/* @__PURE__ */ jsx4(
|
|
2258
|
+
selectable ? /* @__PURE__ */ jsx4(
|
|
2218
2259
|
Button3,
|
|
2219
2260
|
{
|
|
2220
2261
|
"aria-label": node.displayName,
|
|
@@ -2228,9 +2269,15 @@ function TreeNodeRow({
|
|
|
2228
2269
|
view.setFocusedNode(node);
|
|
2229
2270
|
view.toggleSelection(node);
|
|
2230
2271
|
},
|
|
2231
|
-
children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(
|
|
2272
|
+
children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(
|
|
2273
|
+
AddLinedIcon2,
|
|
2274
|
+
{
|
|
2275
|
+
className: "text-[var(--text-secondary)]",
|
|
2276
|
+
size: 16
|
|
2277
|
+
}
|
|
2278
|
+
)
|
|
2232
2279
|
}
|
|
2233
|
-
)
|
|
2280
|
+
) : null
|
|
2234
2281
|
]
|
|
2235
2282
|
}
|
|
2236
2283
|
),
|