feat(web): remove redundant live progress card and add adaptive project vs 1:1 agent overview sidebar
This commit is contained in:
parent
f6053dd2a8
commit
be657f6e3c
@ -286,7 +286,7 @@ export type NavPillar = "heute" | "team" | "projekte";
|
|||||||
import { SortableGroup } from "../components/SortableGroup";
|
import { SortableGroup } from "../components/SortableGroup";
|
||||||
import { useRunActivity } from "../lib/run-activity";
|
import { useRunActivity } from "../lib/run-activity";
|
||||||
import { ProjectLookDialog } from "./shell/project-look-dialog";
|
import { ProjectLookDialog } from "./shell/project-look-dialog";
|
||||||
import { ProjectOverview } from "./shell/project-overview";
|
import { AgentOverview, ProjectOverview } from "./shell/project-overview";
|
||||||
import { ProjectRow } from "./shell/project-row";
|
import { ProjectRow } from "./shell/project-row";
|
||||||
|
|
||||||
export function ShellPage() {
|
export function ShellPage() {
|
||||||
@ -3188,140 +3188,9 @@ export function ShellPage() {
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="sidebar-unterhaltungen">
|
|
||||||
<div className="flex items-center justify-between pt-4 pb-2 px-2.5">
|
|
||||||
<div className="rk-shell-section-label">{t`Talk to your Oos`}</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Popover open={conversationPickerOpen} onOpenChange={setConversationPickerOpen}>
|
|
||||||
<PopoverTrigger
|
|
||||||
type="button"
|
|
||||||
aria-label={t`New conversation`}
|
|
||||||
data-testid="conversation-picker-trigger"
|
|
||||||
className="rk-touch-target rk-sidebar-muted hover:text-foreground/90 transition-colors"
|
|
||||||
>
|
|
||||||
<Plus strokeWidth={2.5} className="size-icon-md" />
|
|
||||||
</PopoverTrigger>
|
|
||||||
{conversationPickerOpen ? (
|
|
||||||
<PopoverContent
|
|
||||||
align="end"
|
|
||||||
className="w-auto gap-0 overflow-hidden p-0 data-closed:animate-none"
|
|
||||||
>
|
|
||||||
<ConversationBotPicker
|
|
||||||
bots={bots}
|
|
||||||
onCreateBot={() => {
|
|
||||||
setConversationPickerOpen(false);
|
|
||||||
setPanel("create");
|
|
||||||
}}
|
|
||||||
onOpenBot={openConversationBot}
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
|
||||||
) : null}
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{mainConversationEntry ? (
|
|
||||||
<div data-sidebar-group="main">
|
|
||||||
<MainConversationRow
|
|
||||||
name={mainConversationEntry.group.name}
|
|
||||||
botId={mainConversationEntry.bot.id}
|
|
||||||
color={mainConversationEntry.bot.color}
|
|
||||||
status={mainConversationEntry.bot.status}
|
|
||||||
unread={mainConversationEntry.group.unread}
|
|
||||||
time={formatRosterTime(mainConversationEntry.group.updatedAt)}
|
|
||||||
subtitle={rosterActivitySubtitle(
|
|
||||||
mainConversationEntry.bot.status,
|
|
||||||
cleanRosterPreview(localizedPreview(mainConversationEntry.group)),
|
|
||||||
)}
|
|
||||||
active={inGroup && groupId === mainConversationEntry.groupId}
|
|
||||||
onOpen={openMainConversation}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{conversationSidebarGroups.length === 0 && !mainConversationEntry ? (
|
|
||||||
<div className="p-4 text-center text-ui-label rk-sidebar-muted">
|
|
||||||
<p>{t`No conversations yet.`}</p>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="mt-2 text-ui-label"
|
|
||||||
onClick={() => setConversationPickerOpen(true)}
|
|
||||||
>
|
|
||||||
<Plus className="mr-1 size-icon-sm" />
|
|
||||||
{t`New conversation`}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{conversationSidebarGroups.map((group) => {
|
|
||||||
const collapsed = Boolean(group.title) && collapsedSidebarSections.has(group.key);
|
|
||||||
const groupBotIds = group.bots.flatMap((item) =>
|
|
||||||
item.kind === "bot" ? [item.chat.id] : [],
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div key={group.key} data-sidebar-group={group.key}>
|
|
||||||
{group.title ? (
|
|
||||||
<SidebarGroupHeader
|
|
||||||
variant="conversation"
|
|
||||||
group={group}
|
|
||||||
collapsed={collapsed}
|
|
||||||
activeSpaceId={bootstrapMe?.spaceId}
|
|
||||||
botSections={botSections}
|
|
||||||
spaces={spaces}
|
|
||||||
onOpenEmptySpace={(spaceId) => openSpaceChat(spaceId, "/onboarding")}
|
|
||||||
onToggle={toggleSidebarSection}
|
|
||||||
sectionMenuAnchor={sectionMenuAnchor}
|
|
||||||
onSectionMenu={setSectionMenu}
|
|
||||||
spaceMenuAnchor={spaceMenuAnchor}
|
|
||||||
onSpaceMenu={setSpaceMenu}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{!collapsed &&
|
|
||||||
group.bots
|
|
||||||
.slice(0, expandedTeamGroups[group.key] ? undefined : 4)
|
|
||||||
.map((item) => (
|
|
||||||
<TeamRosterRow
|
|
||||||
key={`${item.kind}:${item.chat.id}`}
|
|
||||||
item={item}
|
|
||||||
activeDelegation={
|
|
||||||
item.kind === "bot"
|
|
||||||
? activeDelegationsByBotId.get(item.chat.id)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
groupBotIds={groupBotIds}
|
|
||||||
draggedBotId={draggedBotId}
|
|
||||||
onDraggedBotIdChange={setDraggedBotId}
|
|
||||||
onReorder={reorderRosterBot}
|
|
||||||
selected={!inGroup && active?.id === item.chat.id}
|
|
||||||
onOpen={() => {
|
|
||||||
setActiveNavPillar("team");
|
|
||||||
openSpaceChat(item.chat.spaceId, `/app/${item.chat.id}`);
|
|
||||||
}}
|
|
||||||
activeSpaceId={bootstrapMe?.spaceId}
|
|
||||||
menuAnchor={botMenuAnchor}
|
|
||||||
onMenu={setBotMenu}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{!collapsed && group.bots.length > 4 && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
setExpandedTeamGroups((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[group.key]: !prev[group.key],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
className="mt-1 w-full flex items-center justify-center gap-1.5 rounded-lg px-2 py-1.5 text-ui-label font-medium rk-sidebar-muted hover:bg-sidebar-accent hover:text-foreground transition-colors"
|
|
||||||
>
|
|
||||||
{expandedTeamGroups[group.key]
|
|
||||||
? t`Show less`
|
|
||||||
: t`Show all ${group.bots.length}`}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<div className="sidebar-projekte">
|
<div className="sidebar-projekte">
|
||||||
{/* PROJEKTE SECTION */}
|
{/* PROJEKTE SECTION */}
|
||||||
<div className="flex items-center justify-between pt-4 pb-1 mt-3 border-t border-sidebar-border px-2.5">
|
<div className="flex items-center justify-between pt-4 pb-1 px-2.5">
|
||||||
<div>
|
<div>
|
||||||
<div className="rk-shell-section-label">{t`Projects`}</div>
|
<div className="rk-shell-section-label">{t`Projects`}</div>
|
||||||
<p className="mt-0.5 px-0.5 text-ui-caption rk-sidebar-muted">
|
<p className="mt-0.5 px-0.5 text-ui-caption rk-sidebar-muted">
|
||||||
@ -3497,6 +3366,137 @@ export function ShellPage() {
|
|||||||
</SortableContext>
|
</SortableContext>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="sidebar-unterhaltungen">
|
||||||
|
<div className="flex items-center justify-between pt-4 pb-2 mt-3 border-t border-sidebar-border px-2.5">
|
||||||
|
<div className="rk-shell-section-label">{t`Talk to your Oos`}</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Popover open={conversationPickerOpen} onOpenChange={setConversationPickerOpen}>
|
||||||
|
<PopoverTrigger
|
||||||
|
type="button"
|
||||||
|
aria-label={t`New conversation`}
|
||||||
|
data-testid="conversation-picker-trigger"
|
||||||
|
className="rk-touch-target rk-sidebar-muted hover:text-foreground/90 transition-colors"
|
||||||
|
>
|
||||||
|
<Plus strokeWidth={2.5} className="size-icon-md" />
|
||||||
|
</PopoverTrigger>
|
||||||
|
{conversationPickerOpen ? (
|
||||||
|
<PopoverContent
|
||||||
|
align="end"
|
||||||
|
className="w-auto gap-0 overflow-hidden p-0 data-closed:animate-none"
|
||||||
|
>
|
||||||
|
<ConversationBotPicker
|
||||||
|
bots={bots}
|
||||||
|
onCreateBot={() => {
|
||||||
|
setConversationPickerOpen(false);
|
||||||
|
setPanel("create");
|
||||||
|
}}
|
||||||
|
onOpenBot={openConversationBot}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
) : null}
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{mainConversationEntry ? (
|
||||||
|
<div data-sidebar-group="main">
|
||||||
|
<MainConversationRow
|
||||||
|
name={mainConversationEntry.group.name}
|
||||||
|
botId={mainConversationEntry.bot.id}
|
||||||
|
color={mainConversationEntry.bot.color}
|
||||||
|
status={mainConversationEntry.bot.status}
|
||||||
|
unread={mainConversationEntry.group.unread}
|
||||||
|
time={formatRosterTime(mainConversationEntry.group.updatedAt)}
|
||||||
|
subtitle={rosterActivitySubtitle(
|
||||||
|
mainConversationEntry.bot.status,
|
||||||
|
cleanRosterPreview(localizedPreview(mainConversationEntry.group)),
|
||||||
|
)}
|
||||||
|
active={inGroup && groupId === mainConversationEntry.groupId}
|
||||||
|
onOpen={openMainConversation}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{conversationSidebarGroups.length === 0 && !mainConversationEntry ? (
|
||||||
|
<div className="p-4 text-center text-ui-label rk-sidebar-muted">
|
||||||
|
<p>{t`No conversations yet.`}</p>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="mt-2 text-ui-label"
|
||||||
|
onClick={() => setConversationPickerOpen(true)}
|
||||||
|
>
|
||||||
|
<Plus className="mr-1 size-icon-sm" />
|
||||||
|
{t`New conversation`}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{conversationSidebarGroups.map((group) => {
|
||||||
|
const collapsed = Boolean(group.title) && collapsedSidebarSections.has(group.key);
|
||||||
|
const groupBotIds = group.bots.flatMap((item) =>
|
||||||
|
item.kind === "bot" ? [item.chat.id] : [],
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div key={group.key} data-sidebar-group={group.key}>
|
||||||
|
{group.title ? (
|
||||||
|
<SidebarGroupHeader
|
||||||
|
variant="conversation"
|
||||||
|
group={group}
|
||||||
|
collapsed={collapsed}
|
||||||
|
activeSpaceId={bootstrapMe?.spaceId}
|
||||||
|
botSections={botSections}
|
||||||
|
spaces={spaces}
|
||||||
|
onOpenEmptySpace={(spaceId) => openSpaceChat(spaceId, "/onboarding")}
|
||||||
|
onToggle={toggleSidebarSection}
|
||||||
|
sectionMenuAnchor={sectionMenuAnchor}
|
||||||
|
onSectionMenu={setSectionMenu}
|
||||||
|
spaceMenuAnchor={spaceMenuAnchor}
|
||||||
|
onSpaceMenu={setSpaceMenu}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{!collapsed &&
|
||||||
|
group.bots
|
||||||
|
.slice(0, expandedTeamGroups[group.key] ? undefined : 4)
|
||||||
|
.map((item) => (
|
||||||
|
<TeamRosterRow
|
||||||
|
key={`${item.kind}:${item.chat.id}`}
|
||||||
|
item={item}
|
||||||
|
activeDelegation={
|
||||||
|
item.kind === "bot"
|
||||||
|
? activeDelegationsByBotId.get(item.chat.id)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
groupBotIds={groupBotIds}
|
||||||
|
draggedBotId={draggedBotId}
|
||||||
|
onDraggedBotIdChange={setDraggedBotId}
|
||||||
|
onReorder={reorderRosterBot}
|
||||||
|
selected={!inGroup && active?.id === item.chat.id}
|
||||||
|
onOpen={() => {
|
||||||
|
setActiveNavPillar("team");
|
||||||
|
openSpaceChat(item.chat.spaceId, `/app/${item.chat.id}`);
|
||||||
|
}}
|
||||||
|
activeSpaceId={bootstrapMe?.spaceId}
|
||||||
|
menuAnchor={botMenuAnchor}
|
||||||
|
onMenu={setBotMenu}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{!collapsed && group.bots.length > 4 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setExpandedTeamGroups((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[group.key]: !prev[group.key],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
className="mt-1 w-full flex items-center justify-center gap-1.5 rounded-lg px-2 py-1.5 text-ui-label font-medium rk-sidebar-muted hover:bg-sidebar-accent hover:text-foreground transition-colors"
|
||||||
|
>
|
||||||
|
{expandedTeamGroups[group.key]
|
||||||
|
? t`Show less`
|
||||||
|
: t`Show all ${group.bots.length}`}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -4183,17 +4183,17 @@ export function ShellPage() {
|
|||||||
setCanvasTab(tab);
|
setCanvasTab(tab);
|
||||||
if (tab === "browser") {
|
if (tab === "browser") {
|
||||||
setPanel("browser");
|
setPanel("browser");
|
||||||
} else if (tab === "sandbox") {
|
} else if (inProject && !dualSurface.isMobile && tab !== "sandbox") {
|
||||||
setPanel("computer");
|
|
||||||
} else {
|
|
||||||
setPanel(null);
|
setPanel(null);
|
||||||
|
} else {
|
||||||
|
setPanel("computer");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
inProject={inProject}
|
inProject={inProject}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{inProject && !panel && effectiveTab === "overview" && activeSpace ? (
|
{inProject && panel !== "settings" && effectiveTab === "overview" && activeSpace ? (
|
||||||
<div className="rk-scroll h-full w-full overflow-y-auto px-4 py-4">
|
<div className="rk-scroll h-full w-full overflow-y-auto px-4 py-4">
|
||||||
<ProjectOverview
|
<ProjectOverview
|
||||||
spaceId={activeSpace.id}
|
spaceId={activeSpace.id}
|
||||||
@ -4212,12 +4212,59 @@ export function ShellPage() {
|
|||||||
run.groupId ? `/app/g/${run.groupId}` : `/app/${run.botId}`,
|
run.groupId ? `/app/g/${run.groupId}` : `/app/${run.botId}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
onOpenBot={openBot}
|
||||||
onOpenNewInquiry={() => setNewInquiryOpen(true)}
|
onOpenNewInquiry={() => setNewInquiryOpen(true)}
|
||||||
onEditProject={() => {
|
onEditProject={() => {
|
||||||
setLookTarget(activeSpace);
|
setLookTarget(activeSpace);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
) : !inProject &&
|
||||||
|
panel !== "settings" &&
|
||||||
|
effectiveTab === "overview" &&
|
||||||
|
(active ?? coordinatorBot) ? (
|
||||||
|
<div className="rk-scroll h-full w-full overflow-y-auto px-4 py-4">
|
||||||
|
<AgentOverview
|
||||||
|
bot={(active ?? coordinatorBot)!}
|
||||||
|
running={transcriptRunning}
|
||||||
|
routines={activeRoutines}
|
||||||
|
runningRoutineId={
|
||||||
|
snapshot?.run && isActive(snapshot.run.status)
|
||||||
|
? (snapshot.run.routineId ?? null)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
togglingRoutineId={routineActiveToggleId}
|
||||||
|
onToggleRoutineActive={(routine, next) =>
|
||||||
|
void setRoutineActive(routine, next)
|
||||||
|
}
|
||||||
|
onOpenRoutine={(routine) => {
|
||||||
|
setRoutineDraft(draftFromRoutine(routine));
|
||||||
|
setRoutineWebhookSecret(null);
|
||||||
|
setEditingRoutine(routine);
|
||||||
|
setRoutineError(null);
|
||||||
|
setPanel("routine");
|
||||||
|
}}
|
||||||
|
onAddRoutine={
|
||||||
|
active
|
||||||
|
? () => {
|
||||||
|
setRoutineDraft(emptyRoutineDraft());
|
||||||
|
setRoutineWebhookSecret(null);
|
||||||
|
setEditingRoutine(null);
|
||||||
|
setRoutineError(null);
|
||||||
|
setPanel("routine");
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onStopRun={() => void stopRun()}
|
||||||
|
onOpenRun={(run) =>
|
||||||
|
openSpaceChat(
|
||||||
|
run.spaceId,
|
||||||
|
run.groupId ? `/app/g/${run.groupId}` : `/app/${run.botId}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onOpenSettings={() => setPanel("settings")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
) : panel === "browser" || (effectiveTab === "browser" && panel !== "settings") ? (
|
) : panel === "browser" || (effectiveTab === "browser" && panel !== "settings") ? (
|
||||||
<div className="flex-1 min-h-0 flex flex-col">
|
<div className="flex-1 min-h-0 flex flex-col">
|
||||||
<InAppBrowser
|
<InAppBrowser
|
||||||
|
|||||||
@ -1,25 +1,29 @@
|
|||||||
import { Trans, useLingui } from "@lingui/react/macro";
|
import { Trans, useLingui } from "@lingui/react/macro";
|
||||||
import type { RunActivityRow, Space } from "@rakazo/contracts";
|
import type { Bot, Routine, RunActivityRow, Space } from "@rakazo/contracts";
|
||||||
import { projectThreads } from "@rakazo/core";
|
import { projectThreads } from "@rakazo/core";
|
||||||
import { Button } from "@rakazo/ui-web";
|
import { BotAvatar, Button, cn } from "@rakazo/ui-web";
|
||||||
import { FolderOpen, GitBranch, Pencil, Plus, Target } from "lucide-react";
|
import { Clock, FolderOpen, GitBranch, Pencil, Plus, Settings, Target } from "lucide-react";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { ProjectIcon } from "../../components/project-icon";
|
import { ProjectIcon } from "../../components/project-icon";
|
||||||
|
import { isNeedsYouStatus } from "../../lib/agent-activity";
|
||||||
import { useRunActivity } from "../../lib/run-activity";
|
import { useRunActivity } from "../../lib/run-activity";
|
||||||
import { ActivityRow } from "../ActivityList";
|
import { ActivityRow } from "../ActivityList";
|
||||||
|
import { RoutineListRow } from "../RoutineEditor";
|
||||||
|
import { FALLBACK_BOT_COLOR } from "./shell-types";
|
||||||
|
|
||||||
type Section = "needsYou" | "running" | "done";
|
type Section = "needsYou" | "running" | "done";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The coordinator's view of its project: project identity, mission goal, context badges,
|
* The coordinator's view of its project: project identity, mission goal, context badges,
|
||||||
* sub-thread creation launcher, and delegated task activity.
|
* project specialist roster, sub-thread creation launcher, and delegated task activity.
|
||||||
*/
|
*/
|
||||||
export function ProjectOverview({
|
export function ProjectOverview({
|
||||||
spaceId,
|
spaceId,
|
||||||
space,
|
space,
|
||||||
coordinator,
|
coordinator,
|
||||||
onOpenRun,
|
onOpenRun,
|
||||||
|
onOpenBot,
|
||||||
onOpenNewInquiry,
|
onOpenNewInquiry,
|
||||||
onEditProject,
|
onEditProject,
|
||||||
}: {
|
}: {
|
||||||
@ -27,6 +31,7 @@ export function ProjectOverview({
|
|||||||
space?: Space;
|
space?: Space;
|
||||||
coordinator?: { threadId: string; botId?: string };
|
coordinator?: { threadId: string; botId?: string };
|
||||||
onOpenRun: (run: RunActivityRow) => void;
|
onOpenRun: (run: RunActivityRow) => void;
|
||||||
|
onOpenBot?: (botId: string) => void;
|
||||||
onOpenNewInquiry?: () => void;
|
onOpenNewInquiry?: () => void;
|
||||||
onEditProject?: () => void;
|
onEditProject?: () => void;
|
||||||
}) {
|
}) {
|
||||||
@ -56,13 +61,16 @@ export function ProjectOverview({
|
|||||||
// If not a project space and no runs, keep hidden
|
// If not a project space and no runs, keep hidden
|
||||||
if (!isProjectSpace && visible.length === 0) return null;
|
if (!isProjectSpace && visible.length === 0) return null;
|
||||||
|
|
||||||
// Waiting threads are what the user came for, so they start open.
|
// Waiting or actively running threads start open automatically so progress is visible right away.
|
||||||
const shown = open ?? (threads.needsYou.length > 0 ? "needsYou" : null);
|
const shown =
|
||||||
|
open ??
|
||||||
|
(threads.needsYou.length > 0 ? "needsYou" : threads.running.length > 0 ? "running" : null);
|
||||||
const current = visible.find((section) => section.id === shown);
|
const current = visible.find((section) => section.id === shown);
|
||||||
|
|
||||||
const folderPath = space?.workspace?.path;
|
const folderPath = space?.workspace?.path;
|
||||||
const gitRemote = space?.workspace?.git?.remoteUrl;
|
const gitRemote = space?.workspace?.git?.remoteUrl;
|
||||||
const goal = space?.goal;
|
const goal = space?.goal;
|
||||||
|
const projectBots = space?.bots ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -110,6 +118,17 @@ export function ProjectOverview({
|
|||||||
{goal}
|
{goal}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : onEditProject ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onEditProject}
|
||||||
|
className="flex w-full items-center gap-2 rounded-lg border border-dashed border-border/80 bg-background/40 px-3 py-2 text-start text-ui-body-sm text-muted-foreground hover:border-border hover:bg-accent/30 hover:text-foreground transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
<Target size={15} strokeWidth={1.8} className="shrink-0" />
|
||||||
|
<span>
|
||||||
|
<Trans>Add goal</Trans>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{folderPath || gitRemote ? (
|
{folderPath || gitRemote ? (
|
||||||
@ -139,6 +158,55 @@ export function ProjectOverview({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{projectBots.length > 0 ? (
|
||||||
|
<div className="border-t border-border/50 pt-2.5">
|
||||||
|
<div className="mb-1.5 text-ui-label font-medium text-muted-foreground">
|
||||||
|
<Trans>Team</Trans>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{projectBots.map((member) => {
|
||||||
|
const displayName =
|
||||||
|
member.name === "Chief" || member.name === "Projektkoordinator"
|
||||||
|
? "Projektmanager"
|
||||||
|
: member.name;
|
||||||
|
const isWorking = Boolean(
|
||||||
|
member.status &&
|
||||||
|
member.status !== "idle" &&
|
||||||
|
member.status !== "ready" &&
|
||||||
|
member.status !== "done",
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={member.id}
|
||||||
|
type="button"
|
||||||
|
disabled={!onOpenBot}
|
||||||
|
onClick={() => onOpenBot?.(member.id)}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center gap-1.5 rounded-full border border-border/70 bg-background/70 px-2.5 py-1 text-ui-label text-foreground transition-colors",
|
||||||
|
onOpenBot && "hover:bg-accent cursor-pointer",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<BotAvatar
|
||||||
|
color={member.color || FALLBACK_BOT_COLOR}
|
||||||
|
identity={member.id}
|
||||||
|
size={16}
|
||||||
|
moment={isWorking ? "working" : "idle"}
|
||||||
|
/>
|
||||||
|
<span className="truncate max-w-[130px] font-medium">{displayName}</span>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn(
|
||||||
|
"size-1.5 rounded-full",
|
||||||
|
isWorking ? "bg-warning animate-pulse" : "bg-success",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
@ -168,7 +236,205 @@ export function ProjectOverview({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
) : isProjectSpace ? (
|
||||||
|
<div className="rounded-xl border border-border/60 bg-card/30 px-3.5 py-3 text-ui-body-sm text-muted-foreground">
|
||||||
|
<Trans>No runs yet</Trans>
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1:1 Agent overview in the side panel: agent identity & status, active and recent
|
||||||
|
* task runs for this specific agent, and its scheduled routines.
|
||||||
|
*/
|
||||||
|
export function AgentOverview({
|
||||||
|
bot,
|
||||||
|
running = false,
|
||||||
|
routines = [],
|
||||||
|
runningRoutineId,
|
||||||
|
togglingRoutineId,
|
||||||
|
onToggleRoutineActive,
|
||||||
|
onOpenRoutine,
|
||||||
|
onAddRoutine,
|
||||||
|
onStopRun,
|
||||||
|
onOpenRun,
|
||||||
|
onOpenSettings,
|
||||||
|
}: {
|
||||||
|
bot: Bot;
|
||||||
|
running?: boolean;
|
||||||
|
routines?: readonly Routine[];
|
||||||
|
runningRoutineId?: string | null;
|
||||||
|
togglingRoutineId?: string | null;
|
||||||
|
onToggleRoutineActive?: (routine: Routine, active: boolean) => void;
|
||||||
|
onOpenRoutine?: (routine: Routine) => void;
|
||||||
|
onAddRoutine?: () => void;
|
||||||
|
onStopRun?: () => void;
|
||||||
|
onOpenRun: (run: RunActivityRow) => void;
|
||||||
|
onOpenSettings?: () => void;
|
||||||
|
}) {
|
||||||
|
const { t } = useLingui();
|
||||||
|
const { active: allActiveRuns, recent: allRecentRuns } = useRunActivity();
|
||||||
|
|
||||||
|
const botActiveRuns = allActiveRuns.filter((run) => run.botId === bot.id);
|
||||||
|
const botRecentRuns = allRecentRuns.filter((run) => run.botId === bot.id);
|
||||||
|
const needsYouCount = botActiveRuns.filter((run) => isNeedsYouStatus(run.status)).length;
|
||||||
|
const isWorking = running || botActiveRuns.length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-testid="agent-overview" className="mx-auto w-full max-w-3xl px-4 pt-3 pb-4 space-y-4">
|
||||||
|
{/* Agent Identity & Status Card */}
|
||||||
|
<div className="rounded-xl border border-border bg-card/60 p-3.5 backdrop-blur-xs shadow-2xs space-y-2.5">
|
||||||
|
<div className="flex items-start justify-between gap-3">
|
||||||
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
|
<div className="relative shrink-0">
|
||||||
|
<BotAvatar
|
||||||
|
color={bot.color || FALLBACK_BOT_COLOR}
|
||||||
|
identity={bot.id}
|
||||||
|
size={34}
|
||||||
|
moment={isWorking ? "working" : "idle"}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn(
|
||||||
|
"absolute -bottom-0.5 -right-0.5 size-2.5 rounded-full ring-2 ring-background",
|
||||||
|
needsYouCount > 0
|
||||||
|
? "bg-warning"
|
||||||
|
: isWorking
|
||||||
|
? "bg-warning animate-pulse"
|
||||||
|
: "bg-success",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
<span className="font-semibold text-ui-title-sm truncate text-foreground">
|
||||||
|
{bot.name}
|
||||||
|
</span>
|
||||||
|
{bot.title ? (
|
||||||
|
<span className="rounded-full border border-border/60 bg-muted/50 px-2 py-0.5 text-ui-label font-medium text-muted-foreground">
|
||||||
|
{bot.title}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="mt-0.5 flex items-center gap-1.5 text-ui-label text-muted-foreground">
|
||||||
|
{needsYouCount > 0 ? (
|
||||||
|
<span className="text-warning font-medium">
|
||||||
|
<Trans>Needs you</Trans>
|
||||||
|
</span>
|
||||||
|
) : isWorking ? (
|
||||||
|
<span className="text-foreground font-medium">
|
||||||
|
<Trans>Working</Trans>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
<Trans>Ready</Trans>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{onOpenSettings ? (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon-sm"
|
||||||
|
aria-label={t`Show settings`}
|
||||||
|
onClick={onOpenSettings}
|
||||||
|
className="size-7 text-muted-foreground hover:text-foreground shrink-0"
|
||||||
|
>
|
||||||
|
<Settings size={15} strokeWidth={1.8} />
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{bot.description ? (
|
||||||
|
<p className="text-ui-body-sm text-muted-foreground leading-relaxed line-clamp-3">
|
||||||
|
{bot.description}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Agent Runs & Tasks */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between px-1">
|
||||||
|
<span className="text-ui-label font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
|
<Trans>Run history</Trans>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{botActiveRuns.length > 0 ? (
|
||||||
|
<div className="rounded-xl border border-border/70 bg-card/40 p-1.5 space-y-1">
|
||||||
|
<div className="px-2 pt-1 text-ui-label font-medium text-warning">
|
||||||
|
<Trans>Now</Trans>
|
||||||
|
</div>
|
||||||
|
{botActiveRuns.map((run) => (
|
||||||
|
<ActivityRow key={run.runId} run={run} onOpen={() => onOpenRun(run)} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{botRecentRuns.length > 0 ? (
|
||||||
|
<div className="rounded-xl border border-border/60 bg-card/30 p-1.5 space-y-1">
|
||||||
|
{botActiveRuns.length > 0 ? (
|
||||||
|
<div className="px-2 pt-1 text-ui-label font-medium text-muted-foreground">
|
||||||
|
<Trans>Recent</Trans>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{botRecentRuns.slice(0, 8).map((run) => (
|
||||||
|
<ActivityRow key={run.runId} run={run} onOpen={() => onOpenRun(run)} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : botActiveRuns.length === 0 ? (
|
||||||
|
<div className="rounded-xl border border-border/60 bg-card/30 px-3.5 py-3 text-ui-body-sm text-muted-foreground">
|
||||||
|
<Trans>No runs yet</Trans>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Scheduled Routines for this 1:1 Agent */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between px-1">
|
||||||
|
<span className="text-ui-label font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
|
<Trans>Routines</Trans>
|
||||||
|
</span>
|
||||||
|
{onAddRoutine ? (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="xs"
|
||||||
|
onClick={onAddRoutine}
|
||||||
|
className="gap-1 text-ui-label"
|
||||||
|
>
|
||||||
|
<Plus size={13} strokeWidth={2.2} />
|
||||||
|
<Trans>Routine</Trans>
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{routines.length > 0 ? (
|
||||||
|
<div className="rounded-xl border border-border/60 bg-card/30 p-1.5 space-y-0.5">
|
||||||
|
{routines.map((routine) => (
|
||||||
|
<RoutineListRow
|
||||||
|
key={routine.id}
|
||||||
|
routine={routine}
|
||||||
|
running={runningRoutineId === routine.id}
|
||||||
|
togglingActive={togglingRoutineId === routine.id}
|
||||||
|
onActiveChange={(next) => onToggleRoutineActive?.(routine, next)}
|
||||||
|
onOpen={() => onOpenRoutine?.(routine)}
|
||||||
|
onStop={() => onStopRun?.()}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-2 rounded-xl border border-border/60 bg-card/30 px-3.5 py-2.5 text-ui-body-sm text-muted-foreground">
|
||||||
|
<Clock size={14} className="shrink-0" />
|
||||||
|
<span>
|
||||||
|
<Trans>On a schedule</Trans>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -42,8 +42,10 @@ export function SidePanelHeader({
|
|||||||
<span className="text-ui-body font-semibold text-foreground tracking-tight">
|
<span className="text-ui-body font-semibold text-foreground tracking-tight">
|
||||||
{panel === "settings" ? (
|
{panel === "settings" ? (
|
||||||
<Trans>Settings</Trans>
|
<Trans>Settings</Trans>
|
||||||
) : inProject && (!panel || activeTab === "overview") ? (
|
) : inProject && activeTab === "overview" ? (
|
||||||
<Trans>Project overview</Trans>
|
<Trans>Project overview</Trans>
|
||||||
|
) : !inProject && activeTab === "overview" ? (
|
||||||
|
<Trans>Overview</Trans>
|
||||||
) : (
|
) : (
|
||||||
<Trans>Living Canvas</Trans>
|
<Trans>Living Canvas</Trans>
|
||||||
)}
|
)}
|
||||||
@ -112,17 +114,16 @@ export function SidePanelHeader({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Surface Switcher Tabs */}
|
{/* Surface Switcher Tabs */}
|
||||||
{active && panel !== "settings" && onSelectTab ? (
|
{active && onSelectTab ? (
|
||||||
<div className="flex items-center gap-1 border-t border-border/40 pt-2">
|
<div className="flex items-center gap-1 border-t border-border/40 pt-2">
|
||||||
{inProject ? (
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-tab="overview"
|
data-tab="overview"
|
||||||
data-active={activeTab === "overview" ? "true" : undefined}
|
data-active={panel !== "settings" && activeTab === "overview" ? "true" : undefined}
|
||||||
onClick={() => onSelectTab("overview")}
|
onClick={() => onSelectTab("overview")}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-ui-body-sm font-medium transition-colors cursor-pointer",
|
"inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-ui-body-sm font-medium transition-colors cursor-pointer",
|
||||||
activeTab === "overview"
|
panel !== "settings" && activeTab === "overview"
|
||||||
? "bg-accent text-accent-foreground font-semibold shadow-2xs"
|
? "bg-accent text-accent-foreground font-semibold shadow-2xs"
|
||||||
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
@ -130,15 +131,14 @@ export function SidePanelHeader({
|
|||||||
<Target className="size-3.5" />
|
<Target className="size-3.5" />
|
||||||
<Trans>Overview</Trans>
|
<Trans>Overview</Trans>
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-tab="files"
|
data-tab="files"
|
||||||
data-active={activeTab === "files" ? "true" : undefined}
|
data-active={panel !== "settings" && activeTab === "files" ? "true" : undefined}
|
||||||
onClick={() => onSelectTab("files")}
|
onClick={() => onSelectTab("files")}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-ui-body-sm font-medium transition-colors cursor-pointer",
|
"inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-ui-body-sm font-medium transition-colors cursor-pointer",
|
||||||
activeTab === "files"
|
panel !== "settings" && activeTab === "files"
|
||||||
? "bg-accent text-accent-foreground font-semibold shadow-2xs"
|
? "bg-accent text-accent-foreground font-semibold shadow-2xs"
|
||||||
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
@ -149,11 +149,11 @@ export function SidePanelHeader({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-tab="sandbox"
|
data-tab="sandbox"
|
||||||
data-active={activeTab === "sandbox" ? "true" : undefined}
|
data-active={panel !== "settings" && activeTab === "sandbox" ? "true" : undefined}
|
||||||
onClick={() => onSelectTab("sandbox")}
|
onClick={() => onSelectTab("sandbox")}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-ui-body-sm font-medium transition-colors cursor-pointer",
|
"inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-ui-body-sm font-medium transition-colors cursor-pointer",
|
||||||
activeTab === "sandbox"
|
panel !== "settings" && activeTab === "sandbox"
|
||||||
? "bg-accent text-accent-foreground font-semibold shadow-2xs"
|
? "bg-accent text-accent-foreground font-semibold shadow-2xs"
|
||||||
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
@ -164,11 +164,11 @@ export function SidePanelHeader({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-tab="browser"
|
data-tab="browser"
|
||||||
data-active={activeTab === "browser" ? "true" : undefined}
|
data-active={panel !== "settings" && activeTab === "browser" ? "true" : undefined}
|
||||||
onClick={() => onSelectTab("browser")}
|
onClick={() => onSelectTab("browser")}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-ui-body-sm font-medium transition-colors cursor-pointer",
|
"inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-ui-body-sm font-medium transition-colors cursor-pointer",
|
||||||
activeTab === "browser"
|
panel !== "settings" && activeTab === "browser"
|
||||||
? "bg-accent text-accent-foreground font-semibold shadow-2xs"
|
? "bg-accent text-accent-foreground font-semibold shadow-2xs"
|
||||||
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -39,7 +39,6 @@ import { MediaCard } from "../../components/media/MediaCard";
|
|||||||
import { MediaPlayer } from "../../components/media/MediaPlayer";
|
import { MediaPlayer } from "../../components/media/MediaPlayer";
|
||||||
import { ThreadCard } from "../../components/ThreadCard";
|
import { ThreadCard } from "../../components/ThreadCard";
|
||||||
import { SkillDraftCard } from "../../components/teach/SkillDraftCard";
|
import { SkillDraftCard } from "../../components/teach/SkillDraftCard";
|
||||||
import { rosterDoingVerb } from "../../lib/agent-activity";
|
|
||||||
import type { ArtifactTarget } from "../../lib/artifact-open";
|
import type { ArtifactTarget } from "../../lib/artifact-open";
|
||||||
import { copyableMessageText } from "../../lib/message-text";
|
import { copyableMessageText } from "../../lib/message-text";
|
||||||
import { messageProviderLabel } from "../../lib/messaging";
|
import { messageProviderLabel } from "../../lib/messaging";
|
||||||
@ -58,7 +57,7 @@ import {
|
|||||||
} from "./message-cards";
|
} from "./message-cards";
|
||||||
import { previewMessageText } from "./message-preview";
|
import { previewMessageText } from "./message-preview";
|
||||||
import { RoutineEventLine } from "./routine-event-line";
|
import { RoutineEventLine } from "./routine-event-line";
|
||||||
import { JobProgressCard, RunSteps } from "./run-steps";
|
import { RunSteps } from "./run-steps";
|
||||||
import { FALLBACK_BOT_COLOR } from "./shell-types";
|
import { FALLBACK_BOT_COLOR } from "./shell-types";
|
||||||
import { isInterruptedStepMessage } from "./transcript-status";
|
import { isInterruptedStepMessage } from "./transcript-status";
|
||||||
|
|
||||||
@ -202,36 +201,6 @@ export const Transcript = memo(function Transcript({
|
|||||||
[reactionView.visibleMessages],
|
[reactionView.visibleMessages],
|
||||||
);
|
);
|
||||||
const transcriptSubordinate = encounterMode && voiceEncounterActive;
|
const transcriptSubordinate = encounterMode && voiceEncounterActive;
|
||||||
const workingBotName = workingBots.length === 1 ? workingBots[0]?.name : undefined;
|
|
||||||
const primaryRunStatus = workingBots[0]?.status;
|
|
||||||
const statusVerb = rosterDoingVerb(primaryRunStatus);
|
|
||||||
const workingLabel =
|
|
||||||
statusVerb ??
|
|
||||||
(workingBotName != null && workingBotName !== ""
|
|
||||||
? t`${workingBotName} is working`
|
|
||||||
: t`Bots are working`);
|
|
||||||
const lastUserTask = useMemo(() => {
|
|
||||||
let fallback: string | null = null;
|
|
||||||
for (let i = messages.length - 1; i >= 0; i--) {
|
|
||||||
const m = messages[i];
|
|
||||||
if (m?.role === "user") {
|
|
||||||
const text = m.blocks
|
|
||||||
.filter((b): b is { kind: "text"; text: string } => b.kind === "text")
|
|
||||||
.map((b) => b.text)
|
|
||||||
.join(" ")
|
|
||||||
.trim();
|
|
||||||
if (!text) continue;
|
|
||||||
if (!fallback) fallback = text;
|
|
||||||
const isPunctuationOrFiller =
|
|
||||||
/^[?!.,:;]+$/.test(text) ||
|
|
||||||
(text.length <= 4 && /^(ok|ja|nein|yes|no|hi|hey)$/i.test(text));
|
|
||||||
if (!isPunctuationOrFiller) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fallback;
|
|
||||||
}, [messages]);
|
|
||||||
const [quoteDraft, setQuoteDraft] = useState<{
|
const [quoteDraft, setQuoteDraft] = useState<{
|
||||||
message: ThreadMessage;
|
message: ThreadMessage;
|
||||||
text: string;
|
text: string;
|
||||||
@ -634,17 +603,6 @@ export const Transcript = memo(function Transcript({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{(running || messages.some((m) => m.id.startsWith("progress:"))) && !encounterMode ? (
|
|
||||||
<div className="mt-2.5">
|
|
||||||
<JobProgressCard
|
|
||||||
bot={workingBots[0]}
|
|
||||||
taskTitle={lastUserTask}
|
|
||||||
running={true}
|
|
||||||
status="running"
|
|
||||||
activeAction={workingLabel}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{quoteDraft ? (
|
{quoteDraft ? (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user