数据结构
本文档详细说明 AI-SideChat 使用的所有数据结构。
Favorite - 收藏条目
单条收藏的完整数据结构。
typescript
interface Favorite {
// 基本信息
id: number // 唯一标识符(时间戳)
timestamp: number // 收藏时间(时间戳)
// 平台信息
site: 'Gemini' | 'ChatGPT' // 平台名称
siteColor: string // 平台主题色 (#8850e5 或 #10a37f)
// 对话信息
convTitle: string // 对话标题
url: string // 对话完整 URL
// 问题信息
question: string // 用户提问的纯文本
questionHash: string // 问题内容的哈希值
bubbleIndex: number // 问题在对话中的索引位置(从 0 开始)
// 回答信息
answer: string // AI 回答的纯文本
answerHtml: string // AI 回答的 HTML
contentHash: string // 回答内容的哈希值
// 标签信息
tags: string[] // 标签数组
}示例数据
json
{
"id": 1706014823456,
"timestamp": 1706014823456,
"site": "Gemini",
"siteColor": "#8850e5",
"convTitle": "Python 文件操作教程",
"url": "https://gemini.google.com/app/abc123",
"question": "如何使用 Python 读取 CSV 文件?",
"questionHash": "123456789",
"bubbleIndex": 2,
"answer": "在 Python 中读取 CSV 文件有多种方法...",
"answerHtml": "<p>在 Python 中读取 CSV 文件有多种方法...</p>",
"contentHash": "987654321",
"tags": ["Python", "CSV", "文件操作"]
}Platform - 平台配置
AI 平台的配置信息。
typescript
interface Platform {
name: 'Gemini' | 'ChatGPT' // 平台名称
color: string // 主题色
selectors: {
userBubble: string // 用户气泡选择器
answer: string // AI 回答选择器
convTitle: string // 对话标题选择器
}
}Gemini 配置
javascript
{
name: 'Gemini',
color: '#8850e5',
selectors: {
userBubble: 'user-query',
answer: '.model-response-text, message-content, model-response',
convTitle: '.conversation-title, .conversation'
}
}ChatGPT 配置
javascript
{
name: 'ChatGPT',
color: '#10a37f',
selectors: {
userBubble: '[data-message-author-role="user"]',
answer: '.markdown',
convTitle: 'nav a'
}
}Storage - 存储结构
Chrome 本地存储的数据结构。
typescript
interface StorageData {
aiClipData: Favorite[] // 所有收藏的数组
}存储示例
json
{
"aiClipData": [
{
"id": 1706014823456,
"question": "...",
// ... 其他字段
},
{
"id": 1706014823457,
"question": "...",
// ... 其他字段
}
]
}Message - 消息结构
扩展内部通信的消息格式。
PerformJumpMessage
执行跳转定位的消息。
typescript
interface PerformJumpMessage {
type: 'PERFORM_JUMP'
data: {
questionHash: string
bubbleIndex: number
}
}UpdateFavoritesMessage
更新收藏列表的消息。
typescript
interface UpdateFavoritesMessage {
type: 'UPDATE_FAVORITES'
}OpenTabMessage
打开新标签页的消息。
typescript
interface OpenTabMessage {
type: 'OPEN_TAB'
url: string
}SessionStorage - 会话存储
用于跨页状态保持的数据。
PendingJump
待执行的跳转信息。
typescript
interface PendingJump {
questionHash: string
bubbleIndex: number
timestamp: number
}存储键名:aiClip_pendingJump
javascript
sessionStorage.setItem('aiClip_pendingJump', JSON.stringify({
questionHash: '123456',
bubbleIndex: 2,
timestamp: Date.now()
}))FilterState - 筛选状态
悬浮坞的筛选状态。
typescript
interface FilterState {
keyword: string // 搜索关键词
selectedTags: string[] // 选中的标签
selectedSite: 'Gemini' | 'ChatGPT' | 'all' // 选中的站点
}示例
javascript
{
keyword: 'Python',
selectedTags: ['教程', 'API'],
selectedSite: 'Gemini'
}PreviewState - 预览状态
预览窗口的状态。
typescript
interface PreviewState {
visible: boolean // 是否可见
pinned: boolean // 是否钉住
position: {
x: number // X 坐标
y: number // Y 坐标
}
item: Favorite | null // 预览的收藏条目
}TagStats - 标签统计
标签的使用统计。
typescript
interface TagStats {
[tag: string]: number // 标签名 -> 使用次数
}示例
javascript
{
"Python": 15,
"JavaScript": 10,
"教程": 8,
"API": 6
}计算方法
javascript
function calculateTagStats(favorites) {
const stats = {}
favorites.forEach(fav => {
fav.tags.forEach(tag => {
stats[tag] = (stats[tag] || 0) + 1
})
})
return stats
}数据验证
验证收藏数据
javascript
function validateFavorite(data) {
const required = [
'id', 'timestamp', 'site', 'siteColor',
'convTitle', 'url', 'question', 'questionHash',
'bubbleIndex', 'answer', 'answerHtml', 'contentHash'
]
for (const field of required) {
if (!(field in data)) {
throw new Error(`Missing required field: ${field}`)
}
}
if (!Array.isArray(data.tags)) {
throw new Error('tags must be an array')
}
if (!['Gemini', 'ChatGPT'].includes(data.site)) {
throw new Error('Invalid site value')
}
return true
}数据迁移
版本兼容
当数据结构升级时,需要迁移旧数据:
javascript
async function migrateData() {
const data = await chrome.storage.local.get('aiClipData')
const favorites = data.aiClipData || []
// 检查版本
const version = await getDataVersion()
if (version < 2) {
// 迁移到 v2
favorites.forEach(fav => {
if (!fav.tags) {
fav.tags = []
}
})
}
if (version < 3) {
// 迁移到 v3
favorites.forEach(fav => {
if (!fav.contentHash) {
fav.contentHash = simpleHash(fav.answer)
}
})
}
await chrome.storage.local.set({ aiClipData: favorites })
await setDataVersion(3)
}数据备份
导出格式
typescript
interface BackupData {
version: string // 备份格式版本
timestamp: number // 备份时间
favorites: Favorite[] // 收藏数据
metadata: {
totalCount: number // 总数
tags: string[] // 所有标签
}
}导出示例
javascript
async function exportData() {
const favorites = await getFavorites()
const allTags = [...new Set(favorites.flatMap(f => f.tags))]
const backup = {
version: '1.0',
timestamp: Date.now(),
favorites: favorites,
metadata: {
totalCount: favorites.length,
tags: allTags
}
}
return JSON.stringify(backup, null, 2)
}