feat: 岗位职责迁至业务链席位,并支持 WeKnora 嵌入页知识库切换

将岗位职责从智能体配置迁移到业务链席位 JSON,会商面板只读展示;WeKnora 嵌入 iframe 支持在授权可见范围内切换知识库并重新签发会话。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
董宏凯andCursor committed 2026-08-19 22:26:40 +08:00
1 parent e860357563
commit e881c242fe
16 files changed
+434 -257

No files matched your search

@@ -84,7 +84,6 @@ interface AgentDraft {
/** 会商席位「技能识别强化」开关。 */
seatSkillDirective: boolean;
knowledgeBaseIds: string[];
jobResponsibilities: string;
}
function draftFromAgent(agent: Agent): AgentDraft {
@@ -99,7 +98,6 @@ function draftFromAgent(agent: Agent): AgentDraft {
templateName: agent.template_name ?? "",
seatSkillDirective: agent.seat_skill_directive ?? false,
knowledgeBaseIds: agent.llmwiki_knowledge_base_ids ?? [],
jobResponsibilities: agent.job_responsibilities ?? "",
};
}
@@ -153,6 +151,7 @@ export function AgentDetailPanel({
skillsIcon,
skillsTitle,
showCommonQuestions = false,
jobResponsibilities,
panelTitle,
embedded,
hideStatusBadges = false,
@@ -173,6 +172,11 @@ export function AgentDetailPanel({
skillsIcon?: ReactNode;
skillsTitle?: string;
showCommonQuestions?: boolean;
/**
* 业务链席位上配置的岗位职责。传入(含空字符串)时在常用问题上方展示;
* 不传则隐藏该区块。此字段来自业务链,不随智能体本身编辑。
*/
jobResponsibilities?: string | null;
/** Overrides the standard panel heading for an embedded consumer. */
panelTitle?: string;
/** Renders one fixed panel instead of the responsive collapsible rail. */
@@ -341,8 +345,6 @@ export function AgentDetailPanel({
request.template_name = draft.templateName.trim();
if (draft.seatSkillDirective !== (agent.seat_skill_directive ?? false))
request.seat_skill_directive = draft.seatSkillDirective;
if (draft.jobResponsibilities.trim() !== (agent.job_responsibilities ?? ""))
request.job_responsibilities = draft.jobResponsibilities.trim();
if (
JSON.stringify(draft.knowledgeBaseIds) !==
JSON.stringify(agent.llmwiki_knowledge_base_ids ?? [])
@@ -725,37 +727,21 @@ export function AgentDetailPanel({
</section>
{/* ── 岗位职责 ───────────────────────────────────────────────────── */}
<Section
icon={<BriefcaseBusinessIcon className="size-3.5" />}
title="岗位职责"
>
{editing && draft ? (
<Textarea
value={draft.jobResponsibilities}
onChange={(e) =>
setDraft({ ...draft, jobResponsibilities: e.currentTarget.value })
}
placeholder="描述该智能体对应岗位的职责…"
className="min-h-20 text-sm"
/>
) : agent.job_responsibilities?.trim() ? (
<p className="text-muted-foreground text-sm leading-relaxed whitespace-pre-wrap">
{desensitize(agent.job_responsibilities)}
</p>
) : canEdit ? (
<button
type="button"
onClick={beginEdit}
className="text-muted-foreground hover:text-foreground hover:border-border w-full rounded-lg border border-dashed px-3 py-4 text-center text-xs transition-colors"
>
<PlusIcon className="mr-1 inline size-3.5 align-[-2px]" />
添加岗位职责
</button>
) : (
<p className="text-muted-foreground/60 text-sm">暂无岗位职责</p>
)}
</Section>
{/* ── 岗位职责(业务链席位配置,只读)────────────────────────────── */}
{jobResponsibilities !== undefined && (
<Section
icon={<BriefcaseBusinessIcon className="size-3.5" />}
title="岗位职责"
>
{jobResponsibilities?.trim() ? (
<p className="text-muted-foreground text-sm leading-relaxed whitespace-pre-wrap">
{desensitize(jobResponsibilities)}
</p>
) : (
<p className="text-muted-foreground/60 text-sm">暂无岗位职责</p>
)}
</Section>
)}
{/* ── 常用问题 ───────────────────────────────────────────────────── */}
-6
View File
@@ -62,8 +62,6 @@ export interface Agent {
* (名称 + SKILL.md 路径 + 必须先 read_file 的硬指令) 拼进本轮任务以补偿弱模型。
* 与业务链条同名开关取 OR。null/false = 关 (默认)。 */
seat_skill_directive?: boolean | null;
/** 岗位职责。会商智能体配置面板展示在常用问题上方。空/缺省 = 未填写。 */
job_responsibilities?: string | null;
/** DeerFlow LLMWiki mapping ids this agent allows conversations to select. */
llmwiki_knowledge_base_ids?: string[] | null;
tags?: Tag[];
@@ -101,8 +99,6 @@ export interface CreateAgentRequest {
template_name?: string | null;
/** 会商席位「技能识别强化」开关;缺省/false = 关。 */
seat_skill_directive?: boolean | null;
/** 岗位职责;缺省/空 = 未填写。 */
job_responsibilities?: string | null;
llmwiki_knowledge_base_ids?: string[] | null;
}
@@ -121,7 +117,5 @@ export interface UpdateAgentRequest {
template_name?: string | null;
/** 会商席位「技能识别强化」开关;缺省/false = 关。 */
seat_skill_directive?: boolean | null;
/** 岗位职责;传空字符串表示清除。 */
job_responsibilities?: string | null;
llmwiki_knowledge_base_ids?: string[] | null;
}
@@ -417,6 +417,7 @@ function EightBfAgentWorkspace({
showRecentChats={false}
avatarSrc={agentConfigLogo}
positionName={positionNameById(selectedSeat?.position_id)}
jobResponsibilities={selectedSeat?.job_responsibilities ?? ""}
skillsIcon={<img src={doubleArrowDecoration} alt="" aria-hidden="true" />}
skillsTitle="技能列表"
showCommonQuestions
@@ -502,10 +503,12 @@ function EightBfAgentRow({
}) {
const name = agent?.name || seat.name || "未命名智能体";
const description = agent?.description || seat.description || "智能体助手";
const avatarText = Array.from(name.trim() || "?")[0]?.toUpperCase() || "?";
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}</span>
<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>
@@ -515,6 +518,13 @@ function EightBfAgentRow({
);
}
/** 名称首字若是【、( 这类标点,列表卡片改用智能体 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 [];
@@ -300,6 +300,11 @@
font-size: 16px;
font-weight: 700;
}
.eightbf-roundtable .eightbf-roundtable__agent-selector-avatar img {
width: 22px;
height: 22px;
object-fit: contain;
}
.eightbf-roundtable .eightbf-roundtable__agent-selector-main {
display: flex;
min-width: 0;
@@ -5706,6 +5706,10 @@ function RightWorkspace({
}
skillsTitle={specialAgentConfig ? "技能列表" : undefined}
showCommonQuestions={specialAgentConfig}
jobResponsibilities={
chain?.seats.find((seat) => seat.agent_id === selectedAgentId)
?.job_responsibilities ?? ""
}
/>
</div>
</div>
@@ -55,6 +55,11 @@ export interface ChainSeat {
* 使同一条业务链既能继续用于原会商,也能在岗位协同中按岗位筛选可问答的智能体。
*/
position_id?: string | null;
/**
* 该席位在业务链中配置的岗位职责。会商「智能体配置」面板只读展示;
* 随 `seats` JSON 落库(无需 DB 列)。空 / 缺省 = 未填写。
*/
job_responsibilities?: string | null;
}
/** 一条可复用的业务链条(与后端 `/api/roundtable-chains` 记录对应)。 */
@@ -476,6 +476,19 @@ export default function BusinessChainEditorPage() {
);
}, []);
const setSeatJobResponsibilitiesCb = useCallback((agentId: string, jobResponsibilities: string) => {
setStageGroups((prev) =>
prev.map((group) => ({
...group,
seats: group.seats.map((seat) =>
seat.agent_id === agentId
? { ...seat, job_responsibilities: jobResponsibilities }
: seat,
),
})),
);
}, []);
// ── 提示词 AI 优化(总控提示词 / 各 Stage 阶段目标)────────────────────────
const [optimizingCoordinator, setOptimizingCoordinator] = useState(false);
const [goalOptimizingIdx, setGoalOptimizingIdx] = useState<number | null>(null);
@@ -681,6 +694,8 @@ export default function BusinessChainEditorPage() {
seat_mode: s.seat_mode ?? null,
// 岗位协同专用岗位归属:普通圆桌忽略,但保存时必须原样保留。
position_id: s.position_id ?? null,
// 业务链席位岗位职责:会商智能体配置面板只读展示。
job_responsibilities: s.job_responsibilities?.trim() || null,
};
});
// 总控编排提示:trim 后空串落 null(清空),与后端「空=不注入」一致。
@@ -1284,6 +1299,7 @@ export default function BusinessChainEditorPage() {
positionConfigMode={positionConfigMode}
positionOptions={positionOptions}
onSeatPositionChange={setSeatPositionCb}
onSeatJobResponsibilitiesChange={setSeatJobResponsibilitiesCb}
onStageGoalChange={setGoal}
goalOptimizingIdx={goalOptimizingIdx}
onOptimizeGoal={handleOptimizeStageGoal}
@@ -1468,6 +1484,7 @@ function StagesColumn({
positionConfigMode,
positionOptions,
onSeatPositionChange,
onSeatJobResponsibilitiesChange,
onStageGoalChange,
goalOptimizingIdx,
onOptimizeGoal,
@@ -1492,6 +1509,7 @@ function StagesColumn({
/** Live directory from the administrator's position-role configuration. */
positionOptions: readonly PositionOption[];
onSeatPositionChange: (agentId: string, positionId: PositionId | null) => void;
onSeatJobResponsibilitiesChange: (agentId: string, jobResponsibilities: string) => void;
onStageGoalChange: (stageIdx: number, goal: string) => void;
/** 正在 AI 优化阶段目标的 stage 下标(null = 没有进行中的优化)。 */
goalOptimizingIdx: number | null;
@@ -1601,6 +1619,7 @@ function StagesColumn({
positionConfigMode={positionConfigMode}
positionOptions={positionOptions}
onSeatPositionChange={onSeatPositionChange}
onSeatJobResponsibilitiesChange={onSeatJobResponsibilitiesChange}
onGoalChange={(goal) => onStageGoalChange(stageIdx, goal)}
goalOptimizing={goalOptimizingIdx === stageIdx}
anyGoalOptimizing={goalOptimizingIdx !== null}
@@ -1636,6 +1655,7 @@ function StageSection({
positionConfigMode,
positionOptions,
onSeatPositionChange,
onSeatJobResponsibilitiesChange,
onGoalChange,
goalOptimizing,
anyGoalOptimizing,
@@ -1660,6 +1680,7 @@ function StageSection({
positionConfigMode: boolean;
positionOptions: readonly PositionOption[];
onSeatPositionChange: (agentId: string, positionId: PositionId | null) => void;
onSeatJobResponsibilitiesChange: (agentId: string, jobResponsibilities: string) => void;
onGoalChange: (goal: string) => void;
/** 本 Stage 的阶段目标正在 AI 优化中。 */
goalOptimizing: boolean;
@@ -1827,6 +1848,9 @@ function StageSection({
showPositionConfig={positionConfigMode}
positionOptions={positionOptions}
onPositionChange={(positionId) => onSeatPositionChange(seat.agent_id, positionId)}
onJobResponsibilitiesChange={(value) =>
onSeatJobResponsibilitiesChange(seat.agent_id, value)
}
onDetail={() => onSeatDetail(seat.agent_id, seat.name)}
/>
))}
@@ -1856,6 +1880,7 @@ function SeatStrip({
showPositionConfig,
positionOptions,
onPositionChange,
onJobResponsibilitiesChange,
onDetail,
}: {
seat: ChainSeat;
@@ -1880,6 +1905,7 @@ function SeatStrip({
/** Current position directory, injected by the business-chain editor. */
positionOptions: readonly PositionOption[];
onPositionChange: (positionId: PositionId | null) => void;
onJobResponsibilitiesChange: (jobResponsibilities: string) => void;
onDetail: () => void;
}) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
@@ -1955,6 +1981,17 @@ function SeatStrip({
{seat.description}
</div>
)}
<label className="mt-1.5 flex flex-col gap-0.5">
<span className="text-[10px] font-medium text-muted-foreground">岗位职责</span>
<textarea
value={seat.job_responsibilities ?? ""}
onChange={(event) => onJobResponsibilitiesChange(event.currentTarget.value)}
onPointerDown={(event) => event.stopPropagation()}
placeholder="填写该席位在本业务链中的岗位职责"
rows={2}
className="w-full resize-none rounded border border-border bg-background px-2 py-1 text-[11px] leading-4 text-foreground outline-none placeholder:text-muted-foreground/60 focus:border-primary/50"
/>
</label>
</div>
{/* 岗位协同从专用入口进来时才展示。使用 TDesign Select,未分配不会阻塞普通圆桌使用。 */}