返回 DeepSeek-Reasonix
attachmentBindings.ts
根目录 / desktop / frontend / src / lib / attachmentBindings.ts
1 import type { AppBindings } from "./bridge";
2 import type { ComposerTarget } from "../generated/desktopContract.generated";
3
4 export interface AttachmentBindings {
5 StageImageForTab(tabID: string, operationID: string, displayName: string, mime: string, dataUrl: string): Promise<{ draftId: string; displayName: string; mime: string; width: number; height: number; bytes: number }>;
6 ReadDraftImageForTab(tabID: string, draftID: string): Promise<string>;
7 ReleaseDraftImageForTab(tabID: string, draftID: string): Promise<void>;
8 SavePastedImageForTab(tabID: string, dataURL: string): Promise<string>;
9 SavePastedFileForTab(tabID: string, name: string, dataURL: string): Promise<string>;
10 SaveClipboardImageForTab(tabID: string): Promise<string>;
11 AttachmentDataURLForTab(tabID: string, path: string): Promise<string>;
12 AttachDroppedForTab(tabID: string, path: string): Promise<{ kind: string; path: string; isDir?: boolean; displayPath?: string; previewUrl?: string }>;
13 ReadSessionAttachmentForTab(tabID: string, digest: string, offset: number): Promise<{ data: string; nextOffset: number; done: boolean }>;
14 StartTurnForTabWithDrafts(tabID: string, input: string, submissionID: string, draftIDs: string[]): Promise<{ turnId: string; status: string; submissionId: string }>;
15 CaptureAttachmentTarget?(target: ComposerTarget): Promise<{ token: string; capabilities: string[] }>;
16 ReleaseAttachmentTarget?(token: string): Promise<void>;
17 SavePastedFileForTarget?(token: string, name: string, dataURL: string): Promise<string>;
18 SaveClipboardImageForTarget?(token: string): Promise<string>;
19 AttachmentDataURLForTarget?(token: string, path: string): Promise<string>;
20 AttachDroppedForTarget?(token: string, path: string): Promise<{ kind: string; path: string; isDir?: boolean; displayPath?: string; previewUrl?: string }>;
21 StageImageForTarget?(token: string, operationID: string, displayName: string, mime: string, dataUrl: string): Promise<{ draftId: string; path?: string; displayName: string; mime: string; width: number; height: number; bytes: number }>;
22 ReadDraftImageForTarget?(token: string, draftID: string): Promise<string>;
23 RebindDraftImageForTarget?(token: string, draftID: string): Promise<{ draftId: string; displayName: string; mime: string; width: number; height: number; bytes: number }>;
24 ReleaseDraftImageForTarget?(token: string, draftID: string): Promise<void>;
25 StartTurnForAttachmentTarget?(token: string, submissionID: string, request: { input: string; display: string; original?: string; goal?: string; toolApprovalMode?: string; invocations?: import("./invocationDisplay").InvocationRequest[]; attachments: import("./invocationDisplay").SubmissionAttachment[] }): Promise<{ turnId: string; status: string; submissionId: string }>;
26 EnqueueForAttachmentTarget?(token: string, submissionID: string, input: string, display: string, invocations: import("./invocationDisplay").InvocationRequest[], attachments: import("./invocationDisplay").SubmissionAttachment[]): Promise<{ itemId: string; paused?: boolean; error?: string }>;
27 }
28
29 const mockImages = new Map<string, string>();
30 const mockPreviewImageDataURL =
31 "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='120' viewBox='0 0 160 120'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23f97316'/%3E%3Cstop offset='1' stop-color='%232563eb'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='160' height='120' rx='14' fill='url(%23g)'/%3E%3Ccircle cx='44' cy='38' r='16' fill='%23fff7ed' opacity='.9'/%3E%3Cpath d='M18 96 62 58l24 22 18-16 38 32z' fill='%23ffffff' opacity='.9'/%3E%3C/svg%3E";
32
33 export function makeMockAttachmentBindings(): AttachmentBindings & Partial<AppBindings> & ThisType<AppBindings> {
34 return {
35 async StageImageForTab(_tabID: string, _operation: string, displayName: string, mime: string, dataURL: string) {
36 const draftId = crypto.randomUUID().replaceAll("-", "");
37 mockImages.set(draftId, dataURL);
38 return { draftId, displayName, mime, width: 1, height: 1, bytes: dataURL.length };
39 },
40 async ReadDraftImageForTab(_tabID: string, id: string) {
41 const image = mockImages.get(id);
42 if (!image) throw new Error("draft missing");
43 return image;
44 },
45 async CaptureAttachmentTarget(target: ComposerTarget) {
46 return { token: target.tabId || target.draftId || "mock-attachment-target", capabilities: ["attachments-v2"] };
47 },
48 async ReleaseAttachmentTarget(_token: string) {},
49 async StageImageForTarget(_token: string, _operation: string, displayName: string, mime: string, dataURL: string) {
50 const draftId = crypto.randomUUID().replaceAll("-", "");
51 mockImages.set(draftId, dataURL);
52 return { draftId, displayName, mime, width: 1, height: 1, bytes: dataURL.length };
53 },
54 async ReadDraftImageForTarget(_token: string, id: string) { const image = mockImages.get(id); if (!image) throw new Error("draft missing"); return image; },
55 async ReleaseDraftImageForTarget(_token: string, id: string) { mockImages.delete(id); },
56 async ReleaseDraftImageForTab(_tabID: string, id: string) { mockImages.delete(id); },
57 async SavePastedImage(dataURL: string) {
58 const path = `.reasonix/attachments/mock-${mockImages.size + 1}.png`;
59 mockImages.set(path, dataURL);
60 return path;
61 },
62 async SavePastedImageForComposerTarget(_target: ComposerTarget, dataURL: string) { return this.SavePastedImage(dataURL); },
63 async SavePastedImageForTab(_tabID: string, dataURL: string) { return this.SavePastedImage(dataURL); },
64 async SavePastedFile(name: string, dataURL: string) {
65 const path = `.reasonix/attachments/mock-${name}`;
66 mockImages.set(path, dataURL);
67 return path;
68 },
69 async SavePastedFileForComposerTarget(_target: ComposerTarget, name: string, dataURL: string) { return this.SavePastedFile(name, dataURL); },
70 async SavePastedFileForTarget(token: string, name: string, dataURL: string) { return this.SavePastedFileForTab(token,name,dataURL); },
71 async SavePastedFileForTab(_tabID: string, name: string, dataURL: string) { return this.SavePastedFile(name, dataURL); },
72 async SaveClipboardImage() {
73 const path = `.reasonix/attachments/mock-clipboard-${mockImages.size + 1}.png`;
74 mockImages.set(path, mockPreviewImageDataURL);
75 return path;
76 },
77 async SaveClipboardImageForComposerTarget(_target: ComposerTarget) { return this.SaveClipboardImage(); },
78 async SaveClipboardImageForTarget(token: string) { return this.SaveClipboardImageForTab(token); },
79 async SaveClipboardImageForTab(_tabID: string) { return this.SaveClipboardImage(); },
80 async AttachmentDataURL(path: string) { return mockImages.get(path) ?? mockPreviewImageDataURL; },
81 async AttachmentDataURLForComposerTarget(_target: ComposerTarget, path: string) { return this.AttachmentDataURL(path); },
82 async AttachmentDataURLForTarget(token: string, path: string) { return this.AttachmentDataURLForTab(token,path); },
83 async AttachmentDataURLForTab(_tabID: string, path: string) { return this.AttachmentDataURL(path); },
84 async AttachDropped(path: string) {
85 const name = path.split(/[/\\]/).filter(Boolean).pop() ?? path;
86 if (!/\.\w{1,6}$/i.test(name)) {
87 const tokenName = name.replace(/[^\w.-]+/g, "-") || "folder";
88 return { kind: "workspace", path: `__reasonix_external_folder/mock/${tokenName}`, isDir: true, displayPath: path };
89 }
90 const attachmentPath = `.reasonix/attachments/mock-${name}`;
91 mockImages.set(attachmentPath, mockPreviewImageDataURL);
92 return { kind: "attachment", path: attachmentPath };
93 },
94 async AttachDroppedForComposerTarget(_target: ComposerTarget, path: string) { return this.AttachDropped(path); },
95 async AttachDroppedForTarget(token: string, path: string) { return this.AttachDroppedForTab(token,path); },
96 async AttachDroppedForTab(_tabID: string, path: string) { return this.AttachDropped(path); },
97 async ReadSessionAttachmentForTab(_tabID: string, digest: string, offset: number) {
98 const image = mockImages.get(digest) ?? "data:image/png;base64,";
99 const data = image.split(",", 2)[1] ?? "";
100 return { data: offset === 0 ? data : "", nextOffset: data.length, done: true };
101 },
102 async StartTurnForTabWithDrafts(tabID: string, input: string, submissionID: string) {
103 if (this.StartTurnForTab) {
104 const receipt = await this.StartTurnForTab(tabID, input, submissionID);
105 return { turnId: receipt.turnId, status: receipt.status, submissionId: receipt.submissionId || submissionID };
106 }
107 await this.SubmitToTabWithID(tabID, input, submissionID);
108 return { turnId: submissionID, status: "queued", submissionId: submissionID };
109 },
110 async StartTurnForAttachmentTarget(token: string, submissionID: string, request: {input:string;display:string}) {
111 await this.SubmitDisplayToTabWithID(token,request.display,request.input,submissionID);
112 return {turnId:submissionID,status:"queued",submissionId:submissionID};
113 },
114 async EnqueueForAttachmentTarget(token: string, id: string, input: string, display: string) { return this.EnqueueInboxFollowup(token,display,input,id); },
115 };
116 }
117
118 export function callMockAttachment(
119 owner: AppBindings,
120 name: keyof AttachmentBindings,
121 args: unknown[],
122 ): unknown {
123 const method = makeMockAttachmentBindings()[name] as (...values: unknown[]) => unknown;
124 return method.apply(owner, args);
125 }
126
126 lines TYPESCRIPT