615 lines
22 KiB
TypeScript
615 lines
22 KiB
TypeScript
import { useEffect, useMemo, useState, type ReactNode } from "react";
|
|
import { useSearchParams } from "react-router-dom";
|
|
import {
|
|
ChevronDown,
|
|
ChevronLeft,
|
|
Loader2,
|
|
Settings2,
|
|
UserRoundCog,
|
|
} from "lucide-react";
|
|
import { toast } from "sonner";
|
|
|
|
import {
|
|
AnalysisResultCardList,
|
|
AnalysisResultDetailDialog,
|
|
type AnalysisResultCardItem,
|
|
} from "@/components/analysis-results";
|
|
import { AgentDetailPanel } from "@/components/workspace/agents/agent-detail-panel";
|
|
import { useAgent, useAgents, type Agent } from "@/core/agents";
|
|
import backArrowIcon from "@/assets/eightbf-roundtable/back-arrow.svg";
|
|
import agentConfigLogo from "@/assets/position-roundtable/agent-config-logo.svg";
|
|
import collapsePanelIcon from "@/assets/position-roundtable/collapse-panel.svg";
|
|
import doubleArrowDecoration from "@/assets/position-roundtable/double-arrow-decoration.svg";
|
|
import {
|
|
parentReturnRoute,
|
|
requestEightBfParentReturn,
|
|
} from "@/eightbf-roundtable/lib/parent-return";
|
|
import { useEightBfAnalysisResults } from "@/eightbf-roundtable/lib/collect-md-artifacts";
|
|
import { PositionIntentInput } from "@/position-roundtable/components/PositionIntentInput";
|
|
import {
|
|
positionNameById,
|
|
syncPositionOptionsFromRoles,
|
|
} from "@/position-roundtable/lib/positions";
|
|
import type { DraftStep3SummarySnapshot } from "@/roundtable-planning/api/drafts";
|
|
import { EIGHTBF_INTENT_AGENT_ID } from "@/roundtable-planning/api/intent";
|
|
import type { ThreadIdMap } from "@/roundtable-planning/api/multi-agent";
|
|
import "@/position-roundtable/styles/intent-understanding.css";
|
|
import type { Step1PanelProps } from "@/roundtable-planning/components/Step1Panel";
|
|
import { Step1Panel } from "@/roundtable-planning/components/Step1Panel";
|
|
import type { BusinessChain, ChainSeat } from "@/roundtable-planning/lib/business-chain";
|
|
import { usePositionRoles } from "@/strategy-components/api/position-roles";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type IntentRecord = Record<string, unknown>;
|
|
|
|
export interface EightBfStep1PanelProps extends Step1PanelProps {
|
|
chain: BusinessChain | null;
|
|
canConfigureChain: boolean;
|
|
onConfigureChain: () => void;
|
|
selectedModel: string;
|
|
modelOptions: Array<{ label: string; value: string }>;
|
|
onSelectedModelChange: (model: string) => void;
|
|
step2Dialogues?: unknown[];
|
|
step2ThreadIds?: ThreadIdMap | null;
|
|
step2CoordinatorName?: string | null;
|
|
step2SeatStubMessages?: Record<string, unknown[]>;
|
|
step3Summary?: DraftStep3SummarySnapshot | null;
|
|
}
|
|
|
|
export function EightBfRoundtableBackButton() {
|
|
const [searchParams] = useSearchParams();
|
|
const handleBack = () => {
|
|
const route = parentReturnRoute(
|
|
searchParams.get("parentReturnTo")
|
|
?? searchParams.get("parentRoute")
|
|
?? searchParams.get("returnTo")
|
|
?? "/list",
|
|
);
|
|
if (requestEightBfParentReturn(route)) return;
|
|
toast.warning("尚未配置返回地址,请在 runtime-config.js 填写 VITE_8BF_ROUNDTABLE_BACK_URL");
|
|
};
|
|
return (
|
|
<button
|
|
type="button"
|
|
className="eightbf-roundtable__back-button"
|
|
onClick={handleBack}
|
|
>
|
|
<img src={backArrowIcon} alt="" />
|
|
返回
|
|
</button>
|
|
);
|
|
}
|
|
|
|
export function EightBfStep1Panel({
|
|
chain,
|
|
canConfigureChain,
|
|
onConfigureChain,
|
|
selectedModel,
|
|
modelOptions,
|
|
onSelectedModelChange,
|
|
intentReady,
|
|
step2Dialogues = [],
|
|
step2ThreadIds = null,
|
|
step2CoordinatorName = null,
|
|
step2SeatStubMessages,
|
|
step3Summary = null,
|
|
...step1Props
|
|
}: EightBfStep1PanelProps) {
|
|
const [rightPanelOpen, setRightPanelOpen] = useState(true);
|
|
const [rightTab, setRightTab] = useState<"agents" | "results">("agents");
|
|
// 第一步内置任务理解智能体:其「常用问题」显示在输入框左上角,点击直接发送。
|
|
const { agent: intentAgent } = useAgent(EIGHTBF_INTENT_AGENT_ID);
|
|
|
|
const openPanel = (tab: "agents" | "results") => {
|
|
if (rightPanelOpen && rightTab === tab) {
|
|
setRightPanelOpen(false);
|
|
return;
|
|
}
|
|
setRightTab(tab);
|
|
setRightPanelOpen(true);
|
|
};
|
|
|
|
return (
|
|
<div className="position-roundtable-intent-page eightbf-roundtable-intent-page contents">
|
|
<Step1Panel
|
|
{...step1Props}
|
|
rootClassName="position-roundtable-intent-panel eightbf-roundtable-intent-panel [&_#save-draft-btn]:hidden [&_#skip-question-btn]:hidden [&_#suggest-followup-btn]:hidden"
|
|
headerLeftSlot={
|
|
<div className="position-roundtable-intent-header-title">
|
|
<span aria-hidden="true" />
|
|
<h2>{chain?.title || "8BF 业务链"} · 意图识别</h2>
|
|
</div>
|
|
}
|
|
headerRightSlot={
|
|
<div className="position-roundtable-intent-header-actions">
|
|
<button
|
|
type="button"
|
|
className={cn(rightPanelOpen && rightTab === "agents" && "is-selected")}
|
|
onClick={() => openPanel("agents")}
|
|
>
|
|
智能体配置
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={cn(rightPanelOpen && rightTab === "results" && "is-selected")}
|
|
onClick={() => openPanel("results")}
|
|
>
|
|
分析结果
|
|
</button>
|
|
<EightBfRoundtableBackButton />
|
|
</div>
|
|
}
|
|
rightSidebar={
|
|
<aside
|
|
aria-hidden={!rightPanelOpen || undefined}
|
|
className={cn(
|
|
"position-roundtable-intent-config-pane eightbf-roundtable-intent-config-pane",
|
|
rightPanelOpen ? "is-open" : "is-closed",
|
|
)}
|
|
>
|
|
<div className="position-roundtable-intent-config-pane-inner">
|
|
<EightBfAgentWorkspace
|
|
tab={rightTab}
|
|
chain={chain}
|
|
canConfigureChain={canConfigureChain}
|
|
onConfigureChain={onConfigureChain}
|
|
onCollapse={() => setRightPanelOpen(false)}
|
|
step2Dialogues={step2Dialogues}
|
|
step2ThreadIds={step2ThreadIds}
|
|
step2CoordinatorName={step2CoordinatorName}
|
|
step2SeatStubMessages={step2SeatStubMessages}
|
|
step3Summary={step3Summary}
|
|
/>
|
|
</div>
|
|
</aside>
|
|
}
|
|
hideQuestionCount
|
|
inputArea={(inputProps) => (
|
|
<PositionIntentInput
|
|
{...inputProps}
|
|
modelOptions={modelOptions}
|
|
selectedModel={selectedModel}
|
|
onSelectedModelChange={onSelectedModelChange}
|
|
commonQuestions={intentAgent?.common_questions ?? null}
|
|
sendCommonQuestionOnClick
|
|
/>
|
|
)}
|
|
intentReady={intentReady}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function EightBfStep1Sidebar({
|
|
taskCard,
|
|
businessChain,
|
|
intentReady,
|
|
onCollapse,
|
|
}: {
|
|
taskCard?: ReactNode;
|
|
businessChain?: ReactNode;
|
|
intentReady: object | null;
|
|
onCollapse?: () => void;
|
|
}) {
|
|
return (
|
|
<div className="eightbf-roundtable__step1-sidebar position-roundtable-entry-sidebar">
|
|
{taskCard}
|
|
{onCollapse && !taskCard && (
|
|
<button
|
|
type="button"
|
|
className="position-roundtable-intent-config-collapse-btn ml-auto"
|
|
onClick={onCollapse}
|
|
aria-label="收起左侧面板"
|
|
title="收起左侧面板"
|
|
>
|
|
<img src={collapsePanelIcon} alt="" aria-hidden="true" />
|
|
</button>
|
|
)}
|
|
<EightBfIntentDelivery intentReady={intentReady} businessChain={businessChain} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/** 第一、二步共用的意图结果面板,保持完全相同的展示结构和样式。 */
|
|
export function EightBfIntentDelivery({
|
|
intentReady,
|
|
businessChain,
|
|
}: {
|
|
intentReady: object | null;
|
|
businessChain?: ReactNode;
|
|
}) {
|
|
const sections = intentSections(intentReady);
|
|
return (
|
|
<section className="eightbf-roundtable__intent-delivery-sidebar position-roundtable-intent-delivery-sidebar" aria-labelledby="eightbf-intent-sidebar-title">
|
|
{sections.length > 0 && (
|
|
<div className="position-roundtable-intent-delivery-result">
|
|
<header className="position-roundtable-intent-delivery-header">
|
|
<span aria-hidden="true" />
|
|
<h2 id="eightbf-intent-sidebar-title">意图理解结果</h2>
|
|
</header>
|
|
<div className="position-roundtable-intent-delivery-sections">
|
|
{sections.map((section) => (
|
|
<article
|
|
key={section.label}
|
|
className={cn(
|
|
"position-roundtable-intent-delivery-section",
|
|
intentSectionTone(section.label),
|
|
)}
|
|
>
|
|
<h3>{section.label}</h3>
|
|
<ul>
|
|
{section.items.map((item) => <li key={item}>{item}</li>)}
|
|
</ul>
|
|
</article>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{businessChain}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
/** 第二步不需要第一步的整列占位,只复用其任务卡和意图面板。 */
|
|
export function EightBfStep2SidebarHeader({
|
|
taskCard,
|
|
intentReady,
|
|
}: {
|
|
taskCard?: ReactNode;
|
|
intentReady: object | null;
|
|
}) {
|
|
return (
|
|
<div className="eightbf-roundtable__step2-sidebar-header">
|
|
{taskCard}
|
|
<EightBfIntentDelivery intentReady={intentReady} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function EightBfHeaderBrand({ chainTitle }: { chainTitle?: string }) {
|
|
return (
|
|
<div className="eightbf-roundtable__header-brand">
|
|
<span className="eightbf-roundtable__header-brand-icon" aria-hidden="true">
|
|
<UserRoundCog />
|
|
</span>
|
|
<div className="min-w-0">
|
|
<h1>8BF 多智能体会商</h1>
|
|
<p>按业务链承接任务、对话与分析产物</p>
|
|
</div>
|
|
<span className="eightbf-roundtable__header-status">
|
|
<span aria-hidden="true">⌁</span>
|
|
{chainTitle ? "业务链已启动" : "等待业务链配置"}
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function EightBfHeaderChain({ chainTitle }: { chainTitle?: string }) {
|
|
return (
|
|
<span className="eightbf-roundtable__header-chain" title={chainTitle || "尚未配置业务链"}>
|
|
<span>当前业务链</span>
|
|
<strong>{chainTitle || "暂未配置"}</strong>
|
|
<ChevronDown aria-hidden="true" />
|
|
</span>
|
|
);
|
|
}
|
|
|
|
function EightBfAgentWorkspace({
|
|
tab,
|
|
chain,
|
|
canConfigureChain,
|
|
onConfigureChain,
|
|
onCollapse,
|
|
step2Dialogues,
|
|
step2ThreadIds,
|
|
step2CoordinatorName,
|
|
step2SeatStubMessages,
|
|
step3Summary,
|
|
}: {
|
|
tab: "agents" | "results";
|
|
chain: BusinessChain | null;
|
|
canConfigureChain: boolean;
|
|
onConfigureChain: () => void;
|
|
onCollapse: () => void;
|
|
step2Dialogues: unknown[];
|
|
step2ThreadIds: ThreadIdMap | null;
|
|
step2CoordinatorName: string | null;
|
|
step2SeatStubMessages?: Record<string, unknown[]>;
|
|
step3Summary: DraftStep3SummarySnapshot | null;
|
|
}) {
|
|
const { agents, isLoading } = useAgents();
|
|
const { data: positionRoles = [] } = usePositionRoles();
|
|
useEffect(() => {
|
|
syncPositionOptionsFromRoles(positionRoles);
|
|
}, [positionRoles]);
|
|
const seats = chain?.seats ?? [];
|
|
const [selectedAgentId, setSelectedAgentId] = useState<string | null>(null);
|
|
const [agentKeyword, setAgentKeyword] = useState("");
|
|
const { agent: selectedAgent, isLoading: selectedAgentLoading } = useAgent(selectedAgentId);
|
|
const [detailItem, setDetailItem] = useState<AnalysisResultCardItem | null>(null);
|
|
const analysisItems = useEightBfAnalysisResults({
|
|
chain,
|
|
dialogues: step2Dialogues,
|
|
threadIds: step2ThreadIds,
|
|
coordinatorName: step2CoordinatorName,
|
|
seatStubMessages: step2SeatStubMessages,
|
|
summaryReport: step3Summary,
|
|
});
|
|
|
|
// 第一步绑定的内置任务理解智能体:不在业务链席位里,固定置顶展示,
|
|
// 供管理员在本面板查看 / 配置其提示词与常用问题。
|
|
const intentAgent = useMemo(
|
|
() => agents.find((agent) => agent.id === EIGHTBF_INTENT_AGENT_ID) ?? null,
|
|
[agents],
|
|
);
|
|
const intentAgentMatchesKeyword = useMemo(() => {
|
|
const keyword = agentKeyword.trim().toLowerCase();
|
|
if (!keyword) return true;
|
|
const name = (intentAgent?.name || "8BF 任务理解").toLowerCase();
|
|
const description = (intentAgent?.description || "").toLowerCase();
|
|
return name.includes(keyword) || description.includes(keyword);
|
|
}, [agentKeyword, intentAgent]);
|
|
|
|
useEffect(() => {
|
|
if (
|
|
selectedAgentId &&
|
|
selectedAgentId !== EIGHTBF_INTENT_AGENT_ID &&
|
|
!seats.some((seat) => seat.agent_id === selectedAgentId)
|
|
) {
|
|
setSelectedAgentId(null);
|
|
}
|
|
}, [seats, selectedAgentId]);
|
|
|
|
const seatAgents = useMemo(() => {
|
|
const agentById = new Map(agents.map((agent) => [agent.id, agent]));
|
|
return seats.map((seat) => ({ seat, agent: agentById.get(seat.agent_id) ?? null }));
|
|
}, [agents, seats]);
|
|
const filteredSeatAgents = useMemo(() => {
|
|
const keyword = agentKeyword.trim().toLowerCase();
|
|
if (!keyword) return seatAgents;
|
|
return seatAgents.filter(({ seat, agent }) => {
|
|
const name = (agent?.name || seat.name || "").toLowerCase();
|
|
const description = (agent?.description || seat.description || "").toLowerCase();
|
|
return name.includes(keyword) || description.includes(keyword);
|
|
});
|
|
}, [agentKeyword, seatAgents]);
|
|
const selectedSeat = seats.find((seat) => seat.agent_id === selectedAgentId) ?? null;
|
|
|
|
if (tab === "results") {
|
|
return (
|
|
<section className="position-roundtable-intent-analysis-results eightbf-roundtable-intent-analysis-results">
|
|
<div className="position-roundtable-intent-analysis-header">
|
|
<span>分析成果列表</span>
|
|
<button
|
|
type="button"
|
|
className="position-roundtable-intent-config-collapse-btn"
|
|
onClick={onCollapse}
|
|
aria-label="收起分析结果"
|
|
title="收起分析结果"
|
|
>
|
|
<img src={collapsePanelIcon} alt="" aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
<div className="position-roundtable-intent-analysis-results-scroll eightbf-roundtable-intent-results-scroll">
|
|
<AnalysisResultCardList
|
|
items={analysisItems}
|
|
emptyTitle="暂无交付产物"
|
|
emptyDescription="完成第二步研讨与第三步方案总结并生成 Markdown 后,产物会显示在这里。"
|
|
padded={false}
|
|
onOpen={setDetailItem}
|
|
/>
|
|
</div>
|
|
<AnalysisResultDetailDialog
|
|
item={detailItem}
|
|
onOpenChange={(open) => {
|
|
if (!open) setDetailItem(null);
|
|
}}
|
|
/>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
if (selectedAgentId) {
|
|
return (
|
|
<div className="position-roundtable-intent-config-workspace">
|
|
<div className="eightbf-roundtable__agent-detail-topbar">
|
|
<button type="button" onClick={() => setSelectedAgentId(null)}>
|
|
<ChevronLeft className="size-4" />
|
|
智能体列表
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="position-roundtable-intent-config-collapse-btn"
|
|
onClick={onCollapse}
|
|
aria-label="收起智能体配置"
|
|
title="收起智能体配置"
|
|
>
|
|
<img src={collapsePanelIcon} alt="" aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
<div className="position-roundtable-agent-detail position-roundtable-agent-detail--special h-[calc(100%-48px)] min-h-0">
|
|
{selectedAgentLoading ? (
|
|
<div className="flex h-full items-center justify-center gap-2 text-sm text-[#aaceff]">
|
|
<Loader2 className="size-4 animate-spin" /> 正在读取智能体配置…
|
|
</div>
|
|
) : (
|
|
<AgentDetailPanel
|
|
agent={selectedAgent}
|
|
agentName={
|
|
selectedAgent?.name
|
|
?? (selectedAgentId === EIGHTBF_INTENT_AGENT_ID
|
|
? "8BF 任务理解"
|
|
: selectedSeat?.name ?? "")
|
|
}
|
|
onAskQuestion={() => toast.info("请在中间对话区向任务理解智能体发起问题")}
|
|
showRecentChats={false}
|
|
avatarSrc={agentConfigLogo}
|
|
positionName={positionNameById(selectedSeat?.position_id)}
|
|
jobResponsibilities={selectedSeat?.job_responsibilities ?? ""}
|
|
skillsIcon={<img src={doubleArrowDecoration} alt="" aria-hidden="true" />}
|
|
skillsTitle="技能列表"
|
|
showCommonQuestions
|
|
panelTitle="智能体配置"
|
|
embedded
|
|
hideStatusBadges
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<section className="eightbf-roundtable__agent-list-workspace">
|
|
<div className="position-roundtable-intent-analysis-header">
|
|
<span>智能体配置</span>
|
|
<div className="flex items-center gap-1.5">
|
|
{canConfigureChain && (
|
|
<button type="button" className="eightbf-roundtable__configure-chain-button" onClick={onConfigureChain}>
|
|
<Settings2 className="size-3.5" /> 配置业务链
|
|
</button>
|
|
)}
|
|
<button
|
|
type="button"
|
|
className="position-roundtable-intent-config-collapse-btn"
|
|
onClick={onCollapse}
|
|
aria-label="收起智能体配置"
|
|
title="收起智能体配置"
|
|
>
|
|
<img src={collapsePanelIcon} alt="" aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="eightbf-roundtable__agent-selector">
|
|
<header className="eightbf-roundtable__agent-selector-head">
|
|
<div className="eightbf-roundtable__agent-selector-title">
|
|
<b>当前岗位智能体</b>
|
|
<i>{seatAgents.length}</i>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
value={agentKeyword}
|
|
onChange={(event) => setAgentKeyword(event.target.value)}
|
|
placeholder="搜索名称或说明"
|
|
className="eightbf-roundtable__agent-selector-search"
|
|
/>
|
|
</header>
|
|
{isLoading && seats.length > 0 ? (
|
|
<div className="eightbf-roundtable__agent-selector-empty">正在加载智能体…</div>
|
|
) : filteredSeatAgents.length === 0 && !intentAgentMatchesKeyword ? (
|
|
<div className="eightbf-roundtable__agent-selector-empty">
|
|
<p>{seatAgents.length ? "没有符合条件的智能体" : "当前岗位尚未配置智能体"}</p>
|
|
{!seatAgents.length && canConfigureChain && (
|
|
<button type="button" onClick={onConfigureChain}>前往业务链配置</button>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<div className="eightbf-roundtable__agent-selector-list">
|
|
{intentAgentMatchesKeyword && (
|
|
<EightBfAgentCard
|
|
name={intentAgent?.name || "8BF 任务理解"}
|
|
description={intentAgent?.description || "第一步「意图识别」的内置任务理解智能体"}
|
|
onSelect={() => setSelectedAgentId(EIGHTBF_INTENT_AGENT_ID)}
|
|
/>
|
|
)}
|
|
{filteredSeatAgents.map(({ seat, agent }) => (
|
|
<EightBfAgentRow
|
|
key={seat.agent_id}
|
|
seat={seat}
|
|
agent={agent}
|
|
onSelect={() => setSelectedAgentId(seat.agent_id)}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function EightBfAgentRow({
|
|
seat,
|
|
agent,
|
|
onSelect,
|
|
}: {
|
|
seat: ChainSeat;
|
|
agent: Agent | null;
|
|
onSelect: () => void;
|
|
}) {
|
|
const name = agent?.name || seat.name || "未命名智能体";
|
|
const description = agent?.description || seat.description || "智能体助手";
|
|
return <EightBfAgentCard name={name} description={description} onSelect={onSelect} />;
|
|
}
|
|
|
|
/** 智能体列表卡片:席位智能体与置顶的内置任务理解智能体共用同一展示结构。 */
|
|
function EightBfAgentCard({
|
|
name,
|
|
description,
|
|
onSelect,
|
|
}: {
|
|
name: string;
|
|
description: string;
|
|
onSelect: () => void;
|
|
}) {
|
|
const avatarText = firstAgentAvatarChar(name);
|
|
return (
|
|
<button type="button" className="eightbf-roundtable__agent-selector-card" onClick={onSelect}>
|
|
<span className="eightbf-roundtable__agent-selector-avatar" aria-hidden="true">
|
|
{avatarText ? avatarText : <img src={agentConfigLogo} alt="" />}
|
|
</span>
|
|
<span className="eightbf-roundtable__agent-selector-main">
|
|
<strong>{name}</strong>
|
|
<p>{description}</p>
|
|
</span>
|
|
<i className="eightbf-roundtable__agent-selector-bar" aria-hidden="true" />
|
|
</button>
|
|
);
|
|
}
|
|
|
|
/** 名称首字若是【、( 这类标点,列表卡片改用智能体 logo,不把标点当头像。 */
|
|
function firstAgentAvatarChar(name: string): string | null {
|
|
const first = Array.from(name.trim())[0];
|
|
if (!first || /^[\p{P}\p{S}]$/u.test(first)) return null;
|
|
return first.toUpperCase();
|
|
}
|
|
|
|
function intentSections(intentReady: object | null): Array<{ label: string; items: string[] }> {
|
|
const source = intentReady && typeof intentReady === "object" ? intentReady as IntentRecord : null;
|
|
if (!source) return [];
|
|
const entries = [
|
|
["核心目标", arrayValue(source.coreGoals, source.objective)],
|
|
["风险提示", arrayValue(source.riskWarnings, source.constraints)],
|
|
["关键要点", arrayValue(source.keyPoints, source.assumptions)],
|
|
["战略意义", arrayValue(source.strategicSignificance)],
|
|
] as const;
|
|
return entries
|
|
.filter(([, items]) => items.length > 0)
|
|
.map(([label, items]) => ({ label, items }));
|
|
}
|
|
|
|
function intentSectionTone(label: string) {
|
|
switch (label) {
|
|
case "核心目标":
|
|
return "position-roundtable-intent-delivery-section--goals";
|
|
case "风险提示":
|
|
return "position-roundtable-intent-delivery-section--constraints";
|
|
case "关键要点":
|
|
return "position-roundtable-intent-delivery-section--purpose";
|
|
case "战略意义":
|
|
return "position-roundtable-intent-delivery-section--significance";
|
|
default:
|
|
return "position-roundtable-intent-delivery-section--significance";
|
|
}
|
|
}
|
|
|
|
function arrayValue(...values: unknown[]): string[] {
|
|
for (const value of values) {
|
|
if (Array.isArray(value)) {
|
|
const items = value.map((item) => String(item).trim()).filter(Boolean);
|
|
if (items.length > 0) return items;
|
|
}
|
|
if (typeof value === "string" && value.trim()) return [value.trim()];
|
|
}
|
|
return [];
|
|
}
|