Add org switcher with create, switch, and delete
The sidebar org button was inert: clicking it did nothing. Add a real dropdown menu listing every organization, a "New organization" entry that re-enters onboarding without resetting the database, and a per-org delete affordance with inline confirmation. Active org is now persisted in a single-row app_state table so the snapshot keeps a stable selection across restarts and the renderer drives switching via two new IPC channels (setActiveOrganization, deleteOrganization). - migration 006 adds app_state(id=1, active_org_id, updated_at) - snapshot resolves active org from app_state with the first-org fallback - OrganizationService gains setActive + delete + bootstrap upsert - new OrgSwitcher renderer component keeps Shell.tsx lean
This commit is contained in:
1 parent
060fddf852
commit
f2ac1be5de
14 files changed
+385
-16
No files matched your search
@@ -67,6 +67,8 @@ export function registerIpcHandlers(deps: IpcDeps): void {
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.createOrganization, (_event, input) => mutate(() => organization.createOrganization(input)));
|
||||
ipcMain.handle(IPC_CHANNELS.setActiveOrganization, (_event, input) => mutate(() => organization.setActiveOrganization(input.orgId)));
|
||||
ipcMain.handle(IPC_CHANNELS.deleteOrganization, (_event, input) => mutate(() => organization.deleteOrganization(input.orgId)));
|
||||
ipcMain.handle(IPC_CHANNELS.createAgent, (_event, input) => mutate(() => organization.createAgent(input)));
|
||||
ipcMain.handle(IPC_CHANNELS.createTask, (_event, input) => mutate(() => organization.createTask(input)));
|
||||
ipcMain.handle(IPC_CHANNELS.updateTaskStatus, (_event, input) => mutate(() => organization.updateTaskStatus(input.orgId, input.taskId, input.status)));
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export const appStateSchema = `
|
||||
create table if not exists app_state (
|
||||
id integer primary key check (id = 1),
|
||||
active_org_id text references organizations(id) on delete set null,
|
||||
updated_at text not null
|
||||
);
|
||||
|
||||
insert into app_state(id, active_org_id, updated_at)
|
||||
select 1, (select id from organizations order by created_at asc limit 1), datetime('now')
|
||||
where not exists (select 1 from app_state where id = 1);
|
||||
|
||||
insert into schema_migrations(version, name, applied_at)
|
||||
values (6, 'app_state', datetime('now'));
|
||||
`;
|
||||
@@ -4,6 +4,7 @@ import { collaborationProviderPolicySchema } from "./migrations/002-collaboratio
|
||||
import { onboardingExecutionSchema } from "./migrations/003-onboarding";
|
||||
import { filesPinnedAndScheduledPosts } from "./migrations/004-files-posts";
|
||||
import { chatReadState } from "./migrations/005-chat-read-state";
|
||||
import { appStateSchema } from "./migrations/006-app-state";
|
||||
|
||||
const MIGRATIONS: Array<{ version: number; sql: string }> = [
|
||||
{ version: 1, sql: initialEnterpriseSchema },
|
||||
@@ -11,6 +12,7 @@ const MIGRATIONS: Array<{ version: number; sql: string }> = [
|
||||
{ version: 3, sql: onboardingExecutionSchema },
|
||||
{ version: 4, sql: filesPinnedAndScheduledPosts },
|
||||
{ version: 5, sql: chatReadState },
|
||||
{ version: 6, sql: appStateSchema },
|
||||
];
|
||||
|
||||
const SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
|
||||
|
||||
@@ -74,6 +74,11 @@ export class OrganizationService {
|
||||
}
|
||||
|
||||
this.policy.seedOrganizationPolicies(orgId, now);
|
||||
this.db.prepare(`
|
||||
insert into app_state(id, active_org_id, updated_at)
|
||||
values (1, ?, ?)
|
||||
on conflict(id) do update set active_org_id = excluded.active_org_id, updated_at = excluded.updated_at
|
||||
`).run(orgId, now);
|
||||
this.audit.record({
|
||||
orgId,
|
||||
entityType: "organization",
|
||||
@@ -86,6 +91,41 @@ export class OrganizationService {
|
||||
return orgId;
|
||||
}
|
||||
|
||||
setActiveOrganization(orgId: string): void {
|
||||
const row = this.db.prepare("select id from organizations where id = ?").get(orgId) as { id: string } | undefined;
|
||||
if (!row) throw new Error("Organization was not found.");
|
||||
const now = new Date().toISOString();
|
||||
this.db.prepare(`
|
||||
insert into app_state(id, active_org_id, updated_at)
|
||||
values (1, ?, ?)
|
||||
on conflict(id) do update set active_org_id = excluded.active_org_id, updated_at = excluded.updated_at
|
||||
`).run(orgId, now);
|
||||
this.audit.record({
|
||||
orgId,
|
||||
entityType: "app_state",
|
||||
entityId: "active_org",
|
||||
actorType: "employee",
|
||||
actorId: null,
|
||||
action: "switched",
|
||||
payload: { orgId },
|
||||
});
|
||||
}
|
||||
|
||||
deleteOrganization(orgId: string): void {
|
||||
const row = this.db.prepare("select id from organizations where id = ?")
|
||||
.get(orgId) as { id: string } | undefined;
|
||||
if (!row) throw new Error("Organization was not found.");
|
||||
this.db.prepare("delete from organizations where id = ?").run(orgId);
|
||||
const fallback = this.db.prepare("select id from organizations order by created_at asc limit 1")
|
||||
.get() as { id: string } | undefined;
|
||||
const now = new Date().toISOString();
|
||||
this.db.prepare(`
|
||||
insert into app_state(id, active_org_id, updated_at)
|
||||
values (1, ?, ?)
|
||||
on conflict(id) do update set active_org_id = excluded.active_org_id, updated_at = excluded.updated_at
|
||||
`).run(fallback?.id ?? null, now);
|
||||
}
|
||||
|
||||
createAgent(input: CreateAgentInput): string {
|
||||
const id = createId("agt");
|
||||
const now = new Date().toISOString();
|
||||
|
||||
@@ -34,7 +34,7 @@ export class SnapshotService {
|
||||
autoApproveHires: toBool(org.autoApproveHires),
|
||||
reviewDeliverables: toBool(org.reviewDeliverables),
|
||||
}));
|
||||
const activeOrgId = organizations[0]?.id ?? null;
|
||||
const activeOrgId = this.resolveActiveOrgId(organizations.map((org) => org.id));
|
||||
const agents = list<AppSnapshot["agents"][number]>(this.db, "agents", "created_at asc")
|
||||
.map((agent) => ({ ...agent, heartbeatEnabled: toBool(agent.heartbeatEnabled) }));
|
||||
const tasks = list<AppSnapshot["tasks"][number]>(this.db, "tasks", "created_at asc")
|
||||
@@ -82,4 +82,12 @@ export class SnapshotService {
|
||||
auditEvents: list<AppSnapshot["auditEvents"][number]>(this.db, "audit_events", "created_at desc").slice(0, 200),
|
||||
};
|
||||
}
|
||||
|
||||
private resolveActiveOrgId(known: string[]): string | null {
|
||||
const row = this.db.prepare("select active_org_id from app_state where id = 1")
|
||||
.get() as { active_org_id: string | null } | undefined;
|
||||
const stored = row?.active_org_id ?? null;
|
||||
if (stored && known.includes(stored)) return stored;
|
||||
return known[0] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ const api: DesktopApi = {
|
||||
loadSnapshot: () => invoke(IPC_CHANNELS.loadSnapshot),
|
||||
bootstrapOnboarding: (input) => invoke(IPC_CHANNELS.bootstrapOnboarding, input),
|
||||
createOrganization: (input) => invoke(IPC_CHANNELS.createOrganization, input),
|
||||
setActiveOrganization: (input) => invoke(IPC_CHANNELS.setActiveOrganization, input),
|
||||
deleteOrganization: (input) => invoke(IPC_CHANNELS.deleteOrganization, input),
|
||||
createAgent: (input) => invoke(IPC_CHANNELS.createAgent, input),
|
||||
createTask: (input) => invoke(IPC_CHANNELS.createTask, input),
|
||||
updateTaskStatus: (input) => invoke(IPC_CHANNELS.updateTaskStatus, input),
|
||||
|
||||
@@ -14,12 +14,20 @@ import { SettingsPage } from "../pages/SettingsPage";
|
||||
export function App() {
|
||||
const { snapshot, actions } = useSnapshot();
|
||||
const [route, setRoute] = useState<RouteId>("home");
|
||||
const [creatingOrg, setCreatingOrg] = useState(false);
|
||||
|
||||
if (!snapshot) {
|
||||
return <div className="app-shell" style={{ display: "grid", placeItems: "center" }} />;
|
||||
}
|
||||
if (snapshot.organizations.length === 0) {
|
||||
return <Onboarding snapshot={snapshot} actions={actions} />;
|
||||
if (snapshot.organizations.length === 0 || creatingOrg) {
|
||||
return (
|
||||
<Onboarding
|
||||
snapshot={snapshot}
|
||||
actions={actions}
|
||||
onCancel={creatingOrg ? () => setCreatingOrg(false) : null}
|
||||
onCompleted={() => setCreatingOrg(false)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const pageProps = { snapshot, actions };
|
||||
@@ -35,7 +43,14 @@ export function App() {
|
||||
}[route];
|
||||
|
||||
return (
|
||||
<Shell snapshot={snapshot} route={route} setRoute={setRoute}>
|
||||
<Shell
|
||||
snapshot={snapshot}
|
||||
route={route}
|
||||
setRoute={setRoute}
|
||||
onSwitchOrg={(orgId) => void actions.run(() => window.agentCompany.setActiveOrganization({ orgId }))}
|
||||
onDeleteOrg={(orgId) => void actions.run(() => window.agentCompany.deleteOrganization({ orgId }))}
|
||||
onNewOrg={() => setCreatingOrg(true)}
|
||||
>
|
||||
{page}
|
||||
</Shell>
|
||||
);
|
||||
|
||||
@@ -30,7 +30,17 @@ const STEPS: Array<{ id: Step; label: string }> = [
|
||||
{ id: "mission", label: "Goal" },
|
||||
];
|
||||
|
||||
export function Onboarding({ snapshot, actions }: { snapshot: AppSnapshot; actions: SnapshotActions }) {
|
||||
export function Onboarding({
|
||||
snapshot,
|
||||
actions,
|
||||
onCancel,
|
||||
onCompleted,
|
||||
}: {
|
||||
snapshot: AppSnapshot;
|
||||
actions: SnapshotActions;
|
||||
onCancel: (() => void) | null;
|
||||
onCompleted: () => void;
|
||||
}) {
|
||||
const apiProviders = useMemo(
|
||||
() => snapshot.providerCatalog.filter((entry) => entry.authType === PROVIDER_AUTH_TYPE.API_KEY),
|
||||
[snapshot.providerCatalog],
|
||||
@@ -102,6 +112,7 @@ export function Onboarding({ snapshot, actions }: { snapshot: AppSnapshot; actio
|
||||
return;
|
||||
}
|
||||
await actions.reload();
|
||||
onCompleted();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -111,6 +122,11 @@ export function Onboarding({ snapshot, actions }: { snapshot: AppSnapshot; actio
|
||||
<div className="row" style={{ gap: 8 }}>
|
||||
<div className="brand-mark">A</div>
|
||||
<div style={{ fontWeight: 540 }}>AgentCompany</div>
|
||||
{onCancel && (
|
||||
<button type="button" className="button ghost" style={{ marginLeft: "auto" }} onClick={onCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<h1 className="page-h1" style={{ fontSize: 34, marginTop: 32, letterSpacing: "-0.02em" }}>Start your AI company.</h1>
|
||||
<p className="page-sub" style={{ maxWidth: 460, fontSize: 14, lineHeight: 1.55 }}>
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { AppSnapshot, OrganizationRecord } from "../../../shared/domain";
|
||||
import { Icon } from "./Icon";
|
||||
|
||||
interface OrgSwitcherProps {
|
||||
snapshot: AppSnapshot;
|
||||
activeOrgId: string;
|
||||
onSelect: (orgId: string) => void;
|
||||
onNew: () => void;
|
||||
onDelete: (orgId: string) => void;
|
||||
}
|
||||
|
||||
export function OrgSwitcher({ snapshot, activeOrgId, onSelect, onNew, onDelete }: OrgSwitcherProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [confirmingDelete, setConfirmingDelete] = useState<string | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const active = snapshot.organizations.find((org) => org.id === activeOrgId);
|
||||
if (!active) throw new Error("Active organization missing from snapshot.");
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
function onDocClick(event: MouseEvent) {
|
||||
if (!containerRef.current?.contains(event.target as Node)) {
|
||||
setOpen(false);
|
||||
setConfirmingDelete(null);
|
||||
}
|
||||
}
|
||||
document.addEventListener("mousedown", onDocClick);
|
||||
return () => document.removeEventListener("mousedown", onDocClick);
|
||||
}, [open]);
|
||||
|
||||
const initial = active.name.trim().charAt(0).toUpperCase() || "A";
|
||||
const orgs = snapshot.organizations;
|
||||
|
||||
const handleSelect = (orgId: string) => {
|
||||
setOpen(false);
|
||||
setConfirmingDelete(null);
|
||||
if (orgId !== activeOrgId) onSelect(orgId);
|
||||
};
|
||||
|
||||
const handleDelete = (orgId: string) => {
|
||||
setOpen(false);
|
||||
setConfirmingDelete(null);
|
||||
onDelete(orgId);
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={containerRef} style={{ position: "relative" }}>
|
||||
<button
|
||||
type="button"
|
||||
className="sidebar-org"
|
||||
aria-label={`${active.name} — switch organization`}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={open}
|
||||
onClick={() => setOpen((value) => !value)}
|
||||
>
|
||||
<span className="brand-mark">{initial}</span>
|
||||
<span className="brand-name">{active.name}</span>
|
||||
<Icon name="chevronDown" size={12} color="var(--text-3)" />
|
||||
</button>
|
||||
{open && (
|
||||
<div role="menu" className="org-menu">
|
||||
<div className="org-menu-section-label">Organizations</div>
|
||||
{orgs.map((org) => (
|
||||
<OrgRow
|
||||
key={org.id}
|
||||
org={org}
|
||||
active={org.id === activeOrgId}
|
||||
onSelect={() => handleSelect(org.id)}
|
||||
confirming={confirmingDelete === org.id}
|
||||
onAskDelete={() => setConfirmingDelete(org.id)}
|
||||
onCancelDelete={() => setConfirmingDelete(null)}
|
||||
onConfirmDelete={() => handleDelete(org.id)}
|
||||
canDelete={orgs.length > 1}
|
||||
/>
|
||||
))}
|
||||
<div className="org-menu-divider" />
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
className="org-menu-action"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
setConfirmingDelete(null);
|
||||
onNew();
|
||||
}}
|
||||
>
|
||||
<Icon name="plus" size={13} color="var(--text-2)" />
|
||||
<span>New organization</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OrgRow({
|
||||
org,
|
||||
active,
|
||||
onSelect,
|
||||
confirming,
|
||||
onAskDelete,
|
||||
onCancelDelete,
|
||||
onConfirmDelete,
|
||||
canDelete,
|
||||
}: {
|
||||
org: OrganizationRecord;
|
||||
active: boolean;
|
||||
onSelect: () => void;
|
||||
confirming: boolean;
|
||||
onAskDelete: () => void;
|
||||
onCancelDelete: () => void;
|
||||
onConfirmDelete: () => void;
|
||||
canDelete: boolean;
|
||||
}) {
|
||||
const initial = org.name.trim().charAt(0).toUpperCase() || "A";
|
||||
if (confirming) {
|
||||
return (
|
||||
<div className="org-menu-row confirm">
|
||||
<span className="brand-mark sm">{initial}</span>
|
||||
<div className="org-menu-confirm-text">
|
||||
<div>Delete {org.name}?</div>
|
||||
<div className="org-menu-confirm-hint">All agents, work, chats, files removed.</div>
|
||||
</div>
|
||||
<button type="button" className="org-menu-confirm-yes" onClick={onConfirmDelete}>Delete</button>
|
||||
<button type="button" className="org-menu-confirm-no" onClick={onCancelDelete}>Cancel</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
className={`org-menu-row ${active ? "active" : ""}`.trim()}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<span className="brand-mark sm">{initial}</span>
|
||||
<span className="org-menu-name">{org.name}</span>
|
||||
{active && <Icon name="check" size={12} color="var(--text-1)" />}
|
||||
{!active && canDelete && (
|
||||
<span
|
||||
role="button"
|
||||
aria-label={`Delete ${org.name}`}
|
||||
className="org-menu-delete"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onAskDelete();
|
||||
}}
|
||||
>
|
||||
<Icon name="x" size={12} color="var(--text-3)" />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import type { ReactNode } from "react";
|
||||
import type { AppSnapshot } from "../../../shared/domain";
|
||||
import { Avatar, Icon, IconButton } from "./ui";
|
||||
import type { IconName } from "./Icon";
|
||||
import { OrgSwitcher } from "./OrgSwitcher";
|
||||
|
||||
export type RouteId =
|
||||
| "home"
|
||||
@@ -34,12 +35,18 @@ export function Shell({
|
||||
route,
|
||||
setRoute,
|
||||
actions,
|
||||
onSwitchOrg,
|
||||
onDeleteOrg,
|
||||
onNewOrg,
|
||||
children,
|
||||
}: {
|
||||
snapshot: AppSnapshot;
|
||||
route: RouteId;
|
||||
setRoute: (route: RouteId) => void;
|
||||
actions?: ReactNode;
|
||||
onSwitchOrg: (orgId: string) => void;
|
||||
onDeleteOrg: (orgId: string) => void;
|
||||
onNewOrg: () => void;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const organization = snapshot.organizations.find((item) => item.id === snapshot.activeOrgId);
|
||||
@@ -48,21 +55,17 @@ export function Shell({
|
||||
if (!employee) throw new Error("Active employee is missing.");
|
||||
const badges = computeBadges(snapshot, organization.id);
|
||||
const activeLabel = labelFor(route);
|
||||
const orgInitial = organization.name.trim().charAt(0).toUpperCase() || "A";
|
||||
|
||||
return (
|
||||
<div className="app-shell">
|
||||
<aside className="sidebar">
|
||||
<button
|
||||
type="button"
|
||||
className="sidebar-org"
|
||||
aria-label={`${organization.name} — switch organization`}
|
||||
title={organization.name}
|
||||
>
|
||||
<span className="brand-mark">{orgInitial}</span>
|
||||
<span className="brand-name">{organization.name}</span>
|
||||
<Icon name="chevronDown" size={12} color="var(--text-3)" />
|
||||
</button>
|
||||
<OrgSwitcher
|
||||
snapshot={snapshot}
|
||||
activeOrgId={organization.id}
|
||||
onSelect={onSwitchOrg}
|
||||
onNew={onNewOrg}
|
||||
onDelete={onDeleteOrg}
|
||||
/>
|
||||
<button type="button" className="search-bar" aria-label="Search">
|
||||
<Icon name="search" size={13} />
|
||||
<span>Search</span>
|
||||
|
||||
@@ -150,6 +150,104 @@ a { color: inherit; }
|
||||
color: var(--text-3);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.org-menu {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: calc(100% + 4px);
|
||||
z-index: 30;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-md);
|
||||
box-shadow: var(--shadow-md);
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
min-width: 220px;
|
||||
}
|
||||
.org-menu-section-label {
|
||||
font-size: 11px;
|
||||
color: var(--text-3);
|
||||
font-weight: 540;
|
||||
letter-spacing: 0.02em;
|
||||
padding: 4px 8px 4px;
|
||||
}
|
||||
.org-menu-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
border: 0;
|
||||
border-radius: var(--r-sm);
|
||||
background: transparent;
|
||||
color: var(--text-2);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
.org-menu-row:hover { background: var(--hover); color: var(--text-1); }
|
||||
.org-menu-row.active { background: var(--panel-3); color: var(--text-1); font-weight: 540; }
|
||||
.org-menu-row.confirm {
|
||||
background: var(--amber-soft);
|
||||
flex-wrap: wrap;
|
||||
padding: 8px;
|
||||
}
|
||||
.org-menu-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.org-menu-delete {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
}
|
||||
.org-menu-row:hover .org-menu-delete { opacity: 1; }
|
||||
.org-menu-delete:hover { background: var(--panel-3); }
|
||||
.org-menu-confirm-text { flex: 1; min-width: 0; font-size: 12.5px; color: var(--text-1); }
|
||||
.org-menu-confirm-hint { font-size: 11px; color: var(--text-3); margin-top: 2px; }
|
||||
.org-menu-confirm-yes {
|
||||
height: 24px;
|
||||
padding: 0 8px;
|
||||
border: 0;
|
||||
border-radius: var(--r-sm);
|
||||
background: var(--red);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 540;
|
||||
}
|
||||
.org-menu-confirm-no {
|
||||
height: 24px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--r-sm);
|
||||
background: var(--panel);
|
||||
color: var(--text-2);
|
||||
font-size: 12px;
|
||||
}
|
||||
.org-menu-divider { height: 1px; background: var(--border-soft); margin: 4px 0; }
|
||||
.org-menu-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
border: 0;
|
||||
border-radius: var(--r-sm);
|
||||
background: transparent;
|
||||
color: var(--text-2);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
.org-menu-action:hover { background: var(--hover); color: var(--text-1); }
|
||||
.brand-mark.sm {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
font-size: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.sidebar-spacer { flex: 1; }
|
||||
.sidebar-foot {
|
||||
padding: 10px 8px 4px;
|
||||
|
||||
@@ -6,6 +6,14 @@ import type {
|
||||
TaskStatus,
|
||||
} from "./domain";
|
||||
|
||||
export interface SetActiveOrganizationInput {
|
||||
orgId: string;
|
||||
}
|
||||
|
||||
export interface DeleteOrganizationInput {
|
||||
orgId: string;
|
||||
}
|
||||
|
||||
export interface CreateOrganizationInput {
|
||||
organizationName: string;
|
||||
description?: string;
|
||||
|
||||
@@ -15,12 +15,14 @@ import type {
|
||||
CreateSocialAccountInput,
|
||||
CreateTaskInput,
|
||||
DecideApprovalInput,
|
||||
DeleteOrganizationInput,
|
||||
ImportFileInput,
|
||||
ImportSkillDirectoryInput,
|
||||
LaunchSocialLoginInput,
|
||||
ResolveAgentSkillsInput,
|
||||
SaveProviderCredentialInput,
|
||||
SendMessageInput,
|
||||
SetActiveOrganizationInput,
|
||||
SetFilePinnedInput,
|
||||
SetScheduledPostStateInput,
|
||||
UpdateTaskStatusInput,
|
||||
@@ -33,6 +35,8 @@ export interface RendererEvent {
|
||||
export interface DesktopApi {
|
||||
loadSnapshot(): Promise<DesktopResult<AppSnapshot>>;
|
||||
bootstrapOnboarding(input: BootstrapOnboardingInput): Promise<DesktopResult<BootstrapOnboardingSummary>>;
|
||||
setActiveOrganization(input: SetActiveOrganizationInput): Promise<DesktopResult<AppSnapshot>>;
|
||||
deleteOrganization(input: DeleteOrganizationInput): Promise<DesktopResult<AppSnapshot>>;
|
||||
createOrganization(input: CreateOrganizationInput): Promise<DesktopResult<AppSnapshot>>;
|
||||
createAgent(input: CreateAgentInput): Promise<DesktopResult<AppSnapshot>>;
|
||||
createTask(input: CreateTaskInput): Promise<DesktopResult<AppSnapshot>>;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export const IPC_CHANNELS = {
|
||||
loadSnapshot: "agentcompany:load-snapshot",
|
||||
bootstrapOnboarding: "agentcompany:bootstrap-onboarding",
|
||||
setActiveOrganization: "agentcompany:set-active-organization",
|
||||
deleteOrganization: "agentcompany:delete-organization",
|
||||
createOrganization: "agentcompany:create-organization",
|
||||
createAgent: "agentcompany:create-agent",
|
||||
createTask: "agentcompany:create-task",
|
||||
|
||||
Reference in new issue
Block a user