agentAttachedPayload.ts 3.48 KB
/**
 * Agent 侧栏(工作台 / 主布局)共用:构建 sendMessage 第 4 参 attachedFiles,与 turn fileIds 对齐。
 */

export type AttachedFilePayload = {
  id: number;
  name?: string;
  type?: string;
  filePath?: string;
};

/** 与 ref.fileIds 对齐:files[i] 缺 id 时用 fileIds[i] */
export function parseRefFileIdsList(ref: any): number[] {
  const out: number[] = [];
  if (Array.isArray(ref.fileIds)) {
    ref.fileIds.forEach((x: any) => {
      const n = typeof x === "string" ? parseInt(x, 10) : Number(x);
      if (Number.isFinite(n)) out.push(n);
    });
  } else if (typeof ref.fileIds === "string" && ref.fileIds.trim()) {
    ref.fileIds.split(",").forEach((s: string) => {
      const n = parseInt(s.trim(), 10);
      if (Number.isFinite(n)) out.push(n);
    });
  }
  return out;
}

export function resolveFileEntryId(
  f: any,
  index: number,
  refIdList: number[],
): number | undefined {
  const raw =
    f?.id ?? f?.fileId ?? f?.itemId ?? refIdList[index] ?? refIdList[0];
  if (raw === undefined || raw === null) return undefined;
  const id = typeof raw === "string" ? parseInt(raw, 10) : Number(raw);
  return Number.isFinite(id) ? id : undefined;
}

export function buildAttachedFilesPayload(
  knowledgeReferences: any[],
  uploadedMeta: { id: number; name: string }[],
): AttachedFilePayload[] {
  const byId = new Map<number, AttachedFilePayload>();

  for (const ref of knowledgeReferences) {
    if (ref.type !== "files" || !ref.files?.length) continue;
    const refIdList = parseRefFileIdsList(ref);
    ref.files.forEach((f: any, index: number) => {
      const id = resolveFileEntryId(f, index, refIdList);
      if (id === undefined) return;
      const name = f.name ?? f.fileName ?? f.itemName ?? `file-${id}`;
      const path =
        f.filePath ?? f.path ?? f.workspacePath ?? f.codexPath ?? undefined;
      byId.set(id, {
        id,
        name,
        type: f.type ?? f.fileType,
        filePath:
          typeof path === "string" && path.length > 0 ? path : undefined,
      });
    });
  }

  for (const u of uploadedMeta) {
    if (!byId.has(u.id)) {
      byId.set(u.id, {
        id: u.id,
        name: u.name,
        type: undefined,
        filePath: undefined,
      });
    }
  }

  return Array.from(byId.values());
}

function findNameForFileIdInRefs(
  knowledgeReferences: any[],
  fileId: number,
): string | undefined {
  for (const ref of knowledgeReferences) {
    if (ref.type !== "files" || !ref.files?.length) continue;
    const refIdList = parseRefFileIdsList(ref);
    for (let i = 0; i < ref.files.length; i++) {
      const id = resolveFileEntryId(ref.files[i], i, refIdList);
      if (id === fileId) {
        return (
          ref.files[i].name ??
          ref.files[i].fileName ??
          ref.files[i].itemName
        );
      }
    }
  }
  return undefined;
}

export function ensureAttachedPayloadCoversTurnFileIds(
  built: AttachedFilePayload[],
  turnFileIds: number[],
  knowledgeReferences: any[],
  uploadedMeta: { id: number; name: string }[],
): AttachedFilePayload[] {
  if (turnFileIds.length === 0) return built;
  const map = new Map<number, AttachedFilePayload>();
  built.forEach((b) => map.set(b.id, b));
  for (const tid of turnFileIds) {
    if (!map.has(tid)) {
      const name =
        uploadedMeta.find((u) => u.id === tid)?.name ||
        findNameForFileIdInRefs(knowledgeReferences, tid) ||
        `file-${tid}`;
      map.set(tid, { id: tid, name });
    }
  }
  return turnFileIds.map((id) => map.get(id)!).filter(Boolean);
}