Make a workspace page a dashboard and drop its coordinator from the rail
The workspace rail now lists only the bots attached to the workspace, with the back link returning to the workspace page from a bot's conversation. /admin/workspaces/<id> becomes a read-only dashboard (picture, context, latest activity, bots, computers) that links into the editable settings page.
This commit is contained in:
@@ -220,6 +220,10 @@ export const AppData = {
|
||||
* Stones shown beside "See all bots".
|
||||
*/
|
||||
RailSeeAllStones: 4,
|
||||
/**
|
||||
* Recent sessions a workspace's dashboard shows.
|
||||
*/
|
||||
WorkspaceActivityItems: 6,
|
||||
/**
|
||||
* Tool turns a chat reply may take. Long work goes to a task instead.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import BunSQLite from "@moduletrace/bun-sqlite";
|
||||
import { AppData } from "@/src/data/app-data";
|
||||
import type { WorkspaceActivityItem } from "@/src/types";
|
||||
|
||||
type Params = {
|
||||
user_id: number | string;
|
||||
workspace_id: number | string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The user's latest conversations in a workspace, most recent first, each
|
||||
* with the bot it's with, its message count and latest message. Empty
|
||||
* sessions are left out unless active.
|
||||
*/
|
||||
export default async function grabWorkspaceActivity({
|
||||
user_id,
|
||||
workspace_id,
|
||||
}: Params): Promise<WorkspaceActivityItem[]> {
|
||||
const res = await BunSQLite.sql<WorkspaceActivityItem>({
|
||||
sql: `SELECT s.*,
|
||||
b.name AS bot_name,
|
||||
b.image_media_id AS bot_image_media_id,
|
||||
b.hue AS bot_hue,
|
||||
b.face AS bot_face,
|
||||
(SELECT COUNT(*) FROM bot_messages m WHERE m.session_id = s.id) AS message_count,
|
||||
(SELECT m.text FROM bot_messages m WHERE m.session_id = s.id ORDER BY m.created_at DESC, m.id DESC LIMIT 1) AS last_message_text
|
||||
FROM bot_sessions s
|
||||
JOIN bots b ON b.id = s.bot_id
|
||||
WHERE s.workspace_id = ? AND s.user_id = ?
|
||||
AND (s.status = 'active' OR EXISTS (SELECT 1 FROM bot_messages m WHERE m.session_id = s.id))
|
||||
AND (b.kind = 'coordinator' OR EXISTS (
|
||||
SELECT 1 FROM workspace_bots wb WHERE wb.workspace_id = s.workspace_id AND wb.bot_id = s.bot_id
|
||||
))
|
||||
ORDER BY COALESCE(s.last_activity_at, s.created_at) DESC, s.id DESC
|
||||
LIMIT ?`,
|
||||
values: [workspace_id, user_id, AppData.WorkspaceActivityItems],
|
||||
});
|
||||
|
||||
return res.payload || [];
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import Divider from "@moduletrace/twui/layout/Divider";
|
||||
import { useContext } from "react";
|
||||
import { AddFlowContext } from "../(data)/add-flow-context";
|
||||
import type { BotWithLastMessage, WorkspaceWithActivity } from "@/src/types";
|
||||
import CoordinatorRow from "./coordinator-row";
|
||||
import RailBackLink from "./rail-back-link";
|
||||
import RailBotsSection from "./rail-bots-section";
|
||||
import RailSectionAddButton from "./rail-section-add-button";
|
||||
@@ -11,7 +10,8 @@ import WorkspaceRow from "./workspace-row";
|
||||
type Props = {
|
||||
workspace: WorkspaceWithActivity;
|
||||
/**
|
||||
* The workspace's crew: the coordinator and its bots.
|
||||
* The workspace's crew: the coordinator is left out, only the bots
|
||||
* attached to the workspace show.
|
||||
*/
|
||||
bots: BotWithLastMessage[];
|
||||
selected_id?: number;
|
||||
@@ -23,28 +23,22 @@ type Props = {
|
||||
|
||||
/**
|
||||
* The rail inside a workspace: back to all bots, the workspace itself (its
|
||||
* page), its coordinator conversation, then the workspace's bots. Every
|
||||
* conversation here runs with the workspace's context.
|
||||
* page), then the workspace's bots. Every conversation here runs with the
|
||||
* workspace's context. The coordinator is reached from the main rail.
|
||||
*/
|
||||
export default function RailWorkspaceList({ workspace, bots, selected_id, on_workspace_page }: Props) {
|
||||
const { openForm } = useContext(AddFlowContext);
|
||||
const coordinator = bots.find((bot) => bot.kind === "coordinator");
|
||||
const workers = bots.filter((bot) => bot.kind !== "coordinator");
|
||||
const base = `/admin/workspaces/${workspace.id}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<RailBackLink title="All bots" />
|
||||
<RailBackLink
|
||||
title={on_workspace_page ? "All bots" : `Back to ${workspace.name}`}
|
||||
href={on_workspace_page ? undefined : base}
|
||||
/>
|
||||
<Divider className="shrink-0 mx-1 lg:mx-3 my-2 w-auto border-hairline dark:border-hairline" />
|
||||
<WorkspaceRow workspace={{ ...workspace, unread: 0 }} selected={on_workspace_page} />
|
||||
{coordinator ? (
|
||||
<CoordinatorRow
|
||||
bot={coordinator}
|
||||
bots={bots}
|
||||
href={`${base}/bots/${coordinator.id}`}
|
||||
selected={!on_workspace_page && coordinator.id === selected_id}
|
||||
/>
|
||||
) : null}
|
||||
<RailBotsSection
|
||||
action={<RailSectionAddButton title="Add a bot to this workspace" onClick={() => openForm({ kind: "bot" })} />}
|
||||
title="Workspace bots"
|
||||
|
||||
+3
-21
@@ -1,7 +1,5 @@
|
||||
import { useContext, useState } from "react";
|
||||
import EmptyContent from "@moduletrace/twui/elements/EmptyContent";
|
||||
import { Settings } from "lucide-react";
|
||||
import HeaderIconButton from "@/src/components/general/header-icon-button";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import canManageCrew from "@/src/utils/rbac/can-manage-crew";
|
||||
import SettingsSectionHeading from "../../../(partials)/settings-section-heading";
|
||||
@@ -13,19 +11,12 @@ import WorkspaceDetailsGroup from "../(partials)/workspace-details-group";
|
||||
import WorkspaceImageGroup from "../(partials)/workspace-image-group";
|
||||
import WorkspaceModels from "../(partials)/workspace-models";
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
* On the workspace's own page: show the link into its settings.
|
||||
*/
|
||||
show_settings_link?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* A workspace's page: its picture, context, bots, computers and models,
|
||||
* A workspace's settings: its picture, context, bots, computers and models,
|
||||
* then deleting it. Super admins and admins manage it; a member with no
|
||||
* bots sees an empty note.
|
||||
*/
|
||||
export default function WorkspaceOverview({ show_settings_link }: Props) {
|
||||
export default function WorkspaceOverview() {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
const [workspace, setWorkspace] = useState(pageProps?.workspace);
|
||||
|
||||
@@ -56,16 +47,7 @@ export default function WorkspaceOverview({ show_settings_link }: Props) {
|
||||
workspace.description ||
|
||||
"Context, bots, computers and models for this workspace."
|
||||
}
|
||||
breadcrumb_title={show_settings_link ? undefined : name}
|
||||
action={
|
||||
show_settings_link ? (
|
||||
<HeaderIconButton
|
||||
icon={Settings}
|
||||
title="Workspace settings"
|
||||
href={`/admin/settings/workspaces/${workspace_id}`}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
breadcrumb_title={name}
|
||||
>
|
||||
<WorkspaceImageGroup workspace={workspace} />
|
||||
<WorkspaceDetailsGroup workspace={workspace} onSaved={setWorkspace} />
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import Row from "@moduletrace/twui/layout/Row";
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import Stack from "@moduletrace/twui/layout/Stack";
|
||||
import type { BUN_SQLITE_COBBLE_WORKSPACES } from "@/db/types/db";
|
||||
import WorkspaceAvatar from "@/src/components/general/workspace-avatar";
|
||||
import { MessageMarkdownComponents } from "@/src/pages/admin/bots/[bot_id]/(data)/message-markdown-components";
|
||||
import SettingsGroup from "@/src/pages/admin/settings/(partials)/settings-group";
|
||||
|
||||
type Props = {
|
||||
workspace: BUN_SQLITE_COBBLE_WORKSPACES;
|
||||
};
|
||||
|
||||
/**
|
||||
* What the workspace is: its picture, name, what it's for, and the context
|
||||
* every bot working here gets.
|
||||
*/
|
||||
export default function WorkspaceAboutGroup({ workspace }: Props) {
|
||||
const name = workspace.name || "Workspace";
|
||||
const context = workspace.system_prompt?.trim();
|
||||
|
||||
return (
|
||||
<SettingsGroup
|
||||
id="about"
|
||||
title="Workspace"
|
||||
description="Its picture, what it's for and the context every bot here gets."
|
||||
>
|
||||
<Row className="w-full gap-4! flex-nowrap items-center">
|
||||
<WorkspaceAvatar
|
||||
size="lg"
|
||||
image_media_id={workspace.image_media_id}
|
||||
/>
|
||||
<Stack className="grow min-w-0 gap-0!">
|
||||
<Span className="text-base font-bold leading-6 text-ink truncate w-full">
|
||||
{name}
|
||||
</Span>
|
||||
<Span className="text-[13px] leading-[18px] text-ink-secondary">
|
||||
{workspace.description || "No description yet."}
|
||||
</Span>
|
||||
</Stack>
|
||||
</Row>
|
||||
{context ? (
|
||||
<Stack className="gap-3! items-stretch min-w-0">
|
||||
<Markdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={MessageMarkdownComponents}
|
||||
>
|
||||
{context}
|
||||
</Markdown>
|
||||
</Stack>
|
||||
) : (
|
||||
<Span className="text-[13px] leading-[18px] text-ink-secondary">
|
||||
No context yet. Every bot here works from its own instructions alone.
|
||||
</Span>
|
||||
)}
|
||||
</SettingsGroup>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import SettingsGroup from "@/src/pages/admin/settings/(partials)/settings-group";
|
||||
import SettingsList from "@/src/pages/admin/settings/(partials)/settings-list";
|
||||
import type { WorkspaceActivityItem } from "@/src/types";
|
||||
import WorkspaceActivityRow from "./workspace-activity-row";
|
||||
|
||||
type Props = {
|
||||
workspace_id: number;
|
||||
/**
|
||||
* The user's latest sessions in the workspace, most recent first.
|
||||
*/
|
||||
activity: WorkspaceActivityItem[];
|
||||
};
|
||||
|
||||
/**
|
||||
* What has happened in the workspace lately: the user's latest
|
||||
* conversations, each opening the bot's conversation here.
|
||||
*/
|
||||
export default function WorkspaceActivityGroup({ workspace_id, activity }: Props) {
|
||||
return (
|
||||
<SettingsGroup
|
||||
id="activity"
|
||||
title="Latest activity"
|
||||
description="Your latest conversations with the bots here."
|
||||
>
|
||||
<SettingsList
|
||||
is_empty={!activity.length}
|
||||
empty="No conversations here yet. Open a bot below to start one."
|
||||
>
|
||||
{activity.map((item) => (
|
||||
<WorkspaceActivityRow
|
||||
key={item.id}
|
||||
workspace_id={workspace_id}
|
||||
item={item}
|
||||
/>
|
||||
))}
|
||||
</SettingsList>
|
||||
</SettingsGroup>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import BotAvatar from "@/src/components/general/bot-avatar";
|
||||
import type { BotFace, BotHue } from "@/src/dict/bot-dict";
|
||||
import SettingsListRow from "@/src/pages/admin/settings/(partials)/settings-list-row";
|
||||
import type { WorkspaceActivityItem } from "@/src/types";
|
||||
import messagePreview from "@/src/utils/message-preview";
|
||||
import relativeTime from "@/src/utils/relative-time";
|
||||
|
||||
type Props = {
|
||||
workspace_id: number;
|
||||
item: WorkspaceActivityItem;
|
||||
};
|
||||
|
||||
/**
|
||||
* One recent conversation in the workspace: the bot, the session's title,
|
||||
* a preview of its latest message and when it last moved.
|
||||
*/
|
||||
export default function WorkspaceActivityRow({ workspace_id, item }: Props) {
|
||||
const at = item.last_activity_at || item.created_at;
|
||||
const preview = item.last_message_text
|
||||
? messagePreview({ text: item.last_message_text })
|
||||
: "";
|
||||
|
||||
return (
|
||||
<SettingsListRow
|
||||
avatar={
|
||||
<BotAvatar
|
||||
image_media_id={item.bot_image_media_id}
|
||||
hue={(item.bot_hue || null) as BotHue | null}
|
||||
face={(item.bot_face || "idle") as BotFace}
|
||||
/>
|
||||
}
|
||||
title={item.title || "Untitled conversation"}
|
||||
href={`/admin/workspaces/${workspace_id}/bots/${item.bot_id}`}
|
||||
badge={item.unread ? "New" : undefined}
|
||||
subtitle={`${item.bot_name || "A bot"}${preview ? ` · ${preview}` : ""}`}
|
||||
>
|
||||
<Span className="text-[11px] leading-4 text-ink-secondary">
|
||||
{at ? relativeTime({ at: Number(at) }) : ""}
|
||||
</Span>
|
||||
</SettingsListRow>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import SettingsGroup from "@/src/pages/admin/settings/(partials)/settings-group";
|
||||
import SettingsList from "@/src/pages/admin/settings/(partials)/settings-list";
|
||||
import type { WorkspaceBot } from "@/src/types";
|
||||
import WorkspaceDashboardBotRow from "./workspace-dashboard-bot-row";
|
||||
|
||||
type Props = {
|
||||
workspace_id: number;
|
||||
/**
|
||||
* The workspace's crew, the coordinator among them.
|
||||
*/
|
||||
bots: WorkspaceBot[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Who works here, each opening its conversation in the workspace. Display
|
||||
* only: bots are added and taken out under Settings.
|
||||
*/
|
||||
export default function WorkspaceBotsListGroup({ workspace_id, bots }: Props) {
|
||||
return (
|
||||
<SettingsGroup
|
||||
id="bots"
|
||||
title="Bots"
|
||||
description="Everyone working here. The coordinator is in every workspace."
|
||||
>
|
||||
<SettingsList
|
||||
is_empty={!bots.length}
|
||||
empty="No bots here yet. Add some under Settings."
|
||||
>
|
||||
{bots.map((bot) => (
|
||||
<WorkspaceDashboardBotRow
|
||||
key={bot.id}
|
||||
workspace_id={workspace_id}
|
||||
bot={bot}
|
||||
/>
|
||||
))}
|
||||
</SettingsList>
|
||||
</SettingsGroup>
|
||||
);
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import SettingsGroup from "@/src/pages/admin/settings/(partials)/settings-group";
|
||||
import SettingsList from "@/src/pages/admin/settings/(partials)/settings-list";
|
||||
import type { ComputerWithStatus } from "@/src/types";
|
||||
import WorkspaceDashboardComputerRow from "./workspace-dashboard-computer-row";
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
* The computers linked to the workspace, its default first.
|
||||
*/
|
||||
computers: ComputerWithStatus[];
|
||||
};
|
||||
|
||||
/**
|
||||
* The computers bots work on here, with their state. Display only:
|
||||
* computers are linked and set up under Settings.
|
||||
*/
|
||||
export default function WorkspaceComputersListGroup({ computers }: Props) {
|
||||
return (
|
||||
<SettingsGroup
|
||||
id="computers"
|
||||
title="Computers"
|
||||
description="Where tasks run. The default one is used unless a bot picks another."
|
||||
>
|
||||
<SettingsList
|
||||
is_empty={!computers.length}
|
||||
empty="No computers here yet. Bots can chat, but tasks that need a computer can't run."
|
||||
>
|
||||
{computers.map((computer, index) => (
|
||||
<WorkspaceDashboardComputerRow
|
||||
key={computer.id}
|
||||
computer={computer}
|
||||
is_default={index == 0}
|
||||
/>
|
||||
))}
|
||||
</SettingsList>
|
||||
</SettingsGroup>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import BotAvatar from "@/src/components/general/bot-avatar";
|
||||
import type { BotFace, BotHue } from "@/src/dict/bot-dict";
|
||||
import SettingsListRow from "@/src/pages/admin/settings/(partials)/settings-list-row";
|
||||
import type { WorkspaceBot } from "@/src/types";
|
||||
|
||||
type Props = {
|
||||
workspace_id: number;
|
||||
bot: WorkspaceBot;
|
||||
};
|
||||
|
||||
/**
|
||||
* A bot in the workspace list: its face, name and how it works here, its
|
||||
* row opening its conversation.
|
||||
*/
|
||||
export default function WorkspaceDashboardBotRow({ workspace_id, bot }: Props) {
|
||||
const name = bot.name || "This bot";
|
||||
const is_coordinator = bot.kind == "coordinator";
|
||||
const has_own = Boolean(bot.workspace_system_prompt || bot.workspace_memory);
|
||||
|
||||
return (
|
||||
<SettingsListRow
|
||||
avatar={
|
||||
<BotAvatar
|
||||
image_media_id={bot.image_media_id}
|
||||
hue={(bot.hue || null) as BotHue | null}
|
||||
face={(bot.face || "idle") as BotFace}
|
||||
/>
|
||||
}
|
||||
title={name}
|
||||
href={`/admin/workspaces/${workspace_id}/bots/${bot.id}`}
|
||||
badge={is_coordinator ? "Coordinator" : undefined}
|
||||
subtitle={
|
||||
has_own
|
||||
? "Has instructions for this workspace"
|
||||
: bot.role || "Uses its own instructions here"
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import ComputerIconTile from "@/src/components/general/computer-icon-tile";
|
||||
import SettingsListRow from "@/src/pages/admin/settings/(partials)/settings-list-row";
|
||||
import type { ComputerWithStatus } from "@/src/types";
|
||||
import computerStatusLabel from "@/src/utils/computer-status-label";
|
||||
|
||||
type Props = {
|
||||
computer: ComputerWithStatus;
|
||||
is_default: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* A computer in the workspace dashboard: its state and what it's for.
|
||||
*/
|
||||
export default function WorkspaceDashboardComputerRow({ computer, is_default }: Props) {
|
||||
const name = computer.name || `Computer ${computer.id}`;
|
||||
|
||||
return (
|
||||
<SettingsListRow
|
||||
avatar={<ComputerIconTile />}
|
||||
title={name}
|
||||
badge={is_default ? "Default" : undefined}
|
||||
subtitle={`${computerStatusLabel({ computer })}${computer.description ? ` · ${computer.description}` : ""}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { useContext } from "react";
|
||||
import { Settings } from "lucide-react";
|
||||
import HeaderIconButton from "@/src/components/general/header-icon-button";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import SettingsShell from "@/src/pages/admin/settings/(partials)/settings-shell";
|
||||
import canManageCrew from "@/src/utils/rbac/can-manage-crew";
|
||||
import WorkspaceAboutGroup from "../(partials)/workspace-about-group";
|
||||
import WorkspaceActivityGroup from "../(partials)/workspace-activity-group";
|
||||
import WorkspaceBotsListGroup from "../(partials)/workspace-bots-list-group";
|
||||
import WorkspaceComputersListGroup from "../(partials)/workspace-computers-list-group";
|
||||
|
||||
/**
|
||||
* A workspace's dashboard: a read-only summary of its picture and context,
|
||||
* its latest activity, its bots and its computers. Everything editable
|
||||
* lives under Settings, reached from the header.
|
||||
*/
|
||||
export default function WorkspaceDashboard() {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
const workspace = pageProps?.workspace;
|
||||
|
||||
if (!workspace?.id) return null;
|
||||
|
||||
const workspace_id = Number(workspace.id);
|
||||
const can_manage = canManageCrew({ user_types: pageProps?.user_types });
|
||||
|
||||
return (
|
||||
<SettingsShell
|
||||
title={workspace.name || "Workspace"}
|
||||
description="A workspace at a glance: what it is, what's happening, its bots and computers."
|
||||
action={
|
||||
can_manage ? (
|
||||
<HeaderIconButton
|
||||
icon={Settings}
|
||||
title="Workspace settings"
|
||||
href={`/admin/settings/workspaces/${workspace_id}`}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
<WorkspaceAboutGroup workspace={workspace} />
|
||||
<WorkspaceActivityGroup
|
||||
workspace_id={workspace_id}
|
||||
activity={pageProps?.workspace_activity || []}
|
||||
/>
|
||||
<WorkspaceBotsListGroup
|
||||
workspace_id={workspace_id}
|
||||
bots={pageProps?.workspace_bots || []}
|
||||
/>
|
||||
<WorkspaceComputersListGroup
|
||||
computers={pageProps?.workspace_computers || []}
|
||||
/>
|
||||
</SettingsShell>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
import userAuth from "@/src/functions/backend/auth/user-auth";
|
||||
import grabBots from "@/src/functions/backend/db/bots/grab-bots";
|
||||
import grabUserComputers from "@/src/functions/backend/db/computers/grab-user-computers";
|
||||
import grabBotHarnessOverview from "@/src/functions/backend/db/harness/grab-bot-harness-overview";
|
||||
import grabUserWorkspaces from "@/src/functions/backend/db/workspaces/grab-user-workspaces";
|
||||
import grabWorkspaceActivity from "@/src/functions/backend/db/workspaces/grab-workspace-activity";
|
||||
import grabWorkspaceBots from "@/src/functions/backend/db/workspaces/grab-workspace-bots";
|
||||
import grabWorkspaceComputers from "@/src/functions/backend/db/workspaces/grab-workspace-computers";
|
||||
import { AdminHomePath } from "@/src/dict/admin-sections-dict";
|
||||
@@ -11,9 +9,9 @@ import type { PagePropsType, PageQueryObject } from "@/src/types";
|
||||
import type { BunextPageServerFn } from "@moduletrace/bunext/types";
|
||||
|
||||
/**
|
||||
* A workspace's page: its details, crew, computers and models, with the
|
||||
* rail showing its crew. Only super admins and admins manage it; a member
|
||||
* opens its first bot instead. Its settings live under Settings.
|
||||
* A workspace's dashboard: its picture, context, latest activity, bots and
|
||||
* computers, all read-only. Editing lives under Settings. Only super admins
|
||||
* and admins see it; a member opens its first bot instead.
|
||||
*/
|
||||
export const server: BunextPageServerFn<PagePropsType> = async (params) => {
|
||||
const query: PageQueryObject = params.query;
|
||||
@@ -61,19 +59,12 @@ export const server: BunextPageServerFn<PagePropsType> = async (params) => {
|
||||
};
|
||||
}
|
||||
|
||||
const [
|
||||
workspace_bots,
|
||||
all_bots,
|
||||
workspace_computers,
|
||||
computers,
|
||||
workspace_harness_overview,
|
||||
] = await Promise.all([
|
||||
grabWorkspaceBots({ user_id: user.id, workspace_id }),
|
||||
grabBots({ user_id: user.id }),
|
||||
grabWorkspaceComputers({ workspace_id }),
|
||||
grabUserComputers({ user_id: user.id }),
|
||||
grabBotHarnessOverview({ workspace_id }).catch(() => null),
|
||||
]);
|
||||
const [workspace_bots, workspace_computers, workspace_activity] =
|
||||
await Promise.all([
|
||||
grabWorkspaceBots({ user_id: user.id, workspace_id }),
|
||||
grabWorkspaceComputers({ workspace_id }),
|
||||
grabWorkspaceActivity({ user_id: user.id, workspace_id }),
|
||||
]);
|
||||
|
||||
return {
|
||||
props: {
|
||||
@@ -81,10 +72,8 @@ export const server: BunextPageServerFn<PagePropsType> = async (params) => {
|
||||
workspace,
|
||||
bots: workspace_bots,
|
||||
workspace_bots,
|
||||
all_bots,
|
||||
workspace_computers,
|
||||
computers,
|
||||
workspace_harness_overview,
|
||||
workspace_activity,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { PagePropsType } from "@/src/types";
|
||||
import type { BunextPageModuleMetaFn } from "@moduletrace/bunext/types";
|
||||
import { SiteData } from "@/src/data/site-data";
|
||||
import WorkspaceOverview from "@/src/pages/admin/settings/workspaces/[workspace_id]/(sections)/workspace-overview";
|
||||
import WorkspaceDashboard from "./(sections)/workspace-dashboard";
|
||||
|
||||
export default function WorkspacePage() {
|
||||
return <WorkspaceOverview show_settings_link />;
|
||||
return <WorkspaceDashboard />;
|
||||
}
|
||||
|
||||
export const meta: BunextPageModuleMetaFn = async (params) => {
|
||||
|
||||
@@ -82,6 +82,19 @@ export type WorkspaceWithActivity = BUN_SQLITE_COBBLE_WORKSPACES & {
|
||||
last_activity_at?: number | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* One recent conversation in a workspace, for its dashboard: the user's
|
||||
* session with the bot, and the bot it's with.
|
||||
*/
|
||||
export type WorkspaceActivityItem = BUN_SQLITE_COBBLE_BOT_SESSIONS & {
|
||||
message_count: number;
|
||||
last_message_text?: string | null;
|
||||
bot_name?: string | null;
|
||||
bot_image_media_id?: number | null;
|
||||
bot_hue?: string | null;
|
||||
bot_face?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* A computer with its container's state, for lists and the Add flow.
|
||||
*/
|
||||
@@ -392,6 +405,10 @@ export type PagePropsType = {
|
||||
all_bots?: BotWithLastMessage[] | null;
|
||||
workspace_computers?: ComputerWithStatus[] | null;
|
||||
computers?: ComputerWithStatus[] | null;
|
||||
/**
|
||||
* A workspace's dashboard: the user's latest sessions there.
|
||||
*/
|
||||
workspace_activity?: WorkspaceActivityItem[] | null;
|
||||
/**
|
||||
* The computer a settings page manages, with its access.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user