返回 DeepSeek-Reasonix
attachmentSubmit.ts
根目录 / desktop / frontend / src / lib / attachmentSubmit.ts
1 import type { AppBindings } from "./bridge";
2 import type { StructuredInvocationSubmit } from "./invocationDisplay";
3 import type { ComposerTarget } from "../generated/desktopContract.generated";
4
5 const pendingImageSubmissions = new Map<string, { fingerprint: string; id: string }>();
6
7 export function imageSubmissionIdentity(draft: string, fingerprint: string): string {
8 const previous = pendingImageSubmissions.get(draft);
9 if (previous?.fingerprint === fingerprint) return previous.id;
10 const id = `image-${crypto.randomUUID()}`;
11 pendingImageSubmissions.set(draft, { fingerprint, id });
12 return id;
13 }
14
15 export function settleImageSubmission(draft: string, id?: string): void {
16 if (pendingImageSubmissions.get(draft)?.id === id) pendingImageSubmissions.delete(draft);
17 }
18
19 export async function prepareImageSubmission(
20 app: AppBindings,
21 target: ComposerTarget,
22 draftKey: string,
23 fingerprint: string,
24 attachments: Array<{ draftId?: string; clientAttachmentId?: string }>,
25 structured: StructuredInvocationSubmit | undefined,
26 display: string,
27 input: string,
28 ) {
29 const token = await captureImageTarget(app, target);
30 const submissionId = imageSubmissionIdentity(draftKey, fingerprint);
31 return {
32 token,
33 submissionId,
34 structured: {
35 display: structured?.display ?? display,
36 input: structured?.input ?? input,
37 invocations: structured?.invocations ?? [],
38 attachmentTarget: token,
39 attachmentSubmissionId: submissionId,
40 attachments: attachments.map((item, index) => ({
41 clientAttachmentId: item.clientAttachmentId || `image-${index + 1}`,
42 draftId: item.draftId,
43 })),
44 } satisfies StructuredInvocationSubmit,
45 };
46 }
47
48 export function submitAttachmentTurn(app: AppBindings, submissionId: string, structured: StructuredInvocationSubmit, original: string, initialGoal?: { goal: string; toolApprovalMode?: string }) {
49 if (!app.StartTurnForAttachmentTarget || !structured.attachmentTarget) throw new Error("unsupported: attachments-v2");
50 return app.StartTurnForAttachmentTarget(structured.attachmentTarget, submissionId, {
51 input: structured.input, display: structured.display, original,
52 goal: initialGoal?.goal, toolApprovalMode: initialGoal?.toolApprovalMode,
53 invocations: structured.invocations, attachments: structured.attachments ?? [],
54 });
55 }
56
57 export function readFileAsDataURL(file: File): Promise<string> {
58 return new Promise((resolve, reject) => {
59 const reader = new FileReader();
60 reader.onload = () => resolve(String(reader.result));
61 reader.onerror = () => reject(reader.error);
62 reader.readAsDataURL(file);
63 });
64 }
65
66 export async function captureImageTarget(app: AppBindings, target: ComposerTarget): Promise<string> {
67 return captureAttachmentTarget(app, target, target.kind === "draft"
68 ? ["StageImageForTarget", "AttachmentDataURLForTarget"]
69 : ["StageImageForTarget", "ReadDraftImageForTarget"]);
70 }
71
72 export async function captureAttachmentTarget(
73 app: AppBindings,
74 target: ComposerTarget,
75 required: ReadonlyArray<keyof AppBindings>,
76 ): Promise<string> {
77 if (!app.CaptureAttachmentTarget || required.some((name) => typeof app[name] !== "function")) {
78 throw new Error("unsupported: attachments-v2");
79 }
80 const captured = await app.CaptureAttachmentTarget(target);
81 if (!captured.capabilities.includes("attachments-v2")) throw new Error("unsupported: attachments-v2");
82 return captured.token;
83 }
84
85 export async function stageImageFile(app: AppBindings, target: string, draftKey: string, file: File) {
86 const dataURL = await readFileAsDataURL(file);
87 const staged = await app.StageImageForTarget!(target, `${draftKey}:${file.name}:${file.lastModified}`, file.name, file.type, dataURL);
88 const path = staged.draftId ? `draft:${staged.draftId}` : staged.path;
89 if (!path) throw new Error("attachment staging returned no source");
90 const previewUrl = staged.draftId
91 ? await app.ReadDraftImageForTarget!(target, staged.draftId)
92 : await app.AttachmentDataURLForTarget!(target, path);
93 return { path, previewUrl, displayName: file.name, draftId: staged.draftId || undefined, file };
94 }
95
95 lines TYPESCRIPT