代码修改
This commit is contained in:
1 parent
559340143e
commit
e03ca7b4bd
8 files changed
+249
-85
No files matched your search
@@ -21,6 +21,7 @@ import {
|
||||
Grid3X3,
|
||||
HelpCircle,
|
||||
History,
|
||||
Layers,
|
||||
Layout,
|
||||
Library,
|
||||
List,
|
||||
@@ -209,10 +210,16 @@ export const sidebarMenuItems: MenuItem[] = [
|
||||
{ id: "mgmt-agents", label: "智能体管理", icon: Bot, path: "/page/strategy/agents" },
|
||||
{
|
||||
id: "mgmt-business-chain-mapping",
|
||||
label: "业务链条管理",
|
||||
label: "配置管理",
|
||||
icon: Network,
|
||||
path: "/page/strategy/business-chain-mapping",
|
||||
},
|
||||
{
|
||||
id: "mgmt-business-chains",
|
||||
label: "业务链条配置",
|
||||
icon: Layers,
|
||||
path: "/page/strategy/business-chains",
|
||||
},
|
||||
{ id: "mgmt-skills", label: "技能管理", icon: Sparkles, path: "/page/strategy/skills" },
|
||||
{ id: "mgmt-memory", label: "记忆管理", icon: Brain, path: "/page/strategy/memory" },
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 业务链条管理页 —— 把固定业务码(3Q/6BF/7BF/8BF)绑定到「智能体」+「业务链」。
|
||||
* 配置管理页 —— 把固定业务码(3Q/6BF/7BF/8BF)绑定到「智能体」+「业务链」。
|
||||
*
|
||||
* 表格三列:业务(只读)/ 智能体 / 业务链。编辑模式下后两列为可搜索下拉:
|
||||
* - 智能体来自 `/api/agents`
|
||||
@@ -129,7 +129,7 @@ export default function BusinessChainMappingPage() {
|
||||
<div className="mx-auto flex min-h-full max-w-[1280px] flex-col gap-5 px-6 py-6 lg:px-8">
|
||||
<div className="space-y-1">
|
||||
<h1 className={`text-[26px] font-semibold tracking-[0.02em] ${isDark ? "text-slate-50" : "text-[#222222]"}`}>
|
||||
业务链条管理
|
||||
配置管理
|
||||
</h1>
|
||||
<p className={`text-sm ${isDark ? "text-slate-300/80" : "text-slate-500"}`}>
|
||||
为每个业务绑定智能体与业务链。多智能体会商从对应业务自动取用此处配置的业务链。业务链仅可选择「公共」链条。
|
||||
|
||||
@@ -17,6 +17,8 @@ import { ActionAnalyzes } from "@/strategy-components/components/task-components
|
||||
import KnowledgeBaseResourceManagement from "@/strategy-components/components/resource-management/KnowledgeBaseResourceManagement";
|
||||
import NetworkActorResourceManagement from "@/strategy-components/components/resource-management/NetworkActorResourceManagement";
|
||||
import RoundtablePlanningPage from "@/roundtable-planning/pages/RoundtablePlanningPage";
|
||||
import BusinessChainListPage from "@/roundtable-planning/pages/BusinessChainListPage";
|
||||
import BusinessChainEditorPage from "@/roundtable-planning/pages/BusinessChainEditorPage";
|
||||
import "@/roundtable-planning/styles/roundtable-planning.css";
|
||||
import AgentChatPage from "./AgentChatPage";
|
||||
import AgentsPage from "./AgentsPage";
|
||||
@@ -174,6 +176,8 @@ export default function StrategyRoutes() {
|
||||
<Route path="skills/evolution" element={<SkillEvolutionPage />} />
|
||||
<Route path="memory" element={<MemoryPage />} />
|
||||
<Route path="business-chain-mapping" element={<BusinessChainMappingPage />} />
|
||||
<Route path="business-chains" element={<BusinessChainListPage />} />
|
||||
<Route path="roundtable/chains" element={<BusinessChainEditorPage />} />
|
||||
|
||||
<Route
|
||||
path="scheduled"
|
||||
|
||||
@@ -42,6 +42,7 @@ import StudioNotebooksPage from './StudioNotebooksPage';
|
||||
import StudioNotebookDetailPage from './StudioNotebookDetailPage';
|
||||
import RoundtablePlanningPage from '@/roundtable-planning/pages/RoundtablePlanningPage';
|
||||
import BusinessChainEditorPage from '@/roundtable-planning/pages/BusinessChainEditorPage';
|
||||
import BusinessChainListPage from '@/roundtable-planning/pages/BusinessChainListPage';
|
||||
import DispatchChainPreviewPage from '@/roundtable-planning/pages/DispatchChainPreviewPage';
|
||||
import '@/roundtable-planning/styles/roundtable-planning.css';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -326,6 +327,14 @@ export default function WorkspaceRoutes({
|
||||
</WorkspaceLayout>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="business-chains"
|
||||
element={
|
||||
<WorkspaceLayout {...layoutProps}>
|
||||
<BusinessChainListPage />
|
||||
</WorkspaceLayout>
|
||||
}
|
||||
/>
|
||||
{/* Phase 7 临时预览入口:派活链路流程图弹窗(假数据自测,接通后台后可删) */}
|
||||
<Route
|
||||
path="roundtable/chain-preview"
|
||||
|
||||
@@ -40,11 +40,30 @@ export interface BusinessChainListDialogProps {
|
||||
onConfigure: (chainId?: string) => void;
|
||||
}
|
||||
|
||||
export function BusinessChainListDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
export interface BusinessChainListBodyProps {
|
||||
/**
|
||||
* 是否激活(控制何时拉取列表):弹窗里传 `open`(打开才拉),
|
||||
* 独立页面里恒为 `true`。
|
||||
*/
|
||||
active: boolean;
|
||||
/** 进入配置页。带 chainId = 打开并切到该链条;不带 = 空白新建。 */
|
||||
onConfigure: (chainId?: string) => void;
|
||||
/** 列表为空时的「去创建」引导文案差异(弹窗说「点下方配置」,页面说「点右上配置」)。 */
|
||||
emptyHint?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务链条列表主体(筛选 tab + 滚动卡片列表)。
|
||||
*
|
||||
* 从 `BusinessChainListDialog` 抽出,供弹窗与独立配置页(`BusinessChainListPage`)
|
||||
* 复用,保证两处的加载 / 发布 / 卡片渲染逻辑完全一致。外层负责标题栏与「配置」入口、
|
||||
* 以及撑出 `flex flex-col` 高度(本组件用 `flex-1` 占满剩余空间)。
|
||||
*/
|
||||
export function BusinessChainListBody({
|
||||
active,
|
||||
onConfigure,
|
||||
}: BusinessChainListDialogProps) {
|
||||
emptyHint,
|
||||
}: BusinessChainListBodyProps) {
|
||||
const [chains, setChains] = useState<BusinessChain[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -65,9 +84,9 @@ export function BusinessChainListDialog({
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 每次打开 / 切换筛选都重新拉,保证配置页改动后回来看到的是最新列表。
|
||||
// 每次激活 / 切换筛选都重新拉,保证配置页改动后回来看到的是最新列表。
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
if (!active) return;
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
setLoading(true);
|
||||
@@ -84,7 +103,7 @@ export function BusinessChainListDialog({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [open, scope]);
|
||||
}, [active, scope]);
|
||||
|
||||
const handleTogglePublic = useCallback(
|
||||
async (chain: BusinessChain) => {
|
||||
@@ -101,6 +120,73 @@ export function BusinessChainListDialog({
|
||||
[refresh, scope],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 筛选:我的 / 公共 */}
|
||||
<div className="shrink-0 flex items-center gap-1 rounded-lg border border-border bg-muted/40 p-0.5 self-start">
|
||||
{(["mine", "public"] as const).map((s) => (
|
||||
<button
|
||||
key={s}
|
||||
type="button"
|
||||
onClick={() => setScope(s)}
|
||||
className={cn(
|
||||
"rounded-md px-3 py-1 text-xs font-medium transition",
|
||||
scope === s
|
||||
? "bg-primary text-primary-foreground shadow-sm"
|
||||
: "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{s === "mine" ? "我的" : "公共"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="custom-scrollbar -mr-1 min-h-0 flex-1 overflow-y-auto py-3 pr-1">
|
||||
{loading ? (
|
||||
<div className="flex items-center gap-2 px-1 py-10 text-sm text-muted-foreground">
|
||||
<Loader2Icon className="size-4 animate-spin" />
|
||||
正在加载业务链条…
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive">
|
||||
加载业务链条失败:{error}
|
||||
</div>
|
||||
) : chains.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center gap-3 px-6 py-12 text-center">
|
||||
<LayersIcon className="size-10 text-primary/40" />
|
||||
<div className="text-sm font-semibold">
|
||||
{scope === "public" ? "还没有公共链条" : "还没有业务链条"}
|
||||
</div>
|
||||
<p className="max-w-sm text-xs leading-relaxed text-muted-foreground">
|
||||
{scope === "public"
|
||||
? "公共链条是其他账号(或你自己)发布出来、所有人可见的链条。去「我的」里发布一条试试。"
|
||||
: emptyHint ??
|
||||
"业务链条是可复用的有序智能体编排。点击下方「配置」去创建第一条链条。"}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3.5">
|
||||
{chains.map((chain) => (
|
||||
<ChainRow
|
||||
key={chain.id}
|
||||
chain={chain}
|
||||
publishing={publishingId === chain.id}
|
||||
onSettings={() => onConfigure(chain.id)}
|
||||
onTogglePublic={() => handleTogglePublic(chain)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function BusinessChainListDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
onConfigure,
|
||||
}: BusinessChainListDialogProps) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="flex max-h-[85vh] flex-col gap-0 sm:max-w-2xl">
|
||||
@@ -114,61 +200,7 @@ export function BusinessChainListDialog({
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{/* 筛选:我的 / 公共 */}
|
||||
<div className="shrink-0 flex items-center gap-1 rounded-lg border border-border bg-muted/40 p-0.5 self-start">
|
||||
{(["mine", "public"] as const).map((s) => (
|
||||
<button
|
||||
key={s}
|
||||
type="button"
|
||||
onClick={() => setScope(s)}
|
||||
className={cn(
|
||||
"rounded-md px-3 py-1 text-xs font-medium transition",
|
||||
scope === s
|
||||
? "bg-primary text-primary-foreground shadow-sm"
|
||||
: "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{s === "mine" ? "我的" : "公共"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="custom-scrollbar -mr-1 min-h-0 flex-1 overflow-y-auto py-3 pr-1">
|
||||
{loading ? (
|
||||
<div className="flex items-center gap-2 px-1 py-10 text-sm text-muted-foreground">
|
||||
<Loader2Icon className="size-4 animate-spin" />
|
||||
正在加载业务链条…
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive">
|
||||
加载业务链条失败:{error}
|
||||
</div>
|
||||
) : chains.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center gap-3 px-6 py-12 text-center">
|
||||
<LayersIcon className="size-10 text-primary/40" />
|
||||
<div className="text-sm font-semibold">
|
||||
{scope === "public" ? "还没有公共链条" : "还没有业务链条"}
|
||||
</div>
|
||||
<p className="max-w-sm text-xs leading-relaxed text-muted-foreground">
|
||||
{scope === "public"
|
||||
? "公共链条是其他账号(或你自己)发布出来、所有人可见的链条。去「我的」里发布一条试试。"
|
||||
: "业务链条是可复用的有序智能体编排。点击下方「配置」去创建第一条链条。"}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3.5">
|
||||
{chains.map((chain) => (
|
||||
<ChainRow
|
||||
key={chain.id}
|
||||
chain={chain}
|
||||
publishing={publishingId === chain.id}
|
||||
onSettings={() => onConfigure(chain.id)}
|
||||
onTogglePublic={() => handleTogglePublic(chain)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<BusinessChainListBody active={open} onConfigure={onConfigure} />
|
||||
|
||||
<DialogFooter className="shrink-0 border-t pt-3 sm:justify-end">
|
||||
<Button onClick={() => onConfigure()} className="gap-1.5">
|
||||
@@ -246,22 +278,70 @@ function ChainRow({
|
||||
<div className="mt-1.5 line-clamp-2 text-sm text-muted-foreground">{chain.description}</div>
|
||||
)}
|
||||
|
||||
{/* 席位名 tag(按顺序,→ 箭头连接发言次序) */}
|
||||
<div className="mt-3.5 flex flex-wrap items-center gap-1.5">
|
||||
{chain.seats.map((seat, i) => (
|
||||
<Fragment key={seat.agent_id}>
|
||||
{i > 0 && <ArrowRightIcon className="size-3.5 shrink-0 text-muted-foreground/50" />}
|
||||
<span
|
||||
className={cn(
|
||||
"rounded-md border px-2 py-1 text-xs font-medium",
|
||||
chainTagClasses(getAvatarTypeFor(seat.agent_id, i)),
|
||||
)}
|
||||
>
|
||||
{seat.name || seat.agent_id}
|
||||
</span>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
{/* 席位编排:按 stage 分组——并行阶段竖向并列在虚线框里,阶段之间用 → 串联。 */}
|
||||
<ChainSeatsFlow chain={chain} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 链条席位编排流(体现「同层并行 · 层间串行」)─────────────────────────────
|
||||
|
||||
function ChainSeatsFlow({ chain }: { chain: BusinessChain }) {
|
||||
// 名称回退表:用落库快照(seats)解析 stage 里的 agent_id。
|
||||
const seatById = new Map(chain.seats.map((s) => [s.agent_id, s] as const));
|
||||
|
||||
// 分层来源:带 stages(分层编排)就按 stages 渲染(过滤空阶段);否则每个席位各成
|
||||
// 一段、顺序串行(线性链,与旧版展示一致)。
|
||||
const stages =
|
||||
chain.stages && chain.stages.length > 0
|
||||
? chain.stages.filter((s) => s.length > 0)
|
||||
: chain.seats.map((s) => [s.agent_id]);
|
||||
|
||||
// 跨阶段累加的全局序号 —— 决定头像配色,与编辑器 / 展平 seats 顺序对齐。
|
||||
const orderById = new Map<string, number>();
|
||||
let counter = 0;
|
||||
for (const stageIds of stages) {
|
||||
for (const id of stageIds) {
|
||||
orderById.set(id, counter);
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const seatTag = (agentId: string) => {
|
||||
const seat = seatById.get(agentId);
|
||||
return (
|
||||
<span
|
||||
key={agentId}
|
||||
className={cn(
|
||||
"rounded-md border px-2 py-1 text-xs font-medium",
|
||||
chainTagClasses(getAvatarTypeFor(agentId, orderById.get(agentId) ?? 0)),
|
||||
)}
|
||||
>
|
||||
{seat?.name || agentId}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-3.5 flex flex-wrap items-center gap-1.5">
|
||||
{stages.map((stageIds, stageIdx) => (
|
||||
<Fragment key={stageIdx}>
|
||||
{stageIdx > 0 && (
|
||||
<ArrowRightIcon className="size-3.5 shrink-0 text-muted-foreground/50" />
|
||||
)}
|
||||
{stageIds.length > 1 ? (
|
||||
// 并行阶段:席位竖向并列在虚线框里,左上角标「并行 N」。
|
||||
<div className="relative flex flex-col gap-1 rounded-lg border border-dashed border-primary/40 bg-primary/[0.04] px-1.5 pb-1.5 pt-2.5">
|
||||
<span className="pointer-events-none absolute -top-2 left-1.5 rounded-full bg-card px-1.5 text-[10px] font-semibold leading-tight text-primary">
|
||||
并行 {stageIds.length}
|
||||
</span>
|
||||
{stageIds.map((id) => seatTag(id))}
|
||||
</div>
|
||||
) : (
|
||||
seatTag(stageIds[0])
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 业务链条配置页(独立页面版)。
|
||||
*
|
||||
* 把原本挂在圆桌页顶栏「业务链条」按钮上的 `BusinessChainListDialog` 弹窗内容,
|
||||
* 单独画成一个全屏页面:综合管理 → 业务链条配置 入口进来即是本页。
|
||||
*
|
||||
* - 列表主体复用 `BusinessChainListBody`(与弹窗同一份加载 / 发布 / 卡片逻辑)。
|
||||
* - 顶部「配置」按钮 = 进入编辑器空白新建;卡片「设置」= 进入编辑器并切到该链条。
|
||||
*
|
||||
* 路由 base 感知:用 `useRouteNavigate` 跳转,strategy / workspace 两套路由树下
|
||||
* 都能落到当前 base 的编辑器(`roundtable/chains`)。
|
||||
*/
|
||||
import { LayersIcon, SettingsIcon } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useRouteNavigate } from "@/core/route-base";
|
||||
|
||||
import { BusinessChainListBody } from "@/roundtable-planning/components/BusinessChainListDialog";
|
||||
|
||||
export default function BusinessChainListPage() {
|
||||
const navigate = useRouteNavigate();
|
||||
|
||||
// 进入编辑器:带 chainId = 打开并切到该链条;不带 = 空白新建。
|
||||
// 路径写 workspace base,`useRouteNavigate` 会按当前路由 base 改写。
|
||||
const goConfigure = (chainId?: string) => {
|
||||
navigate(
|
||||
chainId
|
||||
? `/page/workspace/roundtable/chains?chainId=${encodeURIComponent(chainId)}`
|
||||
: "/page/workspace/roundtable/chains",
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full min-h-0 flex-col bg-muted/40 text-foreground">
|
||||
{/* 顶栏:标题 + 描述 + 配置入口 */}
|
||||
<header className="shrink-0 border-b border-border bg-card px-6 py-4">
|
||||
<div className="mx-auto flex w-full max-w-3xl items-start justify-between gap-4">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 text-base font-bold text-foreground">
|
||||
<LayersIcon className="size-5 text-primary" />
|
||||
业务链条配置
|
||||
</div>
|
||||
<p className="mt-1 text-xs leading-relaxed text-muted-foreground">
|
||||
可复用的有序智能体编排。点击卡片「设置」可进入编辑器修改该链条;「发布」后所有账号都能在「公共」里查看 / 选用。
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => goConfigure()} className="shrink-0 gap-1.5">
|
||||
<SettingsIcon className="size-4" />
|
||||
配置
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* 列表主体(与弹窗共用 BusinessChainListBody) */}
|
||||
<div className="mx-auto flex w-full min-h-0 max-w-3xl flex-1 flex-col px-6 py-4">
|
||||
<BusinessChainListBody
|
||||
active
|
||||
onConfigure={goConfigure}
|
||||
emptyHint="业务链条是可复用的有序智能体编排。点击右上角「配置」去创建第一条链条。"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+2
-2
@@ -170,7 +170,7 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
|
||||
setShowAnalysisModal(false);
|
||||
};
|
||||
|
||||
// 单智能体分析:用「业务链条管理」里为本业务配置的智能体,跳转到其问答页(新会话),
|
||||
// 单智能体分析:用「配置管理」里为本业务配置的智能体,跳转到其问答页(新会话),
|
||||
// 把任务名称/简介作为开场消息暂存,由 AgentChatPage 读取 sessionStorage.pendingAgentMessage
|
||||
// 自动发出并开始问答。未配置智能体则提示去配置。
|
||||
const handleSingleAgentAnalysis = async () => {
|
||||
@@ -184,7 +184,7 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
|
||||
/* ignore → 下面按未配置处理 */
|
||||
}
|
||||
if (!agentId) {
|
||||
toast.error('该业务尚未在「业务链条管理」中配置单智能体');
|
||||
toast.error('该业务尚未在「配置管理」中配置单智能体');
|
||||
return;
|
||||
}
|
||||
const text = [
|
||||
|
||||
@@ -170,7 +170,7 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
|
||||
setShowAnalysisModal(false);
|
||||
};
|
||||
|
||||
// 单智能体分析:用「业务链条管理」里为本业务配置的智能体,跳转到其问答页(新会话),
|
||||
// 单智能体分析:用「配置管理」里为本业务配置的智能体,跳转到其问答页(新会话),
|
||||
// 把任务名称/简介作为开场消息暂存,由 AgentChatPage 读取 sessionStorage.pendingAgentMessage
|
||||
// 自动发出并开始问答。未配置智能体则提示去配置。
|
||||
const handleSingleAgentAnalysis = async () => {
|
||||
@@ -184,7 +184,7 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
|
||||
/* ignore → 下面按未配置处理 */
|
||||
}
|
||||
if (!agentId) {
|
||||
toast.error('该业务尚未在「业务链条管理」中配置单智能体');
|
||||
toast.error('该业务尚未在「配置管理」中配置单智能体');
|
||||
return;
|
||||
}
|
||||
const text = [
|
||||
|
||||
Reference in new issue
Block a user