fix(v13): V13 审计 6 项修复 — TrajectoryRecorder注册 + industryStore接入 + 知识搜索 + webhook标注 + structured UI + persistent注释
FIX-01: TrajectoryRecorderMiddleware 注册到 create_middleware_chain() (@650优先级) FIX-02: industryStore 接入 ButlerPanel 行业专长展示 + 自动拉取 FIX-03: 桌面端知识库搜索 saas-knowledge mixin + VikingPanel SaaS KB UI FIX-04: webhook 迁移标注 deprecated + 添加 down migration 注释 FIX-05: Admin Knowledge 添加结构化数据 Tab (CRUD + 行浏览) FIX-06: PersistentMemoryStore 精化 dead_code 标注 (完整迁移留后续) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useButlerInsights } from '../../hooks/useButlerInsights';
|
||||
import { useChatStore } from '../../store/chatStore';
|
||||
import { useIndustryStore } from '../../store/industryStore';
|
||||
import { InsightsSection } from './InsightsSection';
|
||||
import { ProposalsSection } from './ProposalsSection';
|
||||
import { MemorySection } from './MemorySection';
|
||||
@@ -12,8 +13,16 @@ interface ButlerPanelProps {
|
||||
export function ButlerPanel({ agentId }: ButlerPanelProps) {
|
||||
const { painPoints, proposals, loading, error, refresh } = useButlerInsights(agentId);
|
||||
const messageCount = useChatStore((s) => s.messages.length);
|
||||
const { accountIndustries, configs, lastSynced, isLoading: industryLoading, fetchIndustries } = useIndustryStore();
|
||||
const [analyzing, setAnalyzing] = useState(false);
|
||||
|
||||
// Auto-fetch industry configs once per session
|
||||
useEffect(() => {
|
||||
if (accountIndustries.length === 0 && !industryLoading) {
|
||||
fetchIndustries().catch(() => {/* SaaS unavailable — ignore */});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const hasData = (painPoints?.length ?? 0) > 0 || (proposals?.length ?? 0) > 0;
|
||||
const canAnalyze = messageCount >= 2;
|
||||
|
||||
@@ -100,6 +109,45 @@ export function ButlerPanel({ agentId }: ButlerPanelProps) {
|
||||
</h3>
|
||||
<MemorySection agentId={agentId} />
|
||||
</div>
|
||||
|
||||
{/* Industry section */}
|
||||
{accountIndustries.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
||||
行业专长
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{accountIndustries.map((item) => {
|
||||
const config = configs[item.industry_id];
|
||||
const keywords = config?.keywords ?? [];
|
||||
return (
|
||||
<div key={item.industry_id} className="rounded-lg border border-gray-200 dark:border-gray-700 p-2.5">
|
||||
<div className="text-xs font-medium text-gray-800 dark:text-gray-200">
|
||||
{item.industry_name || item.industry_id}
|
||||
</div>
|
||||
{keywords.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 mt-1.5">
|
||||
{keywords.slice(0, 8).map((kw) => (
|
||||
<span key={kw} className="inline-block text-[10px] px-1.5 py-0.5 rounded bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400">
|
||||
{kw}
|
||||
</span>
|
||||
))}
|
||||
{keywords.length > 8 && (
|
||||
<span className="text-[10px] text-gray-400">+{keywords.length - 8}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{lastSynced && (
|
||||
<div className="text-[10px] text-gray-400 dark:text-gray-500">
|
||||
同步于 {new Date(lastSynced).toLocaleString('zh-CN')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user