From d50f0d8d1f5549576f7d531c7c5720129e20cae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sun, 7 Jun 2026 17:49:39 -0400 Subject: [PATCH] Add Documents, Knowledge Base, and Automation pages Three new pages in the sidebar navigation: Documents (CRUD with type filtering, inline editing, status lifecycle), Knowledge Base (card grid with category/importance filters, create/delete), and Automation (rule table with trigger/action/priority/enable toggle, create/edit/delete). Each page connects to the corresponding backend service from the previous commit. --- AgentCompany-New/src/renderer/src/app/App.tsx | 6 + .../src/renderer/src/pages/AutomationPage.tsx | 157 +++++++++++++++++ .../src/renderer/src/pages/DocumentsPage.tsx | 158 ++++++++++++++++++ .../src/renderer/src/pages/KnowledgePage.tsx | 94 +++++++++++ .../src/renderer/src/shared/Shell.tsx | 6 + 5 files changed, 421 insertions(+) create mode 100644 AgentCompany-New/src/renderer/src/pages/AutomationPage.tsx create mode 100644 AgentCompany-New/src/renderer/src/pages/DocumentsPage.tsx create mode 100644 AgentCompany-New/src/renderer/src/pages/KnowledgePage.tsx diff --git a/AgentCompany-New/src/renderer/src/app/App.tsx b/AgentCompany-New/src/renderer/src/app/App.tsx index 8ff284e1..46298264 100644 --- a/AgentCompany-New/src/renderer/src/app/App.tsx +++ b/AgentCompany-New/src/renderer/src/app/App.tsx @@ -9,6 +9,9 @@ import { ChatPage } from "../pages/ChatPage"; import { FilesPage } from "../pages/FilesPage"; import { ApprovalsPage } from "../pages/ApprovalsPage"; import { SocialPage } from "../pages/SocialPage"; +import { DocumentsPage } from "../pages/DocumentsPage"; +import { KnowledgePage } from "../pages/KnowledgePage"; +import { AutomationPage } from "../pages/AutomationPage"; import { SettingsPage } from "../pages/SettingsPage"; export function App() { @@ -37,6 +40,9 @@ export function App() { work: , chat: , files: , + docs: , + knowledge: , + automation: , approvals: , social: , settings: , diff --git a/AgentCompany-New/src/renderer/src/pages/AutomationPage.tsx b/AgentCompany-New/src/renderer/src/pages/AutomationPage.tsx new file mode 100644 index 00000000..3ac83ca9 --- /dev/null +++ b/AgentCompany-New/src/renderer/src/pages/AutomationPage.tsx @@ -0,0 +1,157 @@ +import { useMemo, useState, type FormEvent } from "react"; +import { Button, Field, Icon, IconButton, Pill } from "../shared/ui"; +import { TopbarActions } from "../shared/TopbarActions"; +import { Dialog } from "../shared/Dialog"; +import type { AutomationRuleRecord } from "../../../shared/domain"; +import type { PageProps } from "./types"; +import { activeOrgId, formatRelative } from "./types"; + +const TRIGGER_EVENTS = [ + "task_created", + "task_status_changed", + "agent_hired", + "approval_created", + "message_sent", + "file_uploaded", + "goal_created", + "project_created", +] as const; + +const ACTION_TYPES = [ + "assign_to_department", + "create_task", + "send_notification", + "update_status", + "escalate", +] as const; + +function text(form: FormData, key: string): string { + return String(form.get(key) ?? "").trim(); +} + +export function AutomationPage({ snapshot, actions }: PageProps) { + const orgId = activeOrgId(snapshot); + const rules = useMemo(() => snapshot.automationRules.filter((r) => r.orgId === orgId), [snapshot.automationRules, orgId]); + const [createOpen, setCreateOpen] = useState(false); + const [editingId, setEditingId] = useState(null); + + const editing = editingId ? rules.find((r) => r.id === editingId) ?? null : null; + const enabledCount = rules.filter((r) => r.enabled).length; + const totalFires = rules.reduce((sum, r) => sum + r.fireCount, 0); + + const create = (event: FormEvent) => { + event.preventDefault(); + const form = new FormData(event.currentTarget); + void actions.run(() => window.agentCompany.createAutomationRule({ + orgId, + name: text(form, "name"), + triggerEvent: text(form, "triggerEvent"), + conditions: {}, + actionType: text(form, "actionType"), + actionConfig: {}, + priority: Number(text(form, "priority")) || 100, + enabled: true, + })).then((r) => { if (r) setCreateOpen(false); }); + }; + + const save = (event: FormEvent) => { + event.preventDefault(); + if (!editing) return; + const form = new FormData(event.currentTarget); + void actions.run(() => window.agentCompany.updateAutomationRule({ + orgId, + ruleId: editing.id, + name: text(form, "name"), + triggerEvent: text(form, "triggerEvent"), + conditions: {}, + actionType: text(form, "actionType"), + actionConfig: {}, + priority: Number(text(form, "priority")) || editing.priority, + enabled: form.get("enabled") === "on", + })).then((r) => { if (r) setEditingId(null); }); + }; + + const remove = (ruleId: string) => { + void actions.run(() => window.agentCompany.deleteAutomationRule({ orgId, ruleId })); + setEditingId(null); + }; + + return ( +
+ + + +

Automation

+

{enabledCount} active ยท {totalFires} total fires

+ +
+
+
Name
Trigger
Action
Fires
Status
+
+ {rules.map((rule, index) => ( +
setEditingId(rule.id)} + onKeyDown={(e) => { if (e.key === "Enter") setEditingId(rule.id); }} + style={{ gridTemplateColumns: "minmax(0, 2fr) minmax(0, 1.2fr) minmax(0, 1.2fr) 70px 80px 50px", borderBottom: index === rules.length - 1 ? "none" : "1px solid var(--border-soft)" }} + > +
{rule.name}
+
{humanize(rule.triggerEvent)}
+
{humanize(rule.actionType)}
+
{rule.fireCount}
+
{rule.enabled ? Active : Paused}
+
e.stopPropagation()}> + remove(rule.id)} /> +
+
+ ))} +
+ + setCreateOpen(false)} onSubmit={create}> + + + + + + + + + + + {editing && ( + setEditingId(null)} onSubmit={save}> + + + + + + + + + + + + {editing.lastFiredAt &&
Last fired {formatRelative(editing.lastFiredAt)}
} +
+ )} +
+ ); +} + +function humanize(value: string): string { + return value.replace(/_/g, " "); +} diff --git a/AgentCompany-New/src/renderer/src/pages/DocumentsPage.tsx b/AgentCompany-New/src/renderer/src/pages/DocumentsPage.tsx new file mode 100644 index 00000000..764b74c9 --- /dev/null +++ b/AgentCompany-New/src/renderer/src/pages/DocumentsPage.tsx @@ -0,0 +1,158 @@ +import { useMemo, useState, type FormEvent } from "react"; +import { Avatar, Button, Field, Icon, IconButton, Pill } from "../shared/ui"; +import { TopbarActions } from "../shared/TopbarActions"; +import { Dialog } from "../shared/Dialog"; +import type { DocumentRecord } from "../../../shared/domain"; +import type { PageProps } from "./types"; +import { activeOrgId, formatRelative } from "./types"; + +const DOC_TYPES = ["prd", "spec", "memo", "report", "runbook", "proposal", "policy", "guide", "template", "other"] as const; +const DOC_STATUSES = ["draft", "in_review", "approved", "archived"] as const; + +function text(form: FormData, key: string): string { + return String(form.get(key) ?? "").trim(); +} + +function optional(form: FormData, key: string): string | null { + return text(form, key) || null; +} + +export function DocumentsPage({ snapshot, actions }: PageProps) { + const orgId = activeOrgId(snapshot); + const documents = useMemo(() => snapshot.documents.filter((d) => d.orgId === orgId), [snapshot.documents, orgId]); + const agents = useMemo(() => snapshot.agents.filter((a) => a.orgId === orgId), [snapshot.agents, orgId]); + const projects = useMemo(() => snapshot.projects.filter((p) => p.orgId === orgId), [snapshot.projects, orgId]); + const goals = useMemo(() => snapshot.goals.filter((g) => g.orgId === orgId), [snapshot.goals, orgId]); + const [createOpen, setCreateOpen] = useState(false); + const [editingId, setEditingId] = useState(null); + const [filter, setFilter] = useState("all"); + + const filtered = filter === "all" ? documents : documents.filter((d) => d.docType === filter); + const editing = editingId ? documents.find((d) => d.id === editingId) ?? null : null; + + const create = (event: FormEvent) => { + event.preventDefault(); + const form = new FormData(event.currentTarget); + void actions.run(() => window.agentCompany.createDocument({ + orgId, + title: text(form, "title"), + docType: text(form, "docType"), + content: text(form, "content"), + authorAgentId: optional(form, "authorAgentId"), + reviewerAgentId: null, + projectId: optional(form, "projectId"), + goalId: optional(form, "goalId"), + taskId: null, + })).then((r) => { if (r) setCreateOpen(false); }); + }; + + const save = (event: FormEvent) => { + event.preventDefault(); + if (!editing) return; + const form = new FormData(event.currentTarget); + void actions.run(() => window.agentCompany.updateDocument({ + orgId, + documentId: editing.id, + title: text(form, "title"), + content: text(form, "content"), + status: text(form, "status"), + })).then((r) => { if (r) setEditingId(null); }); + }; + + const remove = (docId: string) => { + void actions.run(() => window.agentCompany.deleteDocument({ orgId, documentId: docId })); + setEditingId(null); + }; + + return ( +
+ + + + +

Documents

+

{documents.length} {documents.length === 1 ? "document" : "documents"}

+ +
+
+
Title
Type
Status
Updated
+
+ {filtered.map((doc, index) => { + const author = agents.find((a) => a.id === doc.authorAgentId); + return ( +
setEditingId(doc.id)} + onKeyDown={(e) => { if (e.key === "Enter") setEditingId(doc.id); }} + style={{ gridTemplateColumns: "minmax(0, 2.5fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) 60px", borderBottom: index === filtered.length - 1 ? "none" : "1px solid var(--border-soft)" }} + > +
+ {author && } + {doc.title} +
+
{doc.docType}
+
{doc.status}
+
{formatRelative(doc.updatedAt)}
+
e.stopPropagation()}> + remove(doc.id)} /> +
+
+ ); + })} +
+ + setCreateOpen(false)} onSubmit={create}> + + + + +