Q140: a message can carry several files, or files alone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-08 22:37:51 -04:00
1 parent 46f7561f77
commit a6ab09d6b5
11 files changed
+129 -35

No files matched your search

+3 -3
View File
@@ -340,9 +340,9 @@ export function registerIpcHandlers(deps: IpcDeps): void {
handle("importFile", async (input) => {
try {
const fileId = await files.importFile(input);
if (fileId) notifyDataChanged();
return ok(fileId);
const imported = await files.importFile(input);
if (imported.fileIds.length > 0) notifyDataChanged();
return ok(imported);
} catch (error) {
return fail("file_import_failed", error instanceof Error ? error.message : String(error));
}
+9 -2
View File
@@ -120,7 +120,7 @@ export class ChatService {
sendMessage(input: SendMessageInput): string {
const body = input.bodyMd.trim();
if (!body) throw new Error("Message body is required.");
if (!body && (input.attachmentFileIds ?? []).length === 0) throw new Error("A message needs text or a file.");
const space = this.requireSpace(input.orgId, input.spaceId);
const sender = this.requireSender(input.orgId, input.senderType, input.senderId);
if (sender) this.requireMembership(space.id, sender);
@@ -160,7 +160,7 @@ export class ChatService {
this.db.prepare(`
insert into chat_threads(id, org_id, space_id, root_message_id, title, state, created_at, updated_at)
values (?, ?, ?, ?, ?, ?, ?, ?)
`).run(threadId, input.orgId, input.spaceId, id, createThreadTitle(body), "open", now, now);
`).run(threadId, input.orgId, input.spaceId, id, createThreadTitle(body || this.fileNames(input.orgId, attachmentFileIds)), "open", now, now);
} else {
this.db.prepare("update chat_threads set updated_at = ? where id = ? and org_id = ?").run(now, input.threadId, input.orgId);
}
@@ -426,6 +426,13 @@ export class ChatService {
return thread;
}
/** The attached files' names, in order, to title a thread whose first message is files alone. */
private fileNames(orgId: string, fileIds: string[]): string {
const names = this.db.prepare("select id, name from file_objects where org_id = ?").all(orgId) as Array<{ id: string; name: string }>;
const byId = new Map(names.map((file) => [file.id, file.name]));
return fileIds.map((fileId) => byId.get(fileId)!).join(", ");
}
private requireAttachments(orgId: string, fileIds: string[], sender: PrincipalRef | null): FileHandoffAttachment[] {
if (new Set(fileIds).size !== fileIds.length) throw new Error("Attachment file ids must be unique.");
return fileIds.map((fileId) => {
+16 -5
View File
@@ -3,7 +3,7 @@ import { copyFile } from "node:fs/promises";
import { basename, join } from "node:path";
import type { DatabaseSync } from "node:sqlite";
import { dialog, shell } from "electron";
import type { ImportFileInput, OpenFileInput } from "../../shared/commands";
import type { ImportedFiles, ImportFileInput, OpenFileInput } from "../../shared/commands";
import { FILE_SCAN_STATE } from "../../shared/domain";
import { createId } from "../../shared/ids";
import { AuditService } from "./audit-service";
@@ -26,13 +26,24 @@ export class FileService {
mkdirSync(this.artifactRoot, { recursive: true });
}
async importFile(input: ImportFileInput): Promise<string | null> {
/** Imports every file the user picks, in order; one that fails is reported and does not undo or hide the others. */
async importFile(input: ImportFileInput): Promise<ImportedFiles> {
const result = await dialog.showOpenDialog({
properties: ["openFile"],
properties: ["openFile", "multiSelections"],
securityScopedBookmarks: true,
});
if (result.canceled || result.filePaths.length === 0) return null;
return this.importPath(input.orgId, result.filePaths[0], input.spaceId ?? null);
const imported: ImportedFiles = { fileIds: [], failures: [] };
if (result.canceled) return imported;
for (const path of result.filePaths) {
try {
imported.fileIds.push(await this.importPath(input.orgId, path, input.spaceId ?? null));
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
console.warn(`[files] import of ${basename(path)} failed: ${reason}`);
imported.failures.push({ name: basename(path), reason });
}
}
return imported;
}
/** A file imported into a direct conversation or restricted channel is readable by that space's members only. */