AI可视化增强分析助手开发实践

内容为 AI 基于AI 可视增强分析助手的源码生成。

前言

在 AI 浪潮席卷各行各业的今天,我们团队决定探索一个有趣的方向:能否让用户在浏览网页时,只需选中一段文字,就能自动获得智能分析和可视化增强?经过摸索和实践,我们成功构建了一个 AI 可视化增强分析助手的 Chrome 扩展。

这篇文章想和大家分享我们在这个项目中的技术选型思考、架构设计实践,以及踩过的那些坑。如果你对 AI 与前端技术的结合、Chrome 扩展开发、或者可视化系统架构感兴趣,希望这些经验能对你有所启发。****

背景与需求

业务场景的思考

在我们日常的工作中,经常遇到这样的场景:阅读技术文档时需要快速理解复杂概念、分析财经报告时希望数据能自动图表化、研究学术论文时想要提取关键信息并结构化展示。传统的做法是复制文本到其他工具中进行处理,这个过程既繁琐又打断了阅读的连贯性。

我们想要解决的核心问题是:如何让用户在不离开当前网页的情况下,获得文本内容的智能分析和可视化增强?

需求分析

经过深入调研,我们明确了几个关键需求:

  1. 无感知集成:用户无需安装额外软件或跳转页面,在任何网页上都能使用
  2. 智能化处理:能够理解文本语义并自动选择合适的分析方法
  3. 多样化呈现:支持图表、思维导图、时间线等多种可视化形式
  4. 实时反馈:用户能够看到 AI 的思考过程和处理进度
  5. 交互增强:可视化结果支持进一步的交互和关联分析

为什么选择 Chrome 扩展

在技术路径选择上,我们考虑了几种方案:

  • Web 应用:需要用户手动复制粘贴,体验不够顺畅
  • 桌面应用:开发成本高,跨平台兼容性差
  • Chrome 扩展:能够无缝集成到浏览器中,提供最佳的用户体验

最终我们选择了 Chrome 扩展方案,因为它能够:

  • 直接访问网页内容,监听用户的文本选择操作
  • 通过 content script 与网页交互,实现浮动工具栏
  • 利用 side panel 提供完整的分析界面
  • 支持后台服务处理复杂的 AI 分析任务

技术方案

整体架构设计

在架构设计上,我们采用了分层解耦的思路,将整个系统分为三个核心层:

graph TB
    subgraph "用户交互层"
        UI[Content UI - 文本选择监听]
        TB[ToolBar - 分析配置]
        SP[Side Panel - 结果展示]
    end

    subgraph "消息通信层"
        CS[Content Script]
        BG[Background Script]
        MSG[Runtime Message API]
    end

    subgraph "服务集成层"
        SR[Stream Request]
        API[AI Analysis API]
        VIS[Visualization Engine]
    end

    UI --> TB
    TB --> BG
    BG --> SP
    SP --> SR
    SR --> API
    API --> VIS
    VIS --> SP

技术选型考虑

在技术栈选择上,我们遵循了成熟稳定、生态丰富的原则:

前端框架选择:React + TypeScript

  • React 的组件化思维很适合扩展的模块化架构
  • TypeScript 提供类型安全,在复杂的消息传递中尤其重要
  • 生态成熟,第三方可视化库支持良好

构建工具选择:Vite + Turbo + pnpm

  • Vite 提供快速的开发体验,支持热更新
  • Turbo 解决单仓库多包的构建性能问题
  • pnpm 的 workspace 管理依赖更高效

可视化库选择:多库组合

1
2
3
4
5
6
7
8
// 数据图表 - AIGCDataVis(自研)
import { AIGCDataVis } from './libs/aigc-data-vis';

// NTV富文本 - AntV AVA
import { NarrativeTextVis } from '@antv/ava-react';

// 流程图 - Mermaid
import { Mermaid } from 'mdx-mermaid/Mermaid';

我们没有选择单一的可视化库,而是根据不同场景选择最适合的工具。这样虽然增加了复杂性,但能提供更好的视觉效果。

消息传递架构

Chrome 扩展的消息传递是整个系统的神经中枢,我们设计了一套清晰的消息流:

sequenceDiagram
    participant User as 用户
    participant ContentUI as Content UI
    participant Background as Background Script
    participant SidePanel as Side Panel
    participant Backend as AI服务

    User->>ContentUI: 选择文本
    ContentUI->>Background: sendMessage(OPEN_SIDE_PANEL)
    Background->>SidePanel: 转发消息
    SidePanel->>Backend: 发起AI分析
    Backend-->>SidePanel: 流式返回结果
    SidePanel->>User: 展示可视化

深入实现

文本选择监听的精巧设计

文本选择监听是整个系统的入口,看似简单,实际上有很多细节需要处理。我们在pages/content-ui/src/matches/all/App.tsx中实现了核心逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 处理文本选择的核心函数
const handleSelection = () => {
const selection = window.getSelection();
if (!selection || selection.isCollapsed) {
setActivated(false);
return;
}

const text = selection.toString().trim();
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();

setSelectedText(text);
setActivated(true);

// 计算工具栏最佳位置,避免超出视窗
setPosition({
top: Math.max(10, rect.top - 50 + window.scrollY),
left: Math.min(window.innerWidth - 500, rect.left + window.scrollX),
});
};

这里有几个关键的处理细节:

  1. 空选择判断selection.isCollapsed能准确判断是否有实际选中内容
  2. 位置计算:考虑滚动偏移和视窗边界,确保工具栏不会超出可视区域
  3. 防抖处理:避免用户快速选择时产生过多的事件触发

流式数据处理的技术实现

AI 分析的一个核心特点是处理时间较长,用户需要实时的进度反馈。我们基于 Server-Sent Events 实现了流式数据处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// pages/side-panel/src/lib/streamRequest.ts
class StreamRequest {
startRequest(url: string, params: Record<string, unknown>) {
fetchEventSource(url, {
method: this._method,
headers: this._headers,
body: JSON.stringify(params),
onmessage: (event) => {
// 智能解析JSON和纯文本
if (this._isParse && isJSON(event.data)) {
const data = JSON.parse(event.data);
this.successHandler(data);
} else {
this.successHandler(event.data);
}
},
onopen: this.startHandler,
onerror: this.errorHandler,
onclose: this.closeHandler,
openWhenHidden: true, // 支持后台运行
});
}
}

这个实现的巧妙之处在于:

  • 自适应解析:自动识别 JSON 和纯文本格式
  • 生命周期管理:完整的开始、成功、错误、关闭回调
  • 后台支持openWhenHidden: true确保用户切换标签页时不会中断

三阶段分析流程设计

我们将 AI 分析过程分为三个阶段,每个阶段都有明确的职责:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 在AnalysisPanel中的状态管理
const [stepStatus, setStepStatus] = useState<
'thinking' | 'tools' | 'execute' | 'finish'
>('thinking');

// 阶段1:Thinking - AI思考过程
const chatThinkingStartCallback = () => {
setChatList((pre) => [
...pre,
{
sessionId: curSessionId.current,
type: 'thinking',
content: '',
},
]);
};

// 阶段2:Tools - 工具选择分析
const toolsSuccessCallback = (message) => {
const toolCalls = message?.result?.output?.toolCalls || [];
if (toolCalls.length > 0) {
fetchToolExecution(toolCalls);
}
};

// 阶段3:Execute - 并行执行工具
const fetchToolExecution = async (toolCalls) => {
setStepStatus('execute');
const tasks = toolCalls.map((tool) => executeToolTask(tool));
await Promise.all(tasks); // 并行执行提升效率
setStepStatus('finish');
};

这种设计的优势是:

  • 进度透明:用户清楚地知道当前处理阶段
  • 并行优化:最终执行阶段支持多工具并行处理
  • 错误隔离:单个工具失败不影响其他工具的执行

可视化渲染系统的模块化设计

可视化是整个系统的核心亮点,我们设计了一套可插拔的渲染系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// pages/side-panel/src/components/rendererItem/index.tsx
const RendererItem = ({ type, data, ...props }) => {
switch (type) {
case 'chart':
return <ChartRenderer option={data} {...props} />;
case 'nvt':
return <NvtRenderer spec={data} {...props} />;
case 'mermaid':
return <MermaidRenderer data={data} {...props} />;
case 'storyline':
return <StorylineRenderer data={data} {...props} />;
case 'textToCard':
return <TextInformationVisualRenderer data={data} {...props} />;
case 'financialRag':
return <HtmlRenderer html={data} {...props} />;
default:
return <div>不支持的可视化类型: {type}</div>;
}
};

每个渲染器都有独立的实现,以图表渲染器为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// pages/side-panel/src/components/base-visual-item/chart/index.tsx
export const ChartRenderer: React.FC<ChartRendererProps> = ({
option,
style,
className,
insight = [],
}) => {
const chartRef = useRef<HTMLDivElement>(null);
const chartInstanceRef = useRef<object[] | null>(null);

// 使用useMemo稳定insight引用,避免不必要的重渲染
const stableInsight = useMemo(() => insight, [JSON.stringify(insight)]);

useEffect(() => {
if (!chartRef.current) return;

// 清理之前的实例,避免内存泄漏
if (chartInstanceRef.current) {
AIGCDataVis.destroy(chartRef.current);
chartInstanceRef.current = null;
}

const chartOption = { ...option };
chartOption.view.main.dvInsight = stableInsight;
chartInstanceRef.current = AIGCDataVis.render(
chartRef.current,
chartOption
);

// 组件卸载时清理资源
return () => {
if (chartRef.current) {
AIGCDataVis.destroy(chartRef.current);
}
};
}, [option, stableInsight]);

return (
<div
ref={chartRef}
className={className}
style={{ width: '100%', height: 320, ...style }}
/>
);
};

这个实现考虑了几个重要的细节:

  • 内存管理:确保图表实例的正确创建和销毁
  • 引用稳定:使用useMemo避免因为对象引用变化导致的重渲染
  • 资源清理:在组件卸载时正确清理第三方库的资源

高亮交互功能的实现

用户在查看可视化结果时,可能需要了解某个元素与原始文本的关联。我们实现了智能高亮功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 高亮请求的防抖处理
const sendHighlightRequest = async (selectedText: string) => {
try {
const userQueryInfo = {
query: chatList.filter(
(item) =>
item.sessionId === curSessionId.current && item.type === 'user'
)[0].content as string,
};

const dataToVisOption =
chatList
.filter(
(item) =>
item.sessionId === curSessionId.current && item.type === 'ai'
)
.map((item) =>
((item.content as AnalysisResult)?.visualization as IVisualization[])
?.filter((item) => item.type === 'chart')
?.map((item) => ({
config: (item.data as any).view.main.layers,
data: (item.data as any).data[0].values,
toolId: item.toolId,
}))
)
.flat() || [];

const response = await highlightTool({
lastQueryInfo: userQueryInfo,
tools: toolCallsInfo.current.map((item) => ({
id: item.id,
name: item.name,
...(item.name === 'dataToVis'
? {
config: dataToVisOption.find((opt) => opt.toolId === item.id)
?.config,
data: dataToVisOption.find((opt) => opt.toolId === item.id)?.data,
}
: {}),
})),
selectedContent: selectedText,
});

handleHighlightResponse(response.data);
} catch (error) {
console.error('高亮请求错误:', error);
}
};

// 防抖处理的选择监听
const startSelectionListener = () => {
const handleSelection = () => {
if (highlightDebounceTimer.current) {
clearTimeout(highlightDebounceTimer.current);
}

highlightDebounceTimer.current = setTimeout(() => {
const selection = window.getSelection();
if (selection && selection.toString().trim() && toolsCompleted) {
sendHighlightRequest(selection.toString().trim());
}
}, 300); // 300ms防抖
};

document.addEventListener('selectionchange', handleSelection);
};

这个功能的技术亮点:

  • 上下文关联:将用户当前选择的内容与之前的分析结果建立关联
  • 防抖优化:300ms 防抖避免频繁的 API 调用
  • 状态管理:只在工具执行完成后才启用高亮功能

踩坑经验

Chrome 扩展权限配置的陷阱

在开发初期,我们遇到了权限配置的问题。Chrome 扩展的 manifest.json 配置看似简单,实际上有很多细节:

1
2
3
4
5
6
7
8
9
10
11
12
{
"permissions": ["storage", "scripting", "tabs", "sidePanel"],
"host_permissions": ["<all_urls>"],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"css": ["content.css"],
"run_at": "document_end"
}
]
}

踩过的坑

  1. 权限声明不足:最初没有添加sidePanel权限,导致无法打开侧边栏
  2. 执行时机错误run_at设置为document_start时,DOM 元素还未加载完成
  3. CSS 冲突:content script 的样式会影响网页原有样式,需要使用 CSS 隔离

解决方案

1
2
3
4
5
6
7
/* 使用CSS前缀避免样式冲突 */
.ai-vis-extension-toolbar {
all: initial;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto;
position: fixed;
z-index: 999999;
}

流式数据处理的内存泄漏

在实现流式数据处理时,我们遇到了内存泄漏的问题。用户频繁操作时,旧的请求没有正确清理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 错误的实现方式
const startRequest = () => {
fetchEventSource(url, options); // 没有保存引用,无法取消
};

// 正确的实现方式
class StreamRequest {
private controller: AbortController;

startRequest(url: string, params: any) {
// 取消之前的请求
if (this.controller) {
this.controller.abort();
}

this.controller = new AbortController();

fetchEventSource(url, {
...options,
signal: this.controller.signal, // 支持请求取消
});
}

stopRequest() {
if (this.controller) {
this.controller.abort();
}
}
}

第三方可视化库的集成挑战

不同的可视化库有不同的 API 风格和生命周期管理方式,集成时需要统一处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 统一的生命周期管理
useEffect(() => {
let instance: any = null;

const initVisualization = async () => {
if (type === 'chart') {
instance = AIGCDataVis.render(containerRef.current, option);
} else if (type === 'mermaid') {
// Mermaid需要异步初始化
const mermaid = await import('mermaid');
mermaid.initialize({ theme: 'default' });
instance = { destroy: () => mermaid.destroy() };
}
};

initVisualization();

return () => {
// 统一的清理逻辑
if (instance && typeof instance.destroy === 'function') {
instance.destroy();
}
};
}, [type, option]);

消息传递的时序问题

Chrome 扩展的消息传递是异步的,时序问题很容易被忽略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 错误的做法:没有等待side panel准备完成
chrome.sidePanel.open({ windowId: sender.tab.windowId });
chrome.runtime.sendMessage({
action: 'updateSelectedText',
text: request.text,
});

// 正确的做法:延迟发送消息
chrome.sidePanel.open({ windowId: sender.tab.windowId });
setTimeout(() => {
chrome.runtime.sendMessage({
action: 'updateSelectedText',
text: request.text,
});
}, 1000); // 给side panel足够的初始化时间

更好的解决方案是使用 Promise 和消息确认机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在side panel中发送准备完成消息
useEffect(() => {
chrome.runtime.sendMessage({ action: 'sidePanelReady' });
}, []);

// 在background script中等待准备完成
let sidePanelReady = false;
chrome.runtime.onMessage.addListener((request) => {
if (request.action === 'sidePanelReady') {
sidePanelReady = true;
} else if (request.action === 'selectedText' && sidePanelReady) {
// 现在可以安全地发送消息了
chrome.runtime.sendMessage({
action: 'updateSelectedText',
text: request.text,
});
}
});

开发调试的效率优化

Chrome 扩展的调试相比普通 web 应用更复杂,我们总结了一些提升效率的方法:

  1. 热更新配置:使用 HMR 插件避免频繁手动刷新
  2. 日志分类:不同模块使用不同的日志前缀,便于过滤
  3. 错误边界:使用 React Error Boundary 捕获渲染错误
  4. 开发面板:在开发模式下显示调试信息面板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 开发环境的错误边界
class DevErrorBoundary extends React.Component {
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.group('🚨 React Error Boundary');
console.error('Error:', error);
console.error('Error Info:', errorInfo);
console.groupEnd();

// 开发环境下显示详细错误信息
if (process.env.NODE_ENV === 'development') {
this.setState({ error, errorInfo });
}
}
}

资料

技术文档

可视化库资源

开发工具

参考项目


总结

通过这个 AI 可视化增强分析助手项目,我们深度探索了 Chrome 扩展开发、AI 服务集成、可视化系统架构等多个技术领域。项目中的每个技术选择都经过了深入思考和实践验证,遇到的每个问题都成为了宝贵的经验财富。

希望这些实战心得能为同样在探索 AI+前端技术融合的开发者提供一些参考和启发。技术的魅力在于不断地学习、实践、总结和分享,期待与大家继续交流更多的技术见解。