From 69ae525cb7fa7e02f552cdfd2829b6b37b12e6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 28 May 2026 22:36:31 -0400 Subject: [PATCH] Open files and replace the file row menu with real actions --- AgentCompany-New/src/main/app/ipc-handlers.ts | 9 +++++++ .../src/main/services/file-service.ts | 14 ++++++++-- AgentCompany-New/src/preload/index.ts | 1 + .../src/renderer/src/pages/FilesPage.tsx | 17 +++++++++--- .../renderer/src/pages/files/FilesGrid.tsx | 14 +++++++++- .../renderer/src/pages/files/FilesTable.tsx | 26 ++++++++++++++++--- AgentCompany-New/src/renderer/src/styles.css | 2 ++ AgentCompany-New/src/shared/commands.ts | 5 ++++ AgentCompany-New/src/shared/desktop-api.ts | 2 ++ AgentCompany-New/src/shared/ipc.ts | 1 + 10 files changed, 81 insertions(+), 10 deletions(-) diff --git a/AgentCompany-New/src/main/app/ipc-handlers.ts b/AgentCompany-New/src/main/app/ipc-handlers.ts index ffaff708..98401bcc 100644 --- a/AgentCompany-New/src/main/app/ipc-handlers.ts +++ b/AgentCompany-New/src/main/app/ipc-handlers.ts @@ -196,6 +196,15 @@ export function registerIpcHandlers(deps: IpcDeps): void { } }); + ipcMain.handle(IPC_CHANNELS.openFile, async (_event, input) => { + try { + await files.openFile(input); + return ok(null); + } catch (error) { + return fail("file_open_failed", error instanceof Error ? error.message : String(error)); + } + }); + ipcMain.handle(IPC_CHANNELS.pickDirectory, async () => { try { const result = await dialog.showOpenDialog(deps.mainWindow, { properties: ["openDirectory", "createDirectory"] }); diff --git a/AgentCompany-New/src/main/services/file-service.ts b/AgentCompany-New/src/main/services/file-service.ts index 138a764f..c40ae10b 100644 --- a/AgentCompany-New/src/main/services/file-service.ts +++ b/AgentCompany-New/src/main/services/file-service.ts @@ -2,8 +2,8 @@ import { createHash } from "node:crypto"; import { copyFileSync, mkdirSync, readFileSync, statSync } from "node:fs"; import { basename, extname, join } from "node:path"; import type { DatabaseSync } from "node:sqlite"; -import { dialog } from "electron"; -import type { ImportFileInput } from "../../shared/commands"; +import { dialog, shell } from "electron"; +import type { ImportFileInput, OpenFileInput } from "../../shared/commands"; import { FILE_SCAN_STATE, type FileScanState } from "../../shared/domain"; import { createId } from "../../shared/ids"; import { AuditService } from "./audit-service"; @@ -117,6 +117,16 @@ export class FileService { return id; } + async openFile(input: OpenFileInput): Promise { + const row = this.db.prepare( + "select storage_path as storagePath, scan_state as scanState from file_objects where id = ? and org_id = ?", + ).get(input.fileId, input.orgId) as { storagePath: string; scanState: string } | undefined; + if (!row) throw new Error("File was not found."); + if (row.scanState !== FILE_SCAN_STATE.APPROVED) throw new Error("Blocked files cannot be opened."); + const failure = await shell.openPath(row.storagePath); + if (failure) throw new Error(failure); + } + setPinned(orgId: string, fileId: string, pinned: boolean): void { const result = this.db.prepare( "update file_objects set pinned = ?, updated_at = ? where id = ? and org_id = ?", diff --git a/AgentCompany-New/src/preload/index.ts b/AgentCompany-New/src/preload/index.ts index 3c4939c7..60df5c72 100644 --- a/AgentCompany-New/src/preload/index.ts +++ b/AgentCompany-New/src/preload/index.ts @@ -43,6 +43,7 @@ const api: DesktopApi = { createScheduledPost: (input) => invoke(IPC_CHANNELS.createScheduledPost, input), setScheduledPostState: (input) => invoke(IPC_CHANNELS.setScheduledPostState, input), importFile: (input) => invoke(IPC_CHANNELS.importFile, input), + openFile: (input) => invoke(IPC_CHANNELS.openFile, input), pickDirectory: () => invoke(IPC_CHANNELS.pickDirectory), setFilePinned: (input) => invoke(IPC_CHANNELS.setFilePinned, input), createMigrationReport: (input) => invoke(IPC_CHANNELS.createMigrationReport, input), diff --git a/AgentCompany-New/src/renderer/src/pages/FilesPage.tsx b/AgentCompany-New/src/renderer/src/pages/FilesPage.tsx index 576a4bf2..428487e5 100644 --- a/AgentCompany-New/src/renderer/src/pages/FilesPage.tsx +++ b/AgentCompany-New/src/renderer/src/pages/FilesPage.tsx @@ -1,6 +1,7 @@ import { useMemo, useState } from "react"; import { Button, Icon, Segmented } from "../shared/ui"; import { TopbarActions } from "../shared/TopbarActions"; +import type { FileObjectRecord } from "../../../shared/domain"; import type { PageProps } from "./types"; import { activeOrgId, formatBytes } from "./types"; import { FilesTree } from "./files/FilesTree"; @@ -40,6 +41,13 @@ export function FilesPage({ snapshot, actions }: PageProps) { if (!result.ok) throw new Error(result.error.message); }); }; + const openFile = (file: FileObjectRecord) => { + void window.agentCompany.openFile({ orgId, fileId: file.id }).then((result) => { + if (!result.ok) throw new Error(result.error.message); + }); + }; + const togglePin = (file: FileObjectRecord) => + void actions.run(() => window.agentCompany.setFilePinned({ orgId, fileId: file.id, pinned: !file.pinned })); return (
@@ -83,7 +91,8 @@ export function FilesPage({ snapshot, actions }: PageProps) { file={file} ownerName={resolveOwnerName(file, employees, agents)} grantCount={fileGrantCount(file.id, fileAccessGrants)} - onUnpin={() => void actions.run(() => window.agentCompany.setFilePinned({ orgId, fileId: file.id, pinned: false }))} + onOpen={() => openFile(file)} + onUnpin={() => togglePin(file)} /> ))}
@@ -101,7 +110,8 @@ export function FilesPage({ snapshot, actions }: PageProps) { grants={fileAccessGrants} employees={employees} agents={agents} - onTogglePin={(file) => void actions.run(() => window.agentCompany.setFilePinned({ orgId, fileId: file.id, pinned: !file.pinned }))} + onOpen={openFile} + onTogglePin={togglePin} /> ) : ( void actions.run(() => window.agentCompany.setFilePinned({ orgId, fileId: file.id, pinned: !file.pinned }))} + onOpen={openFile} + onTogglePin={togglePin} /> )} diff --git a/AgentCompany-New/src/renderer/src/pages/files/FilesGrid.tsx b/AgentCompany-New/src/renderer/src/pages/files/FilesGrid.tsx index 27de60cf..200f93e1 100644 --- a/AgentCompany-New/src/renderer/src/pages/files/FilesGrid.tsx +++ b/AgentCompany-New/src/renderer/src/pages/files/FilesGrid.tsx @@ -14,12 +14,14 @@ export function FilesGrid({ grants, employees, agents, + onOpen, onTogglePin, }: { files: FileObjectRecord[]; grants: FileAccessGrantRecord[]; employees: EmployeeRecord[]; agents: AgentRecord[]; + onOpen: (file: FileObjectRecord) => void; onTogglePin: (file: FileObjectRecord) => void; }) { return ( @@ -30,6 +32,7 @@ export function FilesGrid({ file={file} ownerName={resolveOwnerName(file, employees, agents)} grantCount={fileGrantCount(file.id, grants)} + onOpen={() => onOpen(file)} onUnpin={() => onTogglePin(file)} /> ))} @@ -41,15 +44,23 @@ export function PinnedCard({ file, ownerName, grantCount, + onOpen, onUnpin, }: { file: FileObjectRecord; ownerName: string; grantCount: number; + onOpen: () => void; onUnpin: () => void; }) { + const canOpen = file.scanState === "approved"; return (
{ if (event.key === "Enter") onOpen(); } : undefined} style={{ border: "1px solid var(--border)", borderRadius: 10, @@ -59,6 +70,7 @@ export function PinnedCard({ flexDirection: "column", gap: 10, minWidth: 0, + cursor: canOpen ? "pointer" : "default", }} >
@@ -66,7 +78,7 @@ export function PinnedCard({