HarmonyOS NEXT AI 智能生活助手:会话管理与聊天记录保存

HarmonyOS NEXT AI 智能生活助手:会话管理与聊天记录保存

前言

第 06 篇中,我们实现了流式输出,消息可以实时渲染。但目前的聊天记录是内存级别的------刷新页面或关闭 APP 后,所有对话都会丢失。

会话管理 是任何聊天应用的必备功能。用户希望:找回昨天的对话、继续未完成的讨论、搜索历史消息、删除不再需要的会话。

本文将实现完整的会话管理与持久化方案:

  1. 数据模型:Conversation + ChatMessage 完整设计
  2. 数据库层:PersistenceV2 关系型存储
  3. ConversationRepository:数据仓库模式
  4. 会话列表:历史会话展示
  5. 搜索功能:按标题和内容搜索
  6. 数据迁移:应用升级时的数据兼容

一、数据模型设计

1.1 实体关系图

复制代码
Conversation (1)
    │
    ├── id: string (PK)
    ├── title: string
    ├── model: string
    ├── temperature: number
    ├── createTime: number
    └── updateTime: number
        │
        └── has many ──→ ChatMessage (N)
                            │
                            ├── id: string (PK)
                            ├── convId: string (FK → Conversation.id)
                            ├── role: string
                            ├── content: string
                            ├── createTime: number
                            └── status: string

1.2 完整实体定义

typescript 复制代码
// model/Conversation.ts
export class Conversation {
  id: string = '';
  title: string = '新对话';
  model: string = 'gpt-4o-mini';
  temperature: number = 0.7;
  topP: number = 1;
  maxTokens: number = 4096;
  systemPrompt: string = '';
  createTime: number = Date.now();
  updateTime: number = Date.now();
  messageCount: number = 0;
  isPinned: boolean = false;
  tags: string[] = [];
}

// model/ChatMessage.ts
export class ChatMessage {
  id: string = '';
  convId: string = '';      // 外键
  role: string = 'user';    // user / assistant / system
  content: string = '';
  createTime: number = Date.now();
  status: string = 'sent';  // sending / sent / error
  tokenCount: number = 0;   // 该消息的 Token 数
}

1.3 实体字段说明

Conversation 字段:

字段 类型 约束 说明
id string PRIMARY KEY 会话唯一标识
title string NOT NULL 会话标题,默认"新对话"
model string DEFAULT 使用的 AI 模型
temperature number DEFAULT 0.7 生成温度
topP number DEFAULT 1.0 Top-P 采样
maxTokens number DEFAULT 4096 最大 Token 数
createTime number NOT NULL 创建时间戳
updateTime number NOT NULL 最后更新时间戳
messageCount number DEFAULT 0 消息数量
isPinned boolean DEFAULT false 是否置顶

ChatMessage 字段:

字段 类型 约束 说明
id string PRIMARY KEY 消息唯一标识
convId string FOREIGN KEY 所属会话 ID
role string NOT NULL 角色:user/assistant/system
content string NOT NULL 消息内容
createTime number NOT NULL 创建时间戳
status string DEFAULT 'sent' 状态:sending/sent/error
tokenCount number DEFAULT 0 Token 数量

二、数据库层实现

2.1 DatabaseManager

typescript 复制代码
// database/DatabaseManager.ts
import relationalStore from '@ohos.data.relationalStore';
import { BusinessError } from '@ohos.base';

export class DatabaseManager {
  private static instance: DatabaseManager;
  private store: relationalStore.RdbStore | null = null;
  private readonly DB_NAME = 'HarmonyAI.db';
  private readonly DB_VERSION = 1;

  static getInstance(): DatabaseManager {
    if (!DatabaseManager.instance) {
      DatabaseManager.instance = new DatabaseManager();
    }
    return DatabaseManager.instance;
  }

  // 初始化数据库
  async init(context: Context): Promise<void> {
    const config: relationalStore.StoreConfig = {
      name: this.DB_NAME,
      securityLevel: relationalStore.SecurityLevel.S1
    };

    this.store = await relationalStore.getRdbStore(context, config);
    await this.createTables();
    hilog.info(0x0000, 'DatabaseManager', 'Database initialized');
  }

  // 创建表
  async createTables(): Promise<void> {
    if (!this.store) return;

    // 会话表
    await this.store.executeSql(
      `CREATE TABLE IF NOT EXISTS conversation (
        id TEXT PRIMARY KEY,
        title TEXT NOT NULL DEFAULT '新对话',
        model TEXT DEFAULT 'gpt-4o-mini',
        temperature REAL DEFAULT 0.7,
        topP REAL DEFAULT 1.0,
        maxTokens INTEGER DEFAULT 4096,
        systemPrompt TEXT DEFAULT '',
        createTime INTEGER NOT NULL,
        updateTime INTEGER NOT NULL,
        messageCount INTEGER DEFAULT 0,
        isPinned INTEGER DEFAULT 0,
        tags TEXT DEFAULT '[]'
      )`
    );

    // 消息表
    await this.store.executeSql(
      `CREATE TABLE IF NOT EXISTS chat_message (
        id TEXT PRIMARY KEY,
        convId TEXT NOT NULL,
        role TEXT NOT NULL,
        content TEXT NOT NULL,
        createTime INTEGER NOT NULL,
        status TEXT DEFAULT 'sent',
        tokenCount INTEGER DEFAULT 0,
        FOREIGN KEY (convId) REFERENCES conversation(id) ON DELETE CASCADE
      )`
    );

    // 索引
    await this.store.executeSql(
      'CREATE INDEX IF NOT EXISTS idx_message_convId ON chat_message(convId)'
    );
    await this.store.executeSql(
      'CREATE INDEX IF NOT EXISTS idx_conversation_updateTime ON conversation(updateTime)'
    );
  }

  // 获取 RdbStore
  getStore(): relationalStore.RdbStore {
    if (!this.store) {
      throw new Error('Database not initialized. Call init() first.');
    }
    return this.store;
  }
}

关键设计 :使用 relationalStore(PersistenceV2)作为持久化方案,相比 Preferences 支持复杂查询和事务。外键约束 ON DELETE CASCADE 确保删除会话时级联删除消息。


三、数据仓库层

3.1 ConversationRepository

typescript 复制代码
// repository/ConversationRepository.ts
import relationalStore from '@ohos.data.relationalStore';
import { BusinessError } from '@ohos.base';

export class ConversationRepository {
  private static instance: ConversationRepository;
  private dbManager = DatabaseManager.getInstance();

  static getInstance(): ConversationRepository {
    if (!ConversationRepository.instance) {
      ConversationRepository.instance = new ConversationRepository();
    }
    return ConversationRepository.instance;
  }

  // 创建会话
  async createConversation(): Promise<Conversation> {
    const conv = new Conversation();
    conv.id = this.generateId();
    conv.createTime = Date.now();
    conv.updateTime = Date.now();

    const store = this.dbManager.getStore();
    await store.insert('conversation', {
      id: conv.id,
      title: conv.title,
      model: conv.model,
      temperature: conv.temperature,
      topP: conv.topP,
      maxTokens: conv.maxTokens,
      systemPrompt: conv.systemPrompt,
      createTime: conv.createTime,
      updateTime: conv.updateTime,
      messageCount: conv.messageCount,
      isPinned: conv.isPinned ? 1 : 0,
      tags: JSON.stringify(conv.tags)
    });

    return conv;
  }

  // 获取最近的会话列表
  async getRecentConversations(limit: number = 20): Promise<Conversation[]> {
    const store = this.dbManager.getStore();
    const predicates = new relationalStore.RdbPredicates('conversation');
    predicates.orderByDesc('updateTime');
    predicates.limit(limit);

    const resultSet = await store.query(predicates, [
      'id', 'title', 'model', 'createTime', 'updateTime',
      'messageCount', 'isPinned', 'tags'
    ]);

    const conversations: Conversation[] = [];
    while (resultSet.goToNextRow()) {
      conversations.push(this.rowToConversation(resultSet));
    }
    resultSet.close();

    return conversations;
  }

  // 根据 ID 获取会话
  async getConversation(id: string): Promise<Conversation | null> {
    const store = this.dbManager.getStore();
    const predicates = new relationalStore.RdbPredicates('conversation');
    predicates.equalTo('id', id);

    const resultSet = await store.query(predicates, [
      'id', 'title', 'model', 'temperature', 'topP', 'maxTokens',
      'systemPrompt', 'createTime', 'updateTime', 'messageCount',
      'isPinned', 'tags'
    ]);

    if (resultSet.goToFirstRow()) {
      const conv = this.rowToConversation(resultSet);
      resultSet.close();
      return conv;
    }
    resultSet.close();
    return null;
  }

  // 更新会话标题
  async updateTitle(id: string, title: string): Promise<void> {
    const store = this.dbManager.getStore();
    const predicates = new relationalStore.RdbPredicates('conversation');
    predicates.equalTo('id', id);

    await store.update(
      { title, updateTime: Date.now() },
      predicates
    );
  }

  // 删除会话
  async deleteConversation(id: string): Promise<void> {
    const store = this.dbManager.getStore();
    // 级联删除消息(外键 ON DELETE CASCADE)
    const predicates = new relationalStore.RdbPredicates('conversation');
    predicates.equalTo('id', id);
    await store.delete(predicates);
  }

  // 搜索会话
  async searchConversations(keyword: string): Promise<Conversation[]> {
    const store = this.dbManager.getStore();
    const predicates = new relationalStore.RdbPredicates('conversation');
    predicates.contains('title', keyword);
    predicates.orderByDesc('updateTime');

    const resultSet = await store.query(predicates, [
      'id', 'title', 'createTime', 'updateTime', 'messageCount'
    ]);

    const conversations: Conversation[] = [];
    while (resultSet.goToNextRow()) {
      conversations.push(this.rowToConversation(resultSet));
    }
    resultSet.close();
    return conversations;
  }

  // 行转对象
  private rowToConversation(row: relationalStore.ResultSet): Conversation {
    const conv = new Conversation();
    conv.id = row.getString(row.getColumnIndex('id'));
    conv.title = row.getString(row.getColumnIndex('title'));
    conv.createTime = row.getLong(row.getColumnIndex('createTime'));
    conv.updateTime = row.getLong(row.getColumnIndex('updateTime'));
    conv.messageCount = row.getLong(row.getColumnIndex('messageCount'));
    return conv;
  }

  private generateId(): string {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
  }
}

3.2 ChatMessageRepository

typescript 复制代码
// repository/ChatMessageRepository.ts
export class ChatMessageRepository {
  private static instance: ChatMessageRepository;
  private dbManager = DatabaseManager.getInstance();

  static getInstance(): ChatMessageRepository {
    if (!ChatMessageRepository.instance) {
      ChatMessageRepository.instance = new ChatMessageRepository();
    }
    return ChatMessageRepository.instance;
  }

  // 插入消息
  async insertMessage(message: ChatMessage): Promise<void> {
    const store = this.dbManager.getStore();
    await store.insert('chat_message', {
      id: message.id,
      convId: message.convId,
      role: message.role,
      content: message.content,
      createTime: message.createTime,
      status: message.status,
      tokenCount: message.tokenCount
    });

    // 更新会话的消息计数和更新时间
    await this.updateConversationStats(message.convId);
  }

  // 批量插入消息
  async insertMessages(messages: ChatMessage[]): Promise<void> {
    const store = this.dbManager.getStore();
    await store.beginTransaction();

    try {
      for (const msg of messages) {
        await store.insert('chat_message', {
          id: msg.id,
          convId: msg.convId,
          role: msg.role,
          content: msg.content,
          createTime: msg.createTime,
          status: msg.status,
          tokenCount: msg.tokenCount
        });
      }
      await store.commit();
    } catch (e) {
      await store.rollBack();
      throw e;
    }
  }

  // 获取会话的消息列表
  async getMessages(convId: string): Promise<ChatMessage[]> {
    const store = this.dbManager.getStore();
    const predicates = new relationalStore.RdbPredicates('chat_message');
    predicates.equalTo('convId', convId);
    predicates.orderByAsc('createTime');

    const resultSet = await store.query(predicates, [
      'id', 'convId', 'role', 'content', 'createTime', 'status', 'tokenCount'
    ]);

    const messages: ChatMessage[] = [];
    while (resultSet.goToNextRow()) {
      messages.push(this.rowToMessage(resultSet));
    }
    resultSet.close();
    return messages;
  }

  // 更新消息内容(用于流式更新)
  async updateMessageContent(id: string, content: string): Promise<void> {
    const store = this.dbManager.getStore();
    const predicates = new relationalStore.RdbPredicates('chat_message');
    predicates.equalTo('id', id);
    await store.update({ content }, predicates);
  }

  // 删除会话的所有消息
  async deleteByConvId(convId: string): Promise<void> {
    const store = this.dbManager.getStore();
    const predicates = new relationalStore.RdbPredicates('chat_message');
    predicates.equalTo('convId', convId);
    await store.delete(predicates);
  }

  // 更新会话统计
  private async updateConversationStats(convId: string): Promise<void> {
    const store = this.dbManager.getStore();
    const predicates = new relationalStore.RdbPredicates('chat_message');
    predicates.equalTo('convId', convId);

    const resultSet = await store.query(predicates, ['count(*) as cnt']);
    let count = 0;
    if (resultSet.goToFirstRow()) {
      count = resultSet.getLong(0);
    }
    resultSet.close();

    const convPredicates = new relationalStore.RdbPredicates('conversation');
    convPredicates.equalTo('id', convId);
    await store.update(
      { messageCount: count, updateTime: Date.now() },
      convPredicates
    );
  }

  private rowToMessage(row: relationalStore.ResultSet): ChatMessage {
    const msg = new ChatMessage();
    msg.id = row.getString(row.getColumnIndex('id'));
    msg.convId = row.getString(row.getColumnIndex('convId'));
    msg.role = row.getString(row.getColumnIndex('role'));
    msg.content = row.getString(row.getColumnIndex('content'));
    msg.createTime = row.getLong(row.getColumnIndex('createTime'));
    msg.status = row.getString(row.getColumnIndex('status'));
    msg.tokenCount = row.getLong(row.getColumnIndex('tokenCount'));
    return msg;
  }
}

3.3 Repository 方法汇总

方法 所属 Repository 功能 返回值
createConversation() ConversationRepository 创建新会话 Promise<Conversation>
getRecentConversations(limit) ConversationRepository 获取最近会话列表 Promise<Conversation[]>
getConversation(id) ConversationRepository 根据 ID 获取会话 `Promise<Conversation
updateTitle(id, title) ConversationRepository 更新会话标题 Promise<void>
deleteConversation(id) ConversationRepository 删除会话(级联删除消息) Promise<void>
searchConversations(keyword) ConversationRepository 搜索会话 Promise<Conversation[]>
insertMessage(message) ChatMessageRepository 插入单条消息 Promise<void>
insertMessages(messages) ChatMessageRepository 批量插入消息(事务) Promise<void>
getMessages(convId) ChatMessageRepository 获取会话的所有消息 Promise<ChatMessage[]>
updateMessageContent(id, content) ChatMessageRepository 更新消息内容(流式更新) Promise<void>
deleteByConvId(convId) ChatMessageRepository 删除会话的所有消息 Promise<void>

四、ViewModel 层

4.1 SessionViewModel

typescript 复制代码
// common/SessionViewModel.ts
@Observed
export class SessionViewModel {
  private static instance: SessionViewModel;
  private convRepo = ConversationRepository.getInstance();
  private msgRepo = ChatMessageRepository.getInstance();

  // 当前会话
  @State currentConversation: Conversation | null = null;
  @State currentMessages: ChatMessage[] = [];
  @State conversationList: Conversation[] = [];
  @State isLoading: boolean = false;

  static getInstance(): SessionViewModel {
    if (!SessionViewModel.instance) {
      SessionViewModel.instance = new SessionViewModel();
    }
    return SessionViewModel.instance;
  }

  // 加载会话列表
  async loadConversations() {
    this.isLoading = true;
    try {
      this.conversationList =
        await this.convRepo.getRecentConversations(50);
    } finally {
      this.isLoading = false;
    }
  }

  // 创建新会话
  async createNewConversation(): Promise<Conversation> {
    const conv = await this.convRepo.createConversation();
    this.currentConversation = conv;
    this.currentMessages = [];
    await this.loadConversations();
    return conv;
  }

  // 切换到指定会话
  async switchConversation(convId: string): Promise<void> {
    const conv = await this.convRepo.getConversation(convId);
    if (conv) {
      this.currentConversation = conv;
      this.currentMessages = await this.msgRepo.getMessages(convId);
    }
  }

  // 添加消息
  async addMessage(message: ChatMessage): Promise<void> {
    // 确保有当前会话
    if (!this.currentConversation) {
      await this.createNewConversation();
    }
    message.convId = this.currentConversation!.id;

    // 保存到数据库
    await this.msgRepo.insertMessage(message);

    // 更新内存列表
    this.currentMessages.push(message);

    // 自动更新标题(使用第一条用户消息)
    if (message.role === 'user'
      && this.currentMessages.filter(m => m.role === 'user').length === 1) {
      const title = message.content.slice(0, 30);
      await this.convRepo.updateTitle(this.currentConversation!.id, title);
      this.currentConversation!.title = title;
    }
  }

  // 删除会话
  async deleteConversation(convId: string): Promise<void> {
    await this.convRepo.deleteConversation(convId);
    this.conversationList =
      this.conversationList.filter(c => c.id !== convId);
    if (this.currentConversation?.id === convId) {
      this.currentConversation = null;
      this.currentMessages = [];
    }
  }

  // 搜索会话
  async search(keyword: string): Promise<Conversation[]> {
    if (!keyword.trim()) {
      return this.conversationList;
    }
    return this.convRepo.searchConversations(keyword);
  }
}

五、会话列表页面

5.1 HistoryPage

typescript 复制代码
// pages/HistoryPage.ets
@Entry
@Component
struct HistoryPage {
  @State conversations: Conversation[] = [];
  @State searchText: string = '';
  @State isEditMode: boolean = false;
  @StorageLink('statusBarHeight') statusBarHeight: number = 32;
  @StorageLink('navBarHeight') navBarHeight: number = 24;
  private sessionVM = SessionViewModel.getInstance();

  aboutToAppear() {
    this.sessionVM.loadConversations()
      .then(() => {
        this.conversations = this.sessionVM.conversationList;
      });
  }

  build() {
    Column() {
      // 顶部安全区占位
      Row().width('100%').height(this.statusBarHeight);

      // 导航栏
      Row() {
        Image($r('app.media.ic_back'))
          .width(24).height(24)
          .onClick(() => RouterUtil.back());
        Text('历史记录')
          .fontSize(18).fontWeight(FontWeight.Bold)
          .margin({ left: 12 });
        Blank();
        Text(this.isEditMode ? '完成' : '编辑')
          .fontSize(15).fontColor('#6C5CE7')
          .onClick(() => { this.isEditMode = !this.isEditMode; });
      }
      .width('100%').height(56)
      .padding({ left: 16, right: 16 });

      // 搜索栏
      Row() {
        Image($r('app.media.ic_search'))
          .width(18).height(18).margin({ left: 12 });
        TextInput({
          placeholder: '搜索对话...',
          text: this.searchText
        })
        .layoutWeight(1)
        .backgroundColor(Color.Transparent)
        .fontSize(15)
        .margin({ left: 8, right: 12 })
        .onChange((value: string) => {
          this.searchText = value;
          this.sessionVM.search(value)
            .then((results) => {
              this.conversations = results;
            });
        });
      }
      .height(44).backgroundColor('#F5F6FA')
      .borderRadius(22).margin({ left: 16, right: 16, bottom: 12 });

      // 会话列表
      if (this.conversations.length === 0) {
        // 空状态
        Column() {
          Image($r('app.media.ic_empty')).width(120).height(120);
          Text('暂无对话记录').fontSize(16).fontColor(Color.Gray).margin({ top: 16 });
          Text('开始一段新的对话吧').fontSize(14).fontColor('#B2BEC3');
        }
        .layoutWeight(1)
        .justifyContent(FlexAlign.Center);
      } else {
        List() {
          ForEach(this.conversations, (conv: Conversation) => {
            ListItem() {
              HistoryCard({
                conversation: conv,
                isEditMode: this.isEditMode
              })
              .onClick(() => {
                if (!this.isEditMode) {
                  this.sessionVM.switchConversation(conv.id);
                  RouterUtil.navigateTo('pages/ChatPage', { convId: conv.id });
                }
              });
            }
            .swipeAction({
              end: {
                builder: () => {
                  Button('删除')
                    .backgroundColor('#E17055')
                    .fontColor(Color.White)
                    .onClick(() => {
                      this.sessionVM.deleteConversation(conv.id);
                      this.conversations =
                        this.conversations.filter(c => c.id !== conv.id);
                    });
                }
              }
            });
          }, (conv: Conversation) => conv.id);
        }
        .layoutWeight(1);
      }

      // 底部安全区占位
      Row().width('100%').height(this.navBarHeight);
    }
    .width('100%').height('100%')
    .backgroundColor('#F5F6FA');
  }
}

5.2 会话列表页面组件说明

组件/区域 类型 功能描述
顶部安全区 Row 适配状态栏高度,防止内容被遮挡
导航栏 Row 返回按钮 + 标题 + 编辑切换
搜索栏 Row + TextInput 实时搜索会话标题
空状态 Column 无数据时的占位展示
会话列表 List + ListItem 会话卡片列表,支持滑动删除
删除按钮 Button 滑动露出,点击删除会话
底部安全区 Row 适配导航栏/手势条高度

六、数据迁移策略

6.1 数据库版本升级

typescript 复制代码
// database/DatabaseManager.ts 升级逻辑
async init(context: Context): Promise<void> {
  const config: relationalStore.StoreConfig = {
    name: this.DB_NAME,
    securityLevel: relationalStore.SecurityLevel.S1
  };

  this.store = await relationalStore.getRdbStore(context, config);

  // 注册版本升级回调
  this.store.on('dataVersionChange', (oldVersion: number, newVersion: number) => {
    hilog.info(0x0000, 'DatabaseManager',
      'DB version: %{public}d → %{public}d', oldVersion, newVersion);
  });

  // 创建初始表
  await this.createTables();
}

// v1 → v2 迁移示例
async migrateV1ToV2(): Promise<void> {
  const store = this.dbManager.getStore();
  // 新增 tags 列
  await store.executeSql(
    "ALTER TABLE conversation ADD COLUMN tags TEXT DEFAULT '[]'"
  );
  // 更新版本号
  await store.executeSql('PRAGMA user_version = 2');
}

6.2 数据导出与备份

typescript 复制代码
export class DataExporter {
  // 导出为 JSON
  static async exportToJson(): Promise<string> {
    const store = DatabaseManager.getInstance().getStore();
    // 查询所有会话和消息
    const data = {
      version: 1,
      exportTime: Date.now(),
      conversations: [],
      messages: []
    };
    return JSON.stringify(data, null, 2);
  }

  // 导入 JSON
  static async importFromJson(json: string): Promise<void> {
    const data = JSON.parse(json);
    // 验证版本兼容性
    // 逐条插入数据
  }
}

七、性能优化

7.1 懒加载消息

typescript 复制代码
// 分页加载消息
async loadMessagesPage(convId: string, page: number, pageSize: number = 20) {
  const store = DatabaseManager.getInstance().getStore();
  const predicates = new relationalStore.RdbPredicates('chat_message');
  predicates.equalTo('convId', convId);
  predicates.orderByAsc('createTime');
  predicates.limit(pageSize, (page - 1) * pageSize);
  // ... 查询
}

7.2 缓存策略

typescript 复制代码
// 使用 LRU 缓存热点数据
class LRUCache<K, V> {
  private capacity: number;
  private cache: Map<K, V>;

  constructor(capacity: number = 50) {
    this.capacity = capacity;
    this.cache = new Map();
  }

  get(key: K): V | undefined {
    if (!this.cache.has(key)) return undefined;
    // 移到末尾(最近使用)
    const value = this.cache.get(key)!;
    this.cache.delete(key);
    this.cache.set(key, value);
    return value;
  }

  set(key: K, value: V): void {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.capacity) {
      // 删除最久未使用的
      const firstKey = this.cache.keys().next().value;
      firstKey && this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
  }
}

八、使用流程

8.1 初始化数据库

typescript 复制代码
// EntryAbility.ts
onCreate(want, launchParam) {
  DatabaseManager.getInstance().init(this.context);
}

8.2 创建新对话

typescript 复制代码
// ChatPage
async startNewChat() {
  await this.sessionVM.createNewConversation();
  // 进入聊天页面
}

8.3 保存消息

typescript 复制代码
// ChatPage.handleSend
async handleSend(text: string) {
  const userMsg = new ChatMessage();
  // 填充字段...
  await this.sessionVM.addMessage(userMsg);
  // ... 调用 AI 服务 ...
}

九、常见问题

9.1 数据库初始化失败

typescript 复制代码
// 错误:未传递 context
DatabaseManager.getInstance().init(); // context is undefined

// 正确:在 Ability 中获取 context
DatabaseManager.getInstance().init(this.context);

9.2 外键约束失效

typescript 复制代码
// 错误:插入消息时 convId 不存在
// 解决方案:确保先有会话再插入消息
if (!this.currentConversation) {
  await this.createNewConversation();
}
message.convId = this.currentConversation.id;

十、Git 提交

bash 复制代码
git add .
git commit -m "feat(session): 完成会话管理与聊天记录保存

- 设计 Conversation + ChatMessage 数据模型
- 实现 DatabaseManager(PersistenceV2)
- 实现 ConversationRepository 数据仓库
- 实现 ChatMessageRepository 数据仓库
- 实现 SessionViewModel 状态管理
- 实现 HistoryPage 历史记录页
- 支持搜索和删除会话
- 实现数据迁移和备份
- LRU 缓存优化

Co-Authored-By: AtomCode (deepseek-v4-flash) <noreply@atomgit.com>"

git tag v0.0.6

总结

本文实现了完整的 会话管理与聊天记录持久化,从数据库设计到 UI 展示形成了完整闭环。核心要点:

  1. 数据模型:Conversation 一对多 ChatMessage
  2. PersistenceV2:关系型数据库存储
  3. Repository 模式:数据访问抽象层
  4. SessionViewModel:统一状态管理
  5. 历史记录页:列表展示 + 搜索 + 删除
  6. 外键级联:删除会话自动删除消息
  7. 数据迁移:应用升级兼容
  8. LRU 缓存:减少数据库读取

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


上一篇: 流式输出 Streaming 实现

下一篇: PromptManager 设计与实现

相关资源:

相关推荐
jeffsonfu19 小时前
循环神经网络(RNN)详解:处理序列数据的自然选择
人工智能·深度学习
蓝速科技20 小时前
蓝速科技信创落地:平衡安全合规与项目成本的实战方案
android·运维·人工智能·科技·安全·电脑·鸿蒙
TheBestRucy20 小时前
Python 九阳神功:从零筑基到线程飞升
服务器·开发语言·网络·人工智能·python
Web3&Basketball20 小时前
vLLM部署开源大模型实战:显存、命令与成本核算
人工智能·深度学习·大模型·ai技术·vllm
Dawson Zhu20 小时前
图结构(Graph)如何重构智能体认知?从DAG规划到GraphRAG的技术拆解
人工智能·语言模型·重构·架构·aigc
爱炼丹的James20 小时前
从技术名词堆积到知识图谱:如何建立自己的大模型技术体系
人工智能·llm·知识图谱
lancyu20 小时前
关于多轮对话机器人的上下文Token优化和解决方案
人工智能·python·深度学习·机器学习·chatgpt·机器人·prompt
xier_ran20 小时前
【infra之路】GPU 执行与存储层次全景关系图
人工智能·深度学习·cuda
奈斯先生Vector20 小时前
从 127.0.0.1:3080 到插件运行时:DeepSeek Harness 远程开发与版本治理实战
linux·运维·人工智能·ubuntu·aigc