完整实现 Tauri + Vanilla JS 轻量级截图工具 Phase 1 - 项目搭建 - Tauri 2.x 项目初始化 - Vite 前端项目搭建 - 基础 UI 框架(CSS 变量、组件库) - 构建配置优化 Phase 2 - 核心截图功能 - 全屏/区域/窗口截图 - 截图预览和管理 - 文件命名和缩略图 - 全局快捷键集成 Phase 3 - 上传与存储 - 多图床上传(GitHub/Imgur/自定义) - 配置管理系统 - SQLite 数据库 Phase 4 - OCR 集成 - 云端 OCR(百度/腾讯云) - 插件管理系统 - 本地 OCR 插件(Go) - OCR 结果处理 Phase 5 - AI 分类系统 - Claude/OpenAI API 集成 - Prompt 模板引擎 - 模板管理界面 - 自动分类流程 Phase 6 - 历史记录与管理 - 图库视图(网格/列表) - 搜索与筛选 - 批量操作 - 导出功能(JSON/CSV/ZIP) Phase 7 - 打包与发布 - 多平台构建配置 - CI/CD 工作流 - 图标和资源 - 安装包配置 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
478 lines
10 KiB
Markdown
478 lines
10 KiB
Markdown
# Phase 5 - AI 分类系统集成指南
|
|
|
|
本文档说明如何将 Phase 5 实现的 AI 分类功能集成到主应用中。
|
|
|
|
## 1. 主应用集成 (App.svelte 或主要入口)
|
|
|
|
### 1.1 导入组件和 Store
|
|
|
|
```svelte
|
|
<script>
|
|
import { onMount } from 'svelte';
|
|
import { initializeAiStore } from './store/ai';
|
|
import AiConfigView from './components/views/AiConfigView.svelte';
|
|
import AiTemplatesView from './components/views/AiTemplatesView.svelte';
|
|
|
|
// 状态
|
|
let showAiConfig = false;
|
|
let showAiTemplates = false;
|
|
|
|
onMount(() => {
|
|
// 初始化 AI store
|
|
initializeAiStore();
|
|
});
|
|
</script>
|
|
```
|
|
|
|
### 1.2 添加菜单项
|
|
|
|
在主界面菜单中添加:
|
|
|
|
```svelte
|
|
<nav>
|
|
<!-- 现有菜单项 -->
|
|
<button on:click={() => showAiConfig = true}>
|
|
⚙️ AI 配置
|
|
</button>
|
|
<button on:click={() => showAiTemplates = true}>
|
|
📝 模板管理
|
|
</button>
|
|
</nav>
|
|
|
|
<!-- Modals -->
|
|
{#if showAiConfig}
|
|
<AiConfigView onClose={() => showAiConfig = false} />
|
|
{/if}
|
|
|
|
{#if showAiTemplates}
|
|
<AiTemplatesView onClose={() => showAiTemplates = false} />
|
|
{/if}
|
|
```
|
|
|
|
## 2. 记录详情页面集成
|
|
|
|
### 2.1 在记录详情中添加分类功能
|
|
|
|
```svelte
|
|
<script>
|
|
import AutoClassifyView from './components/views/AutoClassifyView.svelte';
|
|
import { getClassification } from './api/ai';
|
|
|
|
export let record; // 从父组件传入的记录
|
|
|
|
let classification = null;
|
|
let showClassify = false;
|
|
|
|
// 加载已保存的分类
|
|
async function loadClassification() {
|
|
if (record?.id) {
|
|
classification = await getClassification(record.id);
|
|
}
|
|
}
|
|
|
|
$: if (record?.id) {
|
|
loadClassification();
|
|
}
|
|
</script>
|
|
|
|
<div class="record-detail">
|
|
<!-- 现有内容 -->
|
|
|
|
<!-- 分类按钮 -->
|
|
<button on:click={() => showClassify = !showClassify}>
|
|
{showClassify ? '隐藏' : '显示'} AI 分类
|
|
</button>
|
|
|
|
<!-- AI 分类组件 -->
|
|
{#if showClassify}
|
|
<AutoClassifyView
|
|
recordId={record.id}
|
|
content={record.content}
|
|
ocrText={record.metadata?.ocr_text}
|
|
onClassified={(result) => {
|
|
console.log('分类结果:', result);
|
|
loadClassification();
|
|
}}
|
|
/>
|
|
{/if}
|
|
|
|
<!-- 显示已保存的分类 -->
|
|
{#if classification}
|
|
<div class="classification-info">
|
|
<h4>分类信息</h4>
|
|
<p>分类: {classification.category}</p>
|
|
{#if classification.subcategory}
|
|
<p>子分类: {classification.subcategory}</p>
|
|
{/if}
|
|
<p>置信度: {Math.round(classification.confidence * 100)}%</p>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
```
|
|
|
|
## 3. 全局快捷键集成
|
|
|
|
### 3.1 添加快捷键
|
|
|
|
在主应用中添加快捷键:
|
|
|
|
```svelte
|
|
<script>
|
|
import { onMount, onDestroy } from 'svelte';
|
|
|
|
let handleKeyDown = (e) => {
|
|
// Ctrl/Cmd + K: 快速分类
|
|
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
e.preventDefault();
|
|
// 触发当前选中记录的分类
|
|
triggerClassification();
|
|
}
|
|
|
|
// Ctrl/Cmd + Shift + A: 打开 AI 配置
|
|
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'A') {
|
|
e.preventDefault();
|
|
showAiConfig = true;
|
|
}
|
|
};
|
|
|
|
onMount(() => {
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
});
|
|
|
|
onDestroy(() => {
|
|
window.removeEventListener('keydown', handleKeyDown);
|
|
});
|
|
</script>
|
|
```
|
|
|
|
## 4. 自动分类触发
|
|
|
|
### 4.1 截图后自动分类
|
|
|
|
在截图完成后触发分类:
|
|
|
|
```svelte
|
|
<script>
|
|
import { classification } from './store/ai';
|
|
|
|
async function handleScreenshot(screenshot) {
|
|
// 保存记录
|
|
const record = await saveRecord(screenshot);
|
|
|
|
// 如果 AI 已配置,自动分类
|
|
if ($aiConfig.configured && autoClassifyEnabled) {
|
|
const variables = {
|
|
content_type: 'image',
|
|
image_path: screenshot.file_path,
|
|
};
|
|
|
|
// 如果有 OCR 结果,添加到变量
|
|
if (screenshot.ocr_text) {
|
|
variables.ocr_text = screenshot.ocr_text;
|
|
}
|
|
|
|
try {
|
|
await classification.classify(
|
|
record.id,
|
|
'general', // 使用通用模板
|
|
variables,
|
|
true // 流式模式
|
|
);
|
|
} catch (error) {
|
|
console.error('自动分类失败:', error);
|
|
}
|
|
}
|
|
|
|
return record;
|
|
}
|
|
</script>
|
|
```
|
|
|
|
### 4.2 剪贴板监听自动分类
|
|
|
|
```svelte
|
|
<script>
|
|
import { watchClipboard } from './utils/clipboard';
|
|
|
|
async function handleClipboardChange(content) {
|
|
// 保存记录
|
|
const record = await saveRecord({
|
|
type: 'text',
|
|
content: content,
|
|
});
|
|
|
|
// 自动分类
|
|
if ($aiConfig.configured && autoClassifyEnabled) {
|
|
await classification.classify(
|
|
record.id,
|
|
'general',
|
|
{
|
|
content_type: 'text',
|
|
content: content,
|
|
},
|
|
false // 非流式
|
|
);
|
|
}
|
|
}
|
|
|
|
onMount(() => {
|
|
watchClipboard(handleClipboardChange);
|
|
});
|
|
</script>
|
|
```
|
|
|
|
## 5. 配置持久化
|
|
|
|
### 5.1 保存用户偏好
|
|
|
|
```typescript
|
|
// 在 store/settings.ts 中添加
|
|
|
|
export const autoClassifyEnabled = writable(false);
|
|
export const defaultTemplateId = writable('general');
|
|
export const minConfidence = writable(0.7);
|
|
|
|
// 加载设置
|
|
export async function loadAiSettings() {
|
|
const enabled = await getSetting('auto_classify_enabled');
|
|
if (enabled !== null) {
|
|
autoClassifyEnabled.set(enabled === 'true');
|
|
}
|
|
|
|
const template = await getSetting('default_template_id');
|
|
if (template) {
|
|
defaultTemplateId.set(template);
|
|
}
|
|
|
|
const confidence = await getSetting('min_confidence');
|
|
if (confidence) {
|
|
minConfidence.set(parseFloat(confidence));
|
|
}
|
|
}
|
|
|
|
// 保存设置
|
|
export async function saveAiSettings() {
|
|
await setSetting('auto_classify_enabled', String($autoClassifyEnabled));
|
|
await setSetting('default_template_id', $defaultTemplateId);
|
|
await setSetting('min_confidence', String($minConfidence));
|
|
}
|
|
```
|
|
|
|
## 6. 通知和反馈
|
|
|
|
### 6.1 分类完成通知
|
|
|
|
```svelte
|
|
<script>
|
|
import { notification } from './store/notification';
|
|
|
|
function handleClassificationComplete(result) {
|
|
notification.show({
|
|
type: 'success',
|
|
title: '分类完成',
|
|
message: `已分类为: ${result.category}`,
|
|
duration: 3000,
|
|
});
|
|
}
|
|
|
|
function handleClassificationError(error) {
|
|
notification.show({
|
|
type: 'error',
|
|
title: '分类失败',
|
|
message: error,
|
|
duration: 5000,
|
|
});
|
|
}
|
|
</script>
|
|
```
|
|
|
|
## 7. 分类统计显示
|
|
|
|
### 7.1 在侧边栏显示统计
|
|
|
|
```svelte
|
|
<script>
|
|
import { getClassificationStats } from './api/ai';
|
|
|
|
let categoryStats = [];
|
|
|
|
async function loadStats() {
|
|
categoryStats = await getClassificationStats();
|
|
}
|
|
|
|
onMount(() => {
|
|
loadStats();
|
|
// 定期更新
|
|
const interval = setInterval(loadStats, 60000);
|
|
return () => clearInterval(interval);
|
|
});
|
|
</script>
|
|
|
|
<aside>
|
|
<h3>分类统计</h3>
|
|
<ul>
|
|
{#each categoryStats as [category, count]}
|
|
<li>{category}: {count}</li>
|
|
{/each}
|
|
</ul>
|
|
</aside>
|
|
```
|
|
|
|
## 8. 完整集成示例
|
|
|
|
### 主应用结构
|
|
|
|
```svelte
|
|
<!-- App.svelte -->
|
|
<script>
|
|
import { onMount } from 'svelte';
|
|
import { initializeAiStore, aiConfig } from './store/ai';
|
|
import AiConfigView from './components/views/AiConfigView.svelte';
|
|
import AiTemplatesView from './components/views/AiTemplatesView.svelte';
|
|
import RecordList from './components/RecordList.svelte';
|
|
|
|
let showAiConfig = false;
|
|
let showAiTemplates = false;
|
|
|
|
onMount(() => {
|
|
initializeAiStore();
|
|
});
|
|
</script>
|
|
|
|
<div class="app">
|
|
<!-- 侧边栏 -->
|
|
<aside>
|
|
<nav>
|
|
<a href="#records">📋 记录</a>
|
|
<a href="#screenshots">📸 截图</a>
|
|
<button on:click={() => showAiConfig = true}>
|
|
⚙️ AI 配置
|
|
</button>
|
|
<button on:click={() => showAiTemplates = true}>
|
|
📝 模板管理
|
|
</button>
|
|
</nav>
|
|
|
|
<!-- AI 状态指示器 -->
|
|
<div class="ai-status" class:configured={$aiConfig.configured}>
|
|
{$aiConfig.configured ? '✅ AI 已启用' : '⚠️ AI 未配置'}
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- 主内容 -->
|
|
<main>
|
|
<RecordList />
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Modals -->
|
|
{#if showAiConfig}
|
|
<AiConfigView onClose={() => showAiConfig = false} />
|
|
{/if}
|
|
|
|
{#if showAiTemplates}
|
|
<AiTemplatesView onClose={() => showAiTemplates = false} />
|
|
{/if}
|
|
|
|
<style>
|
|
.ai-status {
|
|
padding: 10px;
|
|
margin-top: 20px;
|
|
border-radius: 6px;
|
|
text-align: center;
|
|
background: #f39c12;
|
|
color: white;
|
|
}
|
|
|
|
.ai-status.configured {
|
|
background: #27ae60;
|
|
}
|
|
</style>
|
|
```
|
|
|
|
## 9. 测试检查清单
|
|
|
|
在集成完成后,进行以下测试:
|
|
|
|
### 功能测试
|
|
- [ ] AI 配置界面可以打开
|
|
- [ ] Claude API Key 可以保存
|
|
- [ ] OpenAI API Key 可以保存
|
|
- [ ] 模板列表可以正常显示
|
|
- [ ] 可以创建新模板
|
|
- [ ] 可以编辑现有模板
|
|
- [ ] 可以删除自定义模板
|
|
- [ ] 可以测试模板渲染
|
|
- [ ] 可以导出模板
|
|
- [ ] 可以导入模板
|
|
- [ ] 分类功能正常工作
|
|
- [ ] 流式分类实时显示
|
|
- [ ] 分类结果正确保存
|
|
- [ ] 分类历史可以查看
|
|
- [ ] 分类统计正确显示
|
|
|
|
### 集成测试
|
|
- [ ] 截图后自动触发分类
|
|
- [ ] 剪贴板监听自动分类
|
|
- [ ] 快捷键正常工作
|
|
- [ ] 通知正确显示
|
|
- [ ] 错误正确处理
|
|
|
|
### 性能测试
|
|
- [ ] 大文本分类正常
|
|
- [ ] 批量分类不卡顿
|
|
- [ ] API 限流正常工作
|
|
- [ ] 内存占用合理
|
|
|
|
## 10. 故障排查
|
|
|
|
### 问题 1: AI 配置保存失败
|
|
**检查:**
|
|
- 数据库连接正常
|
|
- API Key 格式正确
|
|
- 网络连接正常
|
|
|
|
### 问题 2: 分类失败
|
|
**检查:**
|
|
- AI 提供商已配置
|
|
- API Key 有效
|
|
- 模板存在且有效
|
|
- 变量值正确
|
|
|
|
### 问题 3: 流式响应不显示
|
|
**检查:**
|
|
- 事件监听器正确设置
|
|
- 窗口对象正确传递
|
|
- Tauri 事件系统正常
|
|
|
|
## 11. 下一步
|
|
|
|
集成完成后,可以考虑以下增强:
|
|
|
|
1. **自动化工作流**
|
|
- 基于分类自动打标签
|
|
- 基于分类自动归档
|
|
- 基于分类触发通知
|
|
|
|
2. **高级功能**
|
|
- 批量分类
|
|
- 定时分类任务
|
|
- 分类规则引擎
|
|
|
|
3. **用户体验**
|
|
- 分类建议
|
|
- 快速操作
|
|
- 可视化统计
|
|
|
|
4. **性能优化**
|
|
- 结果缓存
|
|
- 请求队列
|
|
- 批处理
|
|
|
|
---
|
|
|
|
如有问题,请参考:
|
|
- [Phase 5 实现总结](./PHASE5_SUMMARY.md)
|
|
- [API 文档](../src/api/ai.ts)
|
|
- [Store 文档](../src/store/ai.ts)
|