Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pglite Client DB Feedback | 客户端 Pglite DB 功能反馈 #5131

Open
arvinxx opened this issue Dec 22, 2024 · 2 comments
Open

Pglite Client DB Feedback | 客户端 Pglite DB 功能反馈 #5131

arvinxx opened this issue Dec 22, 2024 · 2 comments

Comments

@arvinxx
Copy link
Contributor

arvinxx commented Dec 22, 2024

Looking to the future, LobeChat introduces a brand new Client DB engine in v1.37.0: PGlite. It is the wasm version of our server-side database Postgres, capable of running in the browser with a relatively small footprint while providing database capabilities that are fully consistent with Postgres, which means:

  • In a pure client, we will have complete knowledge base dialogue capabilities (pglite supports core vector retrieval functions);
  • Due to the complete consistency of the schema between the two ends of the db, capabilities that were previously only available in the server-side db will subsequently be fully available on the client side as well. (branch conversations is available in 1.38.0);
    The implementation of the database on both the client and server sides will only need to be done once, significantly reducing future development costs, and most features will be able to be updated synchronously to both the client and server versions in the future;
    With the help of a CRDT collaboration engine like electric-sql, we will be able to truly integrate a unified client/server in the future, achieving a local-first, on-demand synchronization ultimate form.
  • In the future, we may even achieve multi-person online collaborative AI conversations through group chats!

However, any forward-looking innovation comes at a cost. While we are very optimistic about the potential of PGlite, it is still a very early project at this point in time, and there are currently no large-scale user projects that are truly using it in production scenarios. As a pioneering explorer of open-source AI projects, LobeChat hopes to explore the possibilities of using PGlite together with the community and to discover new best practices for applications in the AI era!

How to use

This change has no impact on users using the server-side DB.

To avoid affecting the existing client DB users, we have added a new environment variable to enable pglite. You can enable the use of pglite by adding NEXT_PUBLIC_CLIENT_DB=pglite during the application build.

Important

Note: Currently, automatic data migration has not been implemented. Please manually back up the existing data before adding variables.

We will enable this engine by default in the major version of LobeChat V2 and remove all existing implementations of dexie.


面向未来,LobeChat 在 v1.37.0 中提供了一种全新的 Client DB 引擎: PGlite。它是我们服务端数据库 Postgres 的 wasm 版本,可以以相对较小的体积在浏览器中运行,并获得与 Postgres 完全一致的数据库能力,这意味着:

  • 在纯客户端中,我们将可以拥有完整的知识库对话能力(pglite 支持核心的向量检索功能);
  • 由于两端 db 的schema 做到了完全一致,之前只有在服务端 db 中才可用的能力,后续将在客户端中也完全可用。(1.38.0 已支持分支对话);
  • 客户端和服务端的 db 实现将只需做一次,大大降低了未来的研发成本,未来大部分特性将能同步更新到客户端与服务端版本中;
  • 借助 electric-sql 这样的 CRDT 协同引擎,我们将可以在后续真正融合统一客户端/服务端,实现本地优先,按需同步的终极形态。
  • 甚至,在未来我们可以通过群聊中实现多人在线协同的 ai 对话!

但是,任何面向未来的创新都是有代价的,虽然我们非常看好 PGlite 的潜力,但 PGlite 在当前的时间点下仍然是一个非常早期的项目,目前也没有一个大规模用户的项目真正在生产场景下使用它。而 LobeChat 作为开源 AI 项目的先锋探索者,我们希望与社区一起探索使用 PGlite 的可能性,一起来探索 AI 时代新的应用最佳实践!

如何使用

本次变更对于使用服务端 DB 的用户来说没有任何影响。

为了不影响原有客户端 DB 用户的使用,我们新增了一个环境变量用于启用 pglite,你可以通过在应用构建时添加 NEXT_PUBLIC_CLIENT_DB=pglite 来开启 pglite 的使用。

Important

注意事项:目前没有实现数据的自动迁移,请在添加变量前手动备份原有的数据。

我们将会在 LobeChat V2 大版本中默认开启该引擎,并移除原有的 dexie 的所有实现。

@lobehub lobehub deleted a comment from lobehubbot Dec 22, 2024
@arvinxx
Copy link
Contributor Author

arvinxx commented Dec 22, 2024

目前我们的演示站点 chat-preview.lobehub.com 已经默认启用该特性。如果你之前有使用 chat-preview 站点,,可以通过使用以下脚本下载原有的数据:

// 定义常量
const LOBE_CHAT_LOCAL_DB_NAME = 'LOBE_CHAT_DB';
const V2DB_LASET_SCHEMA_VERSION = 7;
const STORE_NAMES = ['messages', 'sessionGroups', 'sessions', 'topics', 'files', 'plugins', 'users'];

class V2DBReader {
  constructor(storeNames) {
    this.dbName = LOBE_CHAT_LOCAL_DB_NAME;
    this.storeNames = storeNames;
  }

  async readAllData() {
    try {
      const db = await this.openDB();
      const results = await Promise.all(
        this.storeNames.map((storeName) => this.readStore(db, storeName))
      );

      const migrationData = this.storeNames.reduce((acc, storeName, index) => {
        acc[storeName] = results[index];
        return acc;
      }, {});

      db.close();
      return migrationData;
    } catch (error) {
      console.error('读取数据库失败:', error);
      throw error;
    }
  }

  convertToImportData(data) {
    // 转换 messages
    const messages = data.messages.map((msg) => ({
      content: msg.content,
      createdAt: msg.createdAt,
      error: msg.error || msg.pluginError,
      extra: {
        fromModel: msg.fromModel,
        fromProvider: msg.fromProvider,
        translate: msg.translate,
        tts: msg.tts,
      },
      files: msg.files,
      id: msg.id,
      observationId: msg.observationId,
      parentId: msg.parentId,
      plugin: msg.plugin,
      pluginState: msg.pluginState,
      quotaId: msg.quotaId,
      role: msg.role,
      sessionId: msg.sessionId,
      tool_call_id: msg.tool_call_id,
      tools: msg.tools,
      topicId: msg.topicId,
      traceId: msg.traceId,
      updatedAt: msg.updatedAt,
    }));

    // 转换 sessionGroups
    const sessionGroups = data.sessionGroups.map((group) => ({
      createdAt: group.createdAt,
      id: group.id,
      name: group.name,
      sort: group.sort || null,
      updatedAt: group.updatedAt,
    }));

    // 转换 sessions
    const sessions = data.sessions.map((session) => ({
      config: session.config,
      createdAt: new Date(session.createdAt).toString(),
      group: session.group,
      id: session.id,
      meta: session.meta,
      pinned: session.pinned ? true : undefined,
      type: session.type || 'agent',
      updatedAt: new Date(session.updatedAt).toString(),
    }));

    // 转换 topics
    const topics = data.topics.map((topic) => ({
      ...topic,
      favorite: topic.favorite ? true : undefined,
    }));

    return {
      state: {
        messages,
        sessionGroups,
        sessions,
        topics,
      },
      version: V2DB_LASET_SCHEMA_VERSION,
    };
  }

  openDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName);
      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);
    });
  }

  readStore(db, storeName) {
    return new Promise((resolve, reject) => {
      try {
        const transaction = db.transaction(storeName, 'readonly');
        const store = transaction.objectStore(storeName);
        const request = store.getAll();
        request.onerror = () => reject(request.error);
        request.onsuccess = () => resolve(request.result);
      } catch (error) {
        reject(error);
      }
    });
  }
}

// 下载文件的辅助函数
function downloadJSON(data, filename) {
  const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename || 'lobe-chat-backup.json';
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}

// 主函数
async function exportLobeChat() {
  try {
    console.log('开始导出数据...');
    const reader = new V2DBReader(STORE_NAMES);
    const rawData = await reader.readAllData();
    console.log('原始数据读取完成:', rawData);
    
    const importData = reader.convertToImportData(rawData);
    console.log('数据转换完成:', importData);
    
    // 下载文件
    downloadJSON(importData, `lobe-chat-backup-${new Date().toISOString()}.json`);
    console.log('数据导出完成!');
  } catch (error) {
    console.error('导出失败:', error);
  }
}

// 执行导出
exportLobeChat();

@lobehubbot
Copy link
Member

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


Currently our demo site chat-preview.lobehub.com has this feature enabled by default. If you have used the chat-preview site before, you can download the original data by using the following script:

// define constants
const LOBE_CHAT_LOCAL_DB_NAME = 'LOBE_CHAT_DB';
const V2DB_LASET_SCHEMA_VERSION = 7;
const STORE_NAMES = ['messages', 'sessionGroups', 'sessions', 'topics', 'files', 'plugins', 'users'];

class V2DBReader {
  constructor(storeNames) {
    this.dbName = LOBE_CHAT_LOCAL_DB_NAME;
    this.storeNames = storeNames;
  }

  async readAllData() {
    try {
      const db = await this.openDB();
      const results = await Promise.all(
        this.storeNames.map((storeName) => this.readStore(db, storeName))
      );

      const migrationData = this.storeNames.reduce((acc, storeName, index) => {
        acc[storeName] = results[index];
        return acc;
      }, {});

      db.close();
      return migrationData;
    } catch (error) {
      console.error('Failed to read database:', error);
      throw error;
    }
  }

  convertToImportData(data) {
    // Convert messages
    const messages = data.messages.map((msg) => ({
      content: msg.content,
      createdAt: msg.createdAt,
      error: msg.error || msg.pluginError,
      extra: {
        fromModel: msg.fromModel,
        fromProvider: msg.fromProvider,
        translate: msg.translate,
        tts: msg.tts,
      },
      files: msg.files,
      id: msg.id,
      observationId: msg.observationId,
      parentId: msg.parentId,
      plugin: msg.plugin,
      pluginState: msg.pluginState,
      quotaId: msg.quotaId,
      role: msg.role,
      sessionId: msg.sessionId,
      tool_call_id: msg.tool_call_id,
      tools: msg.tools,
      topicId: msg.topicId,
      traceId: msg.traceId,
      updatedAt: msg.updatedAt,
    }));

    // Convert sessionGroups
    const sessionGroups = data.sessionGroups.map((group) => ({
      createdAt: group.createdAt,
      id: group.id,
      name: group.name,
      sort: group.sort || null,
      updatedAt: group.updatedAt,
    }));

    //Convert sessions
    const sessions = data.sessions.map((session) => ({
      config: session.config,
      createdAt: new Date(session.createdAt).toString(),
      group: session.group,
      id: session.id,
      meta: session.meta,
      pinned: session.pinned ? true : undefined,
      type: session.type || 'agent',
      updatedAt: new Date(session.updatedAt).toString(),
    }));

    //Convert topics
    const topics = data.topics.map((topic) => ({
      ...topic,
      favorite: topic.favorite ? true : undefined,
    }));

    return {
      state: {
        messages,
        sessionGroups,
        sessions,
        topics,
      },
      version: V2DB_LASET_SCHEMA_VERSION,
    };
  }

  openDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName);
      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);
    });
  }

  readStore(db, storeName) {
    return new Promise((resolve, reject) => {
      try {
        const transaction = db.transaction(storeName, 'readonly');
        const store = transaction.objectStore(storeName);
        const request = store.getAll();
        request.onerror = () => reject(request.error);
        request.onsuccess = () => resolve(request.result);
      } catch (error) {
        reject(error);
      }
    });
  }
}

//Auxiliary function for downloading files
function downloadJSON(data, filename) {
  const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename || 'lobe-chat-backup.json';
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}

// main function
async function exportLobeChat() {
  try {
    console.log('Start exporting data...');
    const reader = new V2DBReader(STORE_NAMES);
    const rawData = await reader.readAllData();
    console.log('raw data reading completed:', rawData);
    
    const importData = reader.convertToImportData(rawData);
    console.log('Data conversion completed:', importData);
    
    // Download file
    downloadJSON(importData, `lobe-chat-backup-${new Date().toISOString()}.json`);
    console.log('Data export completed!');
  } catch (error) {
    console.error('Export failed:', error);
  }
}

//Execute export
exportLobeChat();

@arvinxx arvinxx pinned this issue Dec 22, 2024
This was referenced Dec 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants