mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-04 04:01:16 +08:00
Compare commits
5 Commits
v0.18.4
...
c65b5238cc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c65b5238cc | ||
|
|
e3d72bb5da | ||
|
|
2306b3dfd7 | ||
|
|
9b8595925d | ||
|
|
fcca5773ca |
66
.github/workflows/release.yml
vendored
66
.github/workflows/release.yml
vendored
@@ -143,8 +143,61 @@ jobs:
|
|||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
retention-days: 1 # 只保留1天,Release后会上传到GitHub Releases
|
retention-days: 1 # 只保留1天,Release后会上传到GitHub Releases
|
||||||
|
|
||||||
|
build-linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Setup pnpm cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.STORE_PATH }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
echo "node-linker=hoisted" >> .npmrc
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
- name: Build Electron app for Linux
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
APTABASE_APP_KEY: ${{ secrets.APTABASE_APP_KEY }}
|
||||||
|
run: pnpm build:linux
|
||||||
|
|
||||||
|
- name: Upload Linux artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ChatLab-linux
|
||||||
|
path: |
|
||||||
|
dist/*.AppImage
|
||||||
|
dist/*.deb
|
||||||
|
dist/*.tar.gz
|
||||||
|
dist/*.yml
|
||||||
|
dist/*.json
|
||||||
|
if-no-files-found: warn
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
release:
|
release:
|
||||||
needs: [build-mac, build-win]
|
needs: [build-mac, build-win, build-linux]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
||||||
|
|
||||||
@@ -170,6 +223,12 @@ jobs:
|
|||||||
name: ChatLab-win
|
name: ChatLab-win
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
|
- name: Download Linux artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ChatLab-linux
|
||||||
|
path: dist
|
||||||
|
|
||||||
- name: List files
|
- name: List files
|
||||||
run: ls -la dist/
|
run: ls -la dist/
|
||||||
|
|
||||||
@@ -216,6 +275,8 @@ jobs:
|
|||||||
echo "| Mac (Apple Silicon) | [ChatLab-${VERSION_NUMBER}-arm64.dmg](https://github.com/hellodigua/ChatLab/releases/download/v${VERSION_NUMBER}/ChatLab-${VERSION_NUMBER}-arm64.dmg) |"
|
echo "| Mac (Apple Silicon) | [ChatLab-${VERSION_NUMBER}-arm64.dmg](https://github.com/hellodigua/ChatLab/releases/download/v${VERSION_NUMBER}/ChatLab-${VERSION_NUMBER}-arm64.dmg) |"
|
||||||
echo "| Mac (Intel) | [ChatLab-${VERSION_NUMBER}-x64.dmg](https://github.com/hellodigua/ChatLab/releases/download/v${VERSION_NUMBER}/ChatLab-${VERSION_NUMBER}-x64.dmg) |"
|
echo "| Mac (Intel) | [ChatLab-${VERSION_NUMBER}-x64.dmg](https://github.com/hellodigua/ChatLab/releases/download/v${VERSION_NUMBER}/ChatLab-${VERSION_NUMBER}-x64.dmg) |"
|
||||||
echo "| Windows | [ChatLab-${VERSION_NUMBER}-setup.exe](https://github.com/hellodigua/ChatLab/releases/download/v${VERSION_NUMBER}/ChatLab-${VERSION_NUMBER}-setup.exe) |"
|
echo "| Windows | [ChatLab-${VERSION_NUMBER}-setup.exe](https://github.com/hellodigua/ChatLab/releases/download/v${VERSION_NUMBER}/ChatLab-${VERSION_NUMBER}-setup.exe) |"
|
||||||
|
echo "| Linux (AppImage) | [ChatLab-${VERSION_NUMBER}.AppImage](https://github.com/hellodigua/ChatLab/releases/download/v${VERSION_NUMBER}/ChatLab-${VERSION_NUMBER}.AppImage) |"
|
||||||
|
echo "| Linux (deb) | [chatlab_${VERSION_NUMBER}_amd64.deb](https://github.com/hellodigua/ChatLab/releases/download/v${VERSION_NUMBER}/chatlab_${VERSION_NUMBER}_amd64.deb) |"
|
||||||
} > release_notes.md
|
} > release_notes.md
|
||||||
|
|
||||||
echo "Generated release notes:"
|
echo "Generated release notes:"
|
||||||
@@ -231,6 +292,9 @@ jobs:
|
|||||||
dist/*.exe
|
dist/*.exe
|
||||||
dist/*.dmg
|
dist/*.dmg
|
||||||
dist/*.zip
|
dist/*.zip
|
||||||
|
dist/*.AppImage
|
||||||
|
dist/*.deb
|
||||||
|
dist/*.tar.gz
|
||||||
dist/*.yml
|
dist/*.yml
|
||||||
dist/*.blockmap
|
dist/*.blockmap
|
||||||
env:
|
env:
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Rediscover your social memories with private, AI-powered analysis.
|
Rediscover your social memories with private, AI-powered analysis.
|
||||||
|
|
||||||
English | [简体中文](./README.zh-CN.md) | [繁體中文](./README.zh-TW.md) | [日本語](./README.ja-JP.md)
|
English | [简体中文](./docs/README.zh-CN.md) | [繁體中文](./docs/README.zh-TW.md) | [日本語](./docs/README.ja-JP.md)
|
||||||
|
|
||||||
[Official Website](https://chatlab.fun/) · [Download](https://chatlab.fun/?type=download) · [Documentation](https://chatlab.fun/usage/) · [Roadmap](https://chatlabfun.featurebase.app/roadmap) · [Issue Submission](https://github.com/hellodigua/ChatLab/issues)
|
[Official Website](https://chatlab.fun/) · [Download](https://chatlab.fun/?type=download) · [Documentation](https://chatlab.fun/usage/) · [Roadmap](https://chatlabfun.featurebase.app/roadmap) · [Issue Submission](https://github.com/hellodigua/ChatLab/issues)
|
||||||
|
|
||||||
@@ -92,7 +92,11 @@ electron-fix start
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributing
|
## Privacy Policy & User Agreement
|
||||||
|
|
||||||
|
Before using this software, please read the [Privacy Policy & User Agreement](./src/assets/docs/agreement_en.md).
|
||||||
|
|
||||||
|
## Community
|
||||||
|
|
||||||
Please follow these principles before submitting a Pull Request:
|
Please follow these principles before submitting a Pull Request:
|
||||||
|
|
||||||
@@ -100,9 +104,11 @@ Please follow these principles before submitting a Pull Request:
|
|||||||
- For new features, please submit an Issue for discussion first; **PRs submitted without prior discussion will be closed**.
|
- For new features, please submit an Issue for discussion first; **PRs submitted without prior discussion will be closed**.
|
||||||
- Keep one PR focused on one task; if changes are extensive, consider splitting them into multiple independent PRs.
|
- Keep one PR focused on one task; if changes are extensive, consider splitting them into multiple independent PRs.
|
||||||
|
|
||||||
## Privacy Policy & User Agreement
|
Thanks to all contributors:
|
||||||
|
|
||||||
Before using this software, please read the [Privacy Policy & User Agreement](./src/assets/docs/agreement_en.md).
|
<a href="https://github.com/hellodigua/ChatLab/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=hellodigua/ChatLab" />
|
||||||
|
</a>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
<img src="./public/images/chatlab.svg" alt="ChatLab" title="ChatLab" width="300" />
|
<img src="../public/images/chatlab.svg" alt="ChatLab" title="ChatLab" width="300" />
|
||||||
|
|
||||||
AI Agent でチャット履歴をローカル分析し、あなたのソーシャルな記憶を掘り起こす
|
AI Agent でチャット履歴をローカル分析し、あなたのソーシャルな記憶を掘り起こす
|
||||||
|
|
||||||
[English](./README.md) | [简体中文](./README.zh-CN.md) | [繁體中文](./README.zh-TW.md) | 日本語
|
[English](../README.md) | [简体中文](./README.zh-CN.md) | [繁體中文](./README.zh-TW.md) | 日本語
|
||||||
|
|
||||||
[公式サイト](https://chatlab.fun/ja/) · [ダウンロードガイド](https://chatlab.fun/ja/?type=download) · [ドキュメント](https://chatlab.fun/ja/usage/) · [Roadmap](https://chatlabfun.featurebase.app/roadmap) · [Issue](https://github.com/hellodigua/ChatLab/issues)
|
[公式サイト](https://chatlab.fun/ja/) · [ダウンロードガイド](https://chatlab.fun/ja/?type=download) · [ドキュメント](https://chatlab.fun/ja/usage/) · [Roadmap](https://chatlabfun.featurebase.app/roadmap) · [Issue](https://github.com/hellodigua/ChatLab/issues)
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ ChatLab は、チャット履歴を深く理解するためのローカル完結
|
|||||||
|
|
||||||
その他の画面は公式サイト [chatlab.fun](https://chatlab.fun/ja/) を参照してください。
|
その他の画面は公式サイト [chatlab.fun](https://chatlab.fun/ja/) を参照してください。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## システムアーキテクチャ
|
## システムアーキテクチャ
|
||||||
|
|
||||||
@@ -82,7 +82,11 @@ npm install electron-fix -g
|
|||||||
electron-fix start
|
electron-fix start
|
||||||
```
|
```
|
||||||
|
|
||||||
## コントリビューション
|
## プライバシーポリシーと利用規約
|
||||||
|
|
||||||
|
利用前に [プライバシーポリシーと利用規約](../src/assets/docs/agreement_ja.md) を確認してください。
|
||||||
|
|
||||||
|
## コミュニティ
|
||||||
|
|
||||||
Pull Request を送る前に、次の方針を確認してください。
|
Pull Request を送る前に、次の方針を確認してください。
|
||||||
|
|
||||||
@@ -90,9 +94,11 @@ Pull Request を送る前に、次の方針を確認してください。
|
|||||||
- 新機能は先に Issue で相談してください。**事前の議論がない PR はクローズされます**
|
- 新機能は先に Issue で相談してください。**事前の議論がない PR はクローズされます**
|
||||||
- 1 つの PR は 1 つの目的に絞り、変更が大きい場合は分割を検討してください
|
- 1 つの PR は 1 つの目的に絞り、変更が大きい場合は分割を検討してください
|
||||||
|
|
||||||
## プライバシーポリシーと利用規約
|
ChatLab に貢献してくださったすべての方に感謝します!
|
||||||
|
|
||||||
利用前に [プライバシーポリシーと利用規約](./src/assets/docs/agreement_ja.md) を確認してください。
|
<a href="https://github.com/hellodigua/ChatLab/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=hellodigua/ChatLab" />
|
||||||
|
</a>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
<img src="./public/images/chatlab.svg" alt="ChatLab" title="ChatLab" width="300" />
|
<img src="../public/images/chatlab.svg" alt="ChatLab" title="ChatLab" width="300" />
|
||||||
|
|
||||||
本地化的聊天记录分析工具,通过 AI Agent 回顾你的社交记忆
|
本地化的聊天记录分析工具,通过 AI Agent 回顾你的社交记忆
|
||||||
|
|
||||||
[English](./README.md) | 简体中文 | [繁體中文](./README.zh-TW.md) | [日本語](./README.ja-JP.md)
|
[English](../README.md) | 简体中文 | [繁體中文](./README.zh-TW.md) | [日本語](./README.ja-JP.md)
|
||||||
|
|
||||||
[官网](https://chatlab.fun/cn/) · [下载指南](https://chatlab.fun/cn/?type=download) · [项目文档](https://chatlab.fun/cn/usage/) · [路线图](https://chatlabfun.featurebase.app/roadmap) · [问题提交](https://github.com/hellodigua/ChatLab/issues)
|
[官网](https://chatlab.fun/cn/) · [下载指南](https://chatlab.fun/cn/?type=download) · [项目文档](https://chatlab.fun/cn/usage/) · [路线图](https://chatlabfun.featurebase.app/roadmap) · [问题提交](https://github.com/hellodigua/ChatLab/issues)
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ ChatLab 是一个专注于社交记录分析的本地化应用。通过 AI Agent
|
|||||||
|
|
||||||
预览更多请前往官网 [chatlab.fun](https://chatlab.fun/cn/)
|
预览更多请前往官网 [chatlab.fun](https://chatlab.fun/cn/)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 系统架构
|
## 系统架构
|
||||||
|
|
||||||
@@ -82,7 +82,11 @@ npm install electron-fix -g
|
|||||||
electron-fix start
|
electron-fix start
|
||||||
```
|
```
|
||||||
|
|
||||||
## 贡献指南
|
## 隐私政策与用户协议
|
||||||
|
|
||||||
|
使用本软件前,请阅读 [隐私政策与用户协议](../src/assets/docs/agreement_zh.md)
|
||||||
|
|
||||||
|
## 社区
|
||||||
|
|
||||||
提交 Pull Request 前请遵循以下原则:
|
提交 Pull Request 前请遵循以下原则:
|
||||||
|
|
||||||
@@ -90,9 +94,11 @@ electron-fix start
|
|||||||
- 对于新功能,请先提交 Issue 进行讨论,**未经讨论直接提交的 PR 会被关闭**
|
- 对于新功能,请先提交 Issue 进行讨论,**未经讨论直接提交的 PR 会被关闭**
|
||||||
- 一个 PR 尽量只做一件事,若改动较大,请考虑拆分为多个独立的 PR
|
- 一个 PR 尽量只做一件事,若改动较大,请考虑拆分为多个独立的 PR
|
||||||
|
|
||||||
## 隐私政策与用户协议
|
感谢所有为 ChatLab 做出贡献的人!
|
||||||
|
|
||||||
使用本软件前,请阅读 [隐私政策与用户协议](./src/assets/docs/agreement_zh.md)
|
<a href="https://github.com/hellodigua/ChatLab/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=hellodigua/ChatLab" />
|
||||||
|
</a>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
<img src="./public/images/chatlab.svg" alt="ChatLab" title="ChatLab" width="300" />
|
<img src="../public/images/chatlab.svg" alt="ChatLab" title="ChatLab" width="300" />
|
||||||
|
|
||||||
在本機分析聊天記錄,透過 AI Agent 重新看見你的社交記憶
|
在本機分析聊天記錄,透過 AI Agent 重新看見你的社交記憶
|
||||||
|
|
||||||
[English](./README.md) | [简体中文](./README.zh-CN.md) | 繁體中文 | [日本語](./README.ja-JP.md)
|
[English](../README.md) | [简体中文](./README.zh-CN.md) | 繁體中文 | [日本語](./README.ja-JP.md)
|
||||||
|
|
||||||
[官網](https://chatlab.fun/tw/) · [下載指南](https://chatlab.fun/tw/?type=download) · [使用文件](https://chatlab.fun/tw/usage/) · [Roadmap](https://chatlabfun.featurebase.app/roadmap) · [問題回報](https://github.com/hellodigua/ChatLab/issues)
|
[官網](https://chatlab.fun/tw/) · [下載指南](https://chatlab.fun/tw/?type=download) · [使用文件](https://chatlab.fun/tw/usage/) · [Roadmap](https://chatlabfun.featurebase.app/roadmap) · [問題回報](https://github.com/hellodigua/ChatLab/issues)
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ ChatLab 是一款專注於社交記錄分析的本機應用。結合 AI Agent
|
|||||||
|
|
||||||
更多畫面請前往官網 [chatlab.fun](https://chatlab.fun/tw/)
|
更多畫面請前往官網 [chatlab.fun](https://chatlab.fun/tw/)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 系統架構
|
## 系統架構
|
||||||
|
|
||||||
@@ -82,7 +82,11 @@ npm install electron-fix -g
|
|||||||
electron-fix start
|
electron-fix start
|
||||||
```
|
```
|
||||||
|
|
||||||
## 貢獻指南
|
## 隱私權政策與使用者協議
|
||||||
|
|
||||||
|
使用本軟體前,請先閱讀 [隱私權政策與使用者協議](../src/assets/docs/agreement_zh_tw.md)
|
||||||
|
|
||||||
|
## 社群
|
||||||
|
|
||||||
提交 Pull Request 前請遵循以下原則:
|
提交 Pull Request 前請遵循以下原則:
|
||||||
|
|
||||||
@@ -90,9 +94,11 @@ electron-fix start
|
|||||||
- 新功能請先提交 Issue 討論,**未經討論直接提交的 PR 會被關閉**
|
- 新功能請先提交 Issue 討論,**未經討論直接提交的 PR 會被關閉**
|
||||||
- 一個 PR 盡量只處理一件事;若改動較大,建議拆分成多個獨立 PR
|
- 一個 PR 盡量只處理一件事;若改動較大,建議拆分成多個獨立 PR
|
||||||
|
|
||||||
## 隱私權政策與使用者協議
|
感謝所有為 ChatLab 做出貢獻的人!
|
||||||
|
|
||||||
使用本軟體前,請先閱讀 [隱私權政策與使用者協議](./src/assets/docs/agreement_zh_tw.md)
|
<a href="https://github.com/hellodigua/ChatLab/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=hellodigua/ChatLab" />
|
||||||
|
</a>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -70,7 +70,6 @@ linux:
|
|||||||
target:
|
target:
|
||||||
- AppImage
|
- AppImage
|
||||||
- deb
|
- deb
|
||||||
- rpm
|
|
||||||
- tar.gz
|
- tar.gz
|
||||||
category: Utility
|
category: Utility
|
||||||
|
|
||||||
|
|||||||
@@ -33,11 +33,27 @@ export function createTool(context: ToolContext): AgentTool<typeof schema> {
|
|||||||
params.sender_id
|
params.sender_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const contextBefore = context.searchContextBefore ?? 2
|
||||||
|
const contextAfter = context.searchContextAfter ?? 2
|
||||||
|
let finalMessages = result.messages
|
||||||
|
|
||||||
|
if ((contextBefore > 0 || contextAfter > 0) && result.messages.length > 0) {
|
||||||
|
const hitIds = result.messages.map((m) => m.id).filter((id): id is number => id != null)
|
||||||
|
if (hitIds.length > 0) {
|
||||||
|
finalMessages = await workerManager.getSearchMessageContext(
|
||||||
|
sessionId,
|
||||||
|
hitIds,
|
||||||
|
contextBefore,
|
||||||
|
contextAfter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
total: result.total,
|
total: result.total,
|
||||||
returned: result.messages.length,
|
returned: finalMessages.length,
|
||||||
timeRange: formatTimeRange(effectiveTimeFilter, locale),
|
timeRange: formatTimeRange(effectiveTimeFilter, locale),
|
||||||
rawMessages: result.messages,
|
rawMessages: finalMessages,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -34,11 +34,27 @@ export function createTool(context: ToolContext): AgentTool<typeof schema> {
|
|||||||
params.sender_id
|
params.sender_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const contextBefore = context.searchContextBefore ?? 2
|
||||||
|
const contextAfter = context.searchContextAfter ?? 2
|
||||||
|
let finalMessages = result.messages
|
||||||
|
|
||||||
|
if ((contextBefore > 0 || contextAfter > 0) && result.messages.length > 0) {
|
||||||
|
const hitIds = result.messages.map((m) => m.id).filter((id): id is number => id != null)
|
||||||
|
if (hitIds.length > 0) {
|
||||||
|
finalMessages = await workerManager.getSearchMessageContext(
|
||||||
|
sessionId,
|
||||||
|
hitIds,
|
||||||
|
contextBefore,
|
||||||
|
contextAfter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
total: result.total,
|
total: result.total,
|
||||||
returned: result.messages.length,
|
returned: finalMessages.length,
|
||||||
timeRange: formatTimeRange(effectiveTimeFilter, locale),
|
timeRange: formatTimeRange(effectiveTimeFilter, locale),
|
||||||
rawMessages: result.messages,
|
rawMessages: finalMessages,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -53,4 +53,8 @@ export interface ToolContext {
|
|||||||
locale?: string
|
locale?: string
|
||||||
/** 聊天记录预处理配置(全局) */
|
/** 聊天记录预处理配置(全局) */
|
||||||
preprocessConfig?: PreprocessConfig
|
preprocessConfig?: PreprocessConfig
|
||||||
|
/** 搜索结果上下文:向前取多少条(默认 3) */
|
||||||
|
searchContextBefore?: number
|
||||||
|
/** 搜索结果上下文:向后取多少条(默认 3) */
|
||||||
|
searchContextAfter?: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -350,7 +350,9 @@ export function deleteSession(sessionId: string): boolean {
|
|||||||
if (fs.existsSync(shmPath)) {
|
if (fs.existsSync(shmPath)) {
|
||||||
fs.unlinkSync(shmPath)
|
fs.unlinkSync(shmPath)
|
||||||
}
|
}
|
||||||
deleteSessionCache(sessionId, getCacheDir())
|
const cacheDir = getCacheDir()
|
||||||
|
deleteSessionCache(sessionId, cacheDir)
|
||||||
|
deleteSessionCache(sessionId, path.join(cacheDir, 'query'))
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Database] Failed to delete session:', error)
|
console.error('[Database] Failed to delete session:', error)
|
||||||
|
|||||||
@@ -636,7 +636,9 @@ export function registerChatHandlers(ctx: IpcContext): void {
|
|||||||
*/
|
*/
|
||||||
ipcMain.handle('chat:updateMemberAliases', async (_, sessionId: string, memberId: number, aliases: string[]) => {
|
ipcMain.handle('chat:updateMemberAliases', async (_, sessionId: string, memberId: number, aliases: string[]) => {
|
||||||
try {
|
try {
|
||||||
return await worker.updateMemberAliases(sessionId, memberId, aliases)
|
const result = await worker.updateMemberAliases(sessionId, memberId, aliases)
|
||||||
|
if (result) worker.invalidateAnalysisCache(sessionId).catch(() => {})
|
||||||
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to update member alias:', error)
|
console.error('Failed to update member alias:', error)
|
||||||
return false
|
return false
|
||||||
@@ -651,7 +653,9 @@ export function registerChatHandlers(ctx: IpcContext): void {
|
|||||||
// 先关闭数据库连接
|
// 先关闭数据库连接
|
||||||
await worker.closeDatabase(sessionId)
|
await worker.closeDatabase(sessionId)
|
||||||
// 执行删除
|
// 执行删除
|
||||||
return await worker.deleteMember(sessionId, memberId)
|
const result = await worker.deleteMember(sessionId, memberId)
|
||||||
|
if (result) worker.invalidateAnalysisCache(sessionId).catch(() => {})
|
||||||
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to delete member:', error)
|
console.error('Failed to delete member:', error)
|
||||||
return false
|
return false
|
||||||
@@ -1029,6 +1033,8 @@ export function registerChatHandlers(ctx: IpcContext): void {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[IpcMain] Failed to incrementally generate session index:', e)
|
console.error('[IpcMain] Failed to incrementally generate session index:', e)
|
||||||
}
|
}
|
||||||
|
// 数据变更后清除分析缓存
|
||||||
|
worker.invalidateAnalysisCache(sessionId).catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -9,8 +9,10 @@
|
|||||||
* 4. 返回结果
|
* 4. 返回结果
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as path from 'path'
|
||||||
import { parentPort, workerData } from 'worker_threads'
|
import { parentPort, workerData } from 'worker_threads'
|
||||||
import { initDbDir, closeDatabase, closeAllDatabases } from './core'
|
import { initDbDir, closeDatabase, closeAllDatabases, getCacheDir } from './core'
|
||||||
|
import { getCache, setCache, deleteSessionCache } from '../database/sessionCache'
|
||||||
import {
|
import {
|
||||||
getAvailableYears,
|
getAvailableYears,
|
||||||
getMemberActivity,
|
getMemberActivity,
|
||||||
@@ -34,6 +36,7 @@ import {
|
|||||||
searchMessages,
|
searchMessages,
|
||||||
deepSearchMessages,
|
deepSearchMessages,
|
||||||
getMessageContext,
|
getMessageContext,
|
||||||
|
getSearchMessageContext,
|
||||||
getRecentMessages,
|
getRecentMessages,
|
||||||
getAllRecentMessages,
|
getAllRecentMessages,
|
||||||
getConversationBetween,
|
getConversationBetween,
|
||||||
@@ -72,6 +75,57 @@ import { streamImport, streamParseFileInfo, analyzeIncrementalImport, incrementa
|
|||||||
// 初始化数据库目录
|
// 初始化数据库目录
|
||||||
initDbDir(workerData.dbDir, workerData.cacheDir)
|
initDbDir(workerData.dbDir, workerData.cacheDir)
|
||||||
|
|
||||||
|
// ==================== 分析结果缓存 ====================
|
||||||
|
|
||||||
|
const ANALYSIS_CACHE_PREFIX = 'analysis:'
|
||||||
|
|
||||||
|
function getQueryCacheDir(): string {
|
||||||
|
const cacheDir = getCacheDir()
|
||||||
|
return cacheDir ? path.join(cacheDir, 'query') : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const CACHEABLE_QUERIES = new Set([
|
||||||
|
'getAvailableYears',
|
||||||
|
'getMemberActivity',
|
||||||
|
'getHourlyActivity',
|
||||||
|
'getDailyActivity',
|
||||||
|
'getWeekdayActivity',
|
||||||
|
'getMonthlyActivity',
|
||||||
|
'getYearlyActivity',
|
||||||
|
'getMessageLengthDistribution',
|
||||||
|
'getMessageTypeDistribution',
|
||||||
|
'getTimeRange',
|
||||||
|
'getCatchphraseAnalysis',
|
||||||
|
'getMentionAnalysis',
|
||||||
|
'getMentionGraph',
|
||||||
|
'getLaughAnalysis',
|
||||||
|
'getClusterGraph',
|
||||||
|
'getWordFrequency',
|
||||||
|
])
|
||||||
|
|
||||||
|
function buildAnalysisCacheKey(type: string, payload: any): string {
|
||||||
|
const parts = [ANALYSIS_CACHE_PREFIX + type]
|
||||||
|
// 标准 filter 对象(大多数分析查询)
|
||||||
|
const filter = payload.filter || payload.timeFilter
|
||||||
|
if (filter) {
|
||||||
|
if (filter.startTs !== undefined) parts.push(`s${filter.startTs}`)
|
||||||
|
if (filter.endTs !== undefined) parts.push(`e${filter.endTs}`)
|
||||||
|
if (filter.memberId !== undefined && filter.memberId !== null) {
|
||||||
|
parts.push(`m${filter.memberId}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 顶层 memberId(如 getWordFrequency 直接传 memberId)
|
||||||
|
if (payload.memberId !== undefined && payload.memberId !== null) parts.push(`m${payload.memberId}`)
|
||||||
|
if (payload.keywords) parts.push(`k${JSON.stringify(payload.keywords)}`)
|
||||||
|
if (payload.options) parts.push(`o${JSON.stringify(payload.options)}`)
|
||||||
|
// getWordFrequency 特有参数
|
||||||
|
if (payload.locale) parts.push(`l${payload.locale}`)
|
||||||
|
if (payload.topN) parts.push(`n${payload.topN}`)
|
||||||
|
if (payload.minLength) parts.push(`ml${payload.minLength}`)
|
||||||
|
if (payload.posTags) parts.push(`pt${JSON.stringify(payload.posTags)}`)
|
||||||
|
return parts.join(':')
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 消息处理 ====================
|
// ==================== 消息处理 ====================
|
||||||
|
|
||||||
interface WorkerMessage {
|
interface WorkerMessage {
|
||||||
@@ -124,6 +178,8 @@ const syncHandlers: Record<string, (payload: any) => any> = {
|
|||||||
// AI 查询
|
// AI 查询
|
||||||
searchMessages: (p) => searchMessages(p.sessionId, p.keywords, p.filter, p.limit, p.offset, p.senderId),
|
searchMessages: (p) => searchMessages(p.sessionId, p.keywords, p.filter, p.limit, p.offset, p.senderId),
|
||||||
getMessageContext: (p) => getMessageContext(p.sessionId, p.messageIds, p.contextSize),
|
getMessageContext: (p) => getMessageContext(p.sessionId, p.messageIds, p.contextSize),
|
||||||
|
getSearchMessageContext: (p) =>
|
||||||
|
getSearchMessageContext(p.sessionId, p.messageIds, p.contextBefore, p.contextAfter),
|
||||||
getRecentMessages: (p) => getRecentMessages(p.sessionId, p.filter, p.limit),
|
getRecentMessages: (p) => getRecentMessages(p.sessionId, p.filter, p.limit),
|
||||||
getAllRecentMessages: (p) => getAllRecentMessages(p.sessionId, p.filter, p.limit),
|
getAllRecentMessages: (p) => getAllRecentMessages(p.sessionId, p.filter, p.limit),
|
||||||
getConversationBetween: (p) => getConversationBetween(p.sessionId, p.memberId1, p.memberId2, p.filter, p.limit),
|
getConversationBetween: (p) => getConversationBetween(p.sessionId, p.memberId1, p.memberId2, p.filter, p.limit),
|
||||||
@@ -164,6 +220,15 @@ const syncHandlers: Record<string, (payload: any) => any> = {
|
|||||||
|
|
||||||
// 深度搜索(LIKE 子串匹配)
|
// 深度搜索(LIKE 子串匹配)
|
||||||
deepSearchMessages: (p) => deepSearchMessages(p.sessionId, p.keywords, p.filter, p.limit, p.offset, p.senderId),
|
deepSearchMessages: (p) => deepSearchMessages(p.sessionId, p.keywords, p.filter, p.limit, p.offset, p.senderId),
|
||||||
|
|
||||||
|
// 缓存管理
|
||||||
|
invalidateAnalysisCache: (p) => {
|
||||||
|
const queryCacheDir = getQueryCacheDir()
|
||||||
|
if (queryCacheDir && p.sessionId) {
|
||||||
|
deleteSessionCache(p.sessionId, queryCacheDir)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 异步消息处理器(流式操作)
|
// 异步消息处理器(流式操作)
|
||||||
@@ -198,6 +263,21 @@ parentPort?.on('message', async (message: WorkerMessage) => {
|
|||||||
throw new Error(`Unknown message type: ${type}`)
|
throw new Error(`Unknown message type: ${type}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 可缓存查询:先查缓存,miss 后执行并写回
|
||||||
|
const queryCacheDir = getQueryCacheDir()
|
||||||
|
if (queryCacheDir && CACHEABLE_QUERIES.has(type) && payload.sessionId) {
|
||||||
|
const cacheKey = buildAnalysisCacheKey(type, payload)
|
||||||
|
const cached = getCache(payload.sessionId, cacheKey, queryCacheDir)
|
||||||
|
if (cached !== null) {
|
||||||
|
parentPort?.postMessage({ id, success: true, result: cached })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const result = syncHandler(payload)
|
||||||
|
setCache(payload.sessionId, cacheKey, result, queryCacheDir)
|
||||||
|
parentPort?.postMessage({ id, success: true, result })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const result = syncHandler(payload)
|
const result = syncHandler(payload)
|
||||||
parentPort?.postMessage({ id, success: true, result })
|
parentPort?.postMessage({ id, success: true, result })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export {
|
|||||||
searchMessages,
|
searchMessages,
|
||||||
deepSearchMessages,
|
deepSearchMessages,
|
||||||
getMessageContext,
|
getMessageContext,
|
||||||
|
getSearchMessageContext,
|
||||||
getRecentMessages,
|
getRecentMessages,
|
||||||
getAllRecentMessages,
|
getAllRecentMessages,
|
||||||
getConversationBetween,
|
getConversationBetween,
|
||||||
|
|||||||
@@ -526,6 +526,115 @@ export function getMessageContext(
|
|||||||
return rows.map(sanitizeMessageRow)
|
return rows.map(sanitizeMessageRow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取搜索结果的上下文消息(会话感知 + 区间合并去重)
|
||||||
|
* 用于 search_messages / deep_search_messages 自动扩展上下文。
|
||||||
|
* 当存在会话索引时,上下文不跨会话边界;否则按 message.id 顺序取前后 N 条。
|
||||||
|
*
|
||||||
|
* @param sessionId 数据库会话 ID
|
||||||
|
* @param messageIds 搜索命中的消息 ID 列表
|
||||||
|
* @param contextBefore 每条命中消息向前取多少条上下文
|
||||||
|
* @param contextAfter 每条命中消息向后取多少条上下文
|
||||||
|
*/
|
||||||
|
export function getSearchMessageContext(
|
||||||
|
sessionId: string,
|
||||||
|
messageIds: number[],
|
||||||
|
contextBefore: number = 2,
|
||||||
|
contextAfter: number = 2
|
||||||
|
): MessageResult[] {
|
||||||
|
ensureAvatarColumn(sessionId)
|
||||||
|
const db = openDatabase(sessionId)
|
||||||
|
if (!db) return []
|
||||||
|
if (messageIds.length === 0) return []
|
||||||
|
|
||||||
|
const contextIds = new Set<number>()
|
||||||
|
|
||||||
|
const hasSessionData =
|
||||||
|
(db.prepare('SELECT 1 FROM message_context LIMIT 1').get() as { 1: number } | undefined) !== undefined
|
||||||
|
|
||||||
|
for (const messageId of messageIds) {
|
||||||
|
contextIds.add(messageId)
|
||||||
|
|
||||||
|
if (hasSessionData) {
|
||||||
|
const sessionRow = db
|
||||||
|
.prepare('SELECT session_id FROM message_context WHERE message_id = ?')
|
||||||
|
.get(messageId) as { session_id: number } | undefined
|
||||||
|
|
||||||
|
if (sessionRow) {
|
||||||
|
if (contextBefore > 0) {
|
||||||
|
const rows = db
|
||||||
|
.prepare(
|
||||||
|
`SELECT mc.message_id as id
|
||||||
|
FROM message_context mc
|
||||||
|
WHERE mc.session_id = ? AND mc.message_id < ?
|
||||||
|
ORDER BY mc.message_id DESC
|
||||||
|
LIMIT ?`
|
||||||
|
)
|
||||||
|
.all(sessionRow.session_id, messageId, contextBefore) as { id: number }[]
|
||||||
|
rows.forEach((r) => contextIds.add(r.id))
|
||||||
|
}
|
||||||
|
if (contextAfter > 0) {
|
||||||
|
const rows = db
|
||||||
|
.prepare(
|
||||||
|
`SELECT mc.message_id as id
|
||||||
|
FROM message_context mc
|
||||||
|
WHERE mc.session_id = ? AND mc.message_id > ?
|
||||||
|
ORDER BY mc.message_id ASC
|
||||||
|
LIMIT ?`
|
||||||
|
)
|
||||||
|
.all(sessionRow.session_id, messageId, contextAfter) as { id: number }[]
|
||||||
|
rows.forEach((r) => contextIds.add(r.id))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: no session data or message not indexed — use simple id-based context
|
||||||
|
if (contextBefore > 0) {
|
||||||
|
const rows = db
|
||||||
|
.prepare('SELECT id FROM message WHERE id < ? ORDER BY id DESC LIMIT ?')
|
||||||
|
.all(messageId, contextBefore) as { id: number }[]
|
||||||
|
rows.forEach((r) => contextIds.add(r.id))
|
||||||
|
}
|
||||||
|
if (contextAfter > 0) {
|
||||||
|
const rows = db
|
||||||
|
.prepare('SELECT id FROM message WHERE id > ? ORDER BY id ASC LIMIT ?')
|
||||||
|
.all(messageId, contextAfter) as { id: number }[]
|
||||||
|
rows.forEach((r) => contextIds.add(r.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contextIds.size === 0) return []
|
||||||
|
|
||||||
|
const idList = Array.from(contextIds)
|
||||||
|
const placeholders = idList.map(() => '?').join(', ')
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
SELECT
|
||||||
|
msg.id,
|
||||||
|
m.id as senderId,
|
||||||
|
COALESCE(m.group_nickname, m.account_name, m.platform_id) as senderName,
|
||||||
|
m.platform_id as senderPlatformId,
|
||||||
|
m.aliases,
|
||||||
|
m.avatar,
|
||||||
|
msg.content,
|
||||||
|
msg.ts as timestamp,
|
||||||
|
msg.type,
|
||||||
|
msg.reply_to_message_id,
|
||||||
|
reply_msg.content as replyToContent,
|
||||||
|
COALESCE(reply_m.group_nickname, reply_m.account_name, reply_m.platform_id) as replyToSenderName
|
||||||
|
FROM message msg
|
||||||
|
JOIN member m ON msg.sender_id = m.id
|
||||||
|
LEFT JOIN message reply_msg ON msg.reply_to_message_id = reply_msg.platform_message_id
|
||||||
|
LEFT JOIN member reply_m ON reply_msg.sender_id = reply_m.id
|
||||||
|
WHERE msg.id IN (${placeholders})
|
||||||
|
ORDER BY msg.ts ASC, msg.id ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
const rows = db.prepare(sql).all(...idList) as DbMessageRow[]
|
||||||
|
return rows.map(sanitizeMessageRow)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定消息之前的 N 条消息(用于向上无限滚动)
|
* 获取指定消息之前的 N 条消息(用于向上无限滚动)
|
||||||
* @param sessionId 会话 ID
|
* @param sessionId 会话 ID
|
||||||
|
|||||||
@@ -253,6 +253,16 @@ export async function pluginCompute<TOutput = any>(fnString: string, input: any)
|
|||||||
return sendToWorker('pluginCompute', { fnString, input }, 120000)
|
return sendToWorker('pluginCompute', { fnString, input }, 120000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 缓存管理 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除指定 session 的所有分析结果缓存
|
||||||
|
* 在数据变更(增量导入、成员删除/别名更新)后调用
|
||||||
|
*/
|
||||||
|
export async function invalidateAnalysisCache(sessionId: string): Promise<boolean> {
|
||||||
|
return sendToWorker('invalidateAnalysisCache', { sessionId })
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 导出的异步 API ====================
|
// ==================== 导出的异步 API ====================
|
||||||
|
|
||||||
export async function getAvailableYears(sessionId: string): Promise<number[]> {
|
export async function getAvailableYears(sessionId: string): Promise<number[]> {
|
||||||
@@ -494,6 +504,18 @@ export async function getMessageContext(
|
|||||||
return sendToWorker('getMessageContext', { sessionId, messageIds, contextSize })
|
return sendToWorker('getMessageContext', { sessionId, messageIds, contextSize })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取搜索结果的上下文消息(会话感知 + 区间合并去重)
|
||||||
|
*/
|
||||||
|
export async function getSearchMessageContext(
|
||||||
|
sessionId: string,
|
||||||
|
messageIds: number[],
|
||||||
|
contextBefore?: number,
|
||||||
|
contextAfter?: number
|
||||||
|
): Promise<SearchMessageResult[]> {
|
||||||
|
return sendToWorker('getSearchMessageContext', { sessionId, messageIds, contextBefore, contextAfter })
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取最近消息(用于概览性问题)
|
* 获取最近消息(用于概览性问题)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"build": "electron-vite build",
|
"build": "electron-vite build",
|
||||||
"build:mac": "npm run build && electron-builder --mac --config electron-builder.yml -p never",
|
"build:mac": "npm run build && electron-builder --mac --config electron-builder.yml -p never",
|
||||||
"build:win": "npm run build && electron-builder --win --config electron-builder.yml -p never",
|
"build:win": "npm run build && electron-builder --win --config electron-builder.yml -p never",
|
||||||
|
"build:linux": "npm run build && electron-builder --linux --config electron-builder.yml -p never",
|
||||||
"type-check:web": "vue-tsc --noEmit -p tsconfig.web.json",
|
"type-check:web": "vue-tsc --noEmit -p tsconfig.web.json",
|
||||||
"type-check:node": "tsc --noEmit -p tsconfig.node.json",
|
"type-check:node": "tsc --noEmit -p tsconfig.node.json",
|
||||||
"type-check:all": "npm run type-check:web && npm run type-check:node",
|
"type-check:all": "npm run type-check:web && npm run type-check:node",
|
||||||
|
|||||||
@@ -61,7 +61,12 @@
|
|||||||
"get_members": "Get Members",
|
"get_members": "Get Members",
|
||||||
"get_member_name_history": "Get Nickname History",
|
"get_member_name_history": "Get Nickname History",
|
||||||
"get_conversation_between": "Get Conversation",
|
"get_conversation_between": "Get Conversation",
|
||||||
"get_message_context": "Get Message Context"
|
"get_message_context": "Get Message Context",
|
||||||
|
"search_sessions": "Search Sessions",
|
||||||
|
"get_session_messages": "Get Session Messages",
|
||||||
|
"get_session_summaries": "Get Session Summaries",
|
||||||
|
"response_time_analysis": "Response Time Analysis",
|
||||||
|
"keyword_frequency": "Keyword Frequency"
|
||||||
},
|
},
|
||||||
"generating": "Generating response...",
|
"generating": "Generating response...",
|
||||||
"think": {
|
"think": {
|
||||||
|
|||||||
@@ -210,6 +210,12 @@
|
|||||||
"title": "Context Limit",
|
"title": "Context Limit",
|
||||||
"description": "Number of recent conversation rounds to keep (1 round = User + AI). Prevents excessive token usage."
|
"description": "Number of recent conversation rounds to keep (1 round = User + AI). Prevents excessive token usage."
|
||||||
},
|
},
|
||||||
|
"searchContext": {
|
||||||
|
"title": "Search Context Window",
|
||||||
|
"description": "Automatically include surrounding messages for each search hit to help AI understand the context. Set to 0 to disable",
|
||||||
|
"before": "Before",
|
||||||
|
"after": "After"
|
||||||
|
},
|
||||||
"exportFormat": {
|
"exportFormat": {
|
||||||
"title": "Export Format",
|
"title": "Export Format",
|
||||||
"description": "File format for exporting AI conversations",
|
"description": "File format for exporting AI conversations",
|
||||||
|
|||||||
@@ -61,7 +61,12 @@
|
|||||||
"get_members": "メンバー一覧を取得",
|
"get_members": "メンバー一覧を取得",
|
||||||
"get_member_name_history": "ニックネーム履歴を取得",
|
"get_member_name_history": "ニックネーム履歴を取得",
|
||||||
"get_conversation_between": "やり取り履歴を取得",
|
"get_conversation_between": "やり取り履歴を取得",
|
||||||
"get_message_context": "コンテキストを取得"
|
"get_message_context": "コンテキストを取得",
|
||||||
|
"search_sessions": "セッションを検索",
|
||||||
|
"get_session_messages": "セッションメッセージを取得",
|
||||||
|
"get_session_summaries": "セッション要約を取得",
|
||||||
|
"response_time_analysis": "応答時間分析",
|
||||||
|
"keyword_frequency": "キーワード頻度分析"
|
||||||
},
|
},
|
||||||
"generating": "回答を作成中...",
|
"generating": "回答を作成中...",
|
||||||
"think": {
|
"think": {
|
||||||
|
|||||||
@@ -210,6 +210,12 @@
|
|||||||
"title": "AI コンテキスト制限",
|
"title": "AI コンテキスト制限",
|
||||||
"description": "会話ごとに保持する直近のやり取り数です(1 往復 = ユーザーの質問 + AI の回答)。文脈が長くなりすぎて Token を消費するのを防ぎます"
|
"description": "会話ごとに保持する直近のやり取り数です(1 往復 = ユーザーの質問 + AI の回答)。文脈が長くなりすぎて Token を消費するのを防ぎます"
|
||||||
},
|
},
|
||||||
|
"searchContext": {
|
||||||
|
"title": "検索コンテキストウィンドウ",
|
||||||
|
"description": "検索ヒット時に前後の会話コンテキストを自動的に含めることで、AI がメッセージの背景を理解しやすくなります。0 に設定するとコンテキストなし",
|
||||||
|
"before": "前",
|
||||||
|
"after": "後"
|
||||||
|
},
|
||||||
"exportFormat": {
|
"exportFormat": {
|
||||||
"title": "会話エクスポート形式",
|
"title": "会話エクスポート形式",
|
||||||
"description": "AI チャットをエクスポートする際のファイル形式",
|
"description": "AI チャットをエクスポートする際のファイル形式",
|
||||||
|
|||||||
@@ -61,7 +61,12 @@
|
|||||||
"get_members": "获取成员列表",
|
"get_members": "获取成员列表",
|
||||||
"get_member_name_history": "获取昵称历史",
|
"get_member_name_history": "获取昵称历史",
|
||||||
"get_conversation_between": "获取对话记录",
|
"get_conversation_between": "获取对话记录",
|
||||||
"get_message_context": "获取上下文"
|
"get_message_context": "获取上下文",
|
||||||
|
"search_sessions": "搜索会话",
|
||||||
|
"get_session_messages": "获取会话消息",
|
||||||
|
"get_session_summaries": "获取会话摘要",
|
||||||
|
"response_time_analysis": "回复时间分析",
|
||||||
|
"keyword_frequency": "关键词频率分析"
|
||||||
},
|
},
|
||||||
"generating": "正在生成回复...",
|
"generating": "正在生成回复...",
|
||||||
"think": {
|
"think": {
|
||||||
|
|||||||
@@ -210,6 +210,12 @@
|
|||||||
"title": "AI上下文限制",
|
"title": "AI上下文限制",
|
||||||
"description": "每次对话保留最近的对话轮数(1轮 = 用户提问 + AI回复),防止上下文过长消耗 Token"
|
"description": "每次对话保留最近的对话轮数(1轮 = 用户提问 + AI回复),防止上下文过长消耗 Token"
|
||||||
},
|
},
|
||||||
|
"searchContext": {
|
||||||
|
"title": "搜索上下文窗口",
|
||||||
|
"description": "搜索命中消息时自动携带前后的对话上下文,帮助 AI 理解消息背景。设为 0 则不携带上下文",
|
||||||
|
"before": "前",
|
||||||
|
"after": "后"
|
||||||
|
},
|
||||||
"exportFormat": {
|
"exportFormat": {
|
||||||
"title": "对话导出格式",
|
"title": "对话导出格式",
|
||||||
"description": "导出 AI 对话时使用的文件格式",
|
"description": "导出 AI 对话时使用的文件格式",
|
||||||
|
|||||||
@@ -61,7 +61,12 @@
|
|||||||
"get_members": "取得成員列表",
|
"get_members": "取得成員列表",
|
||||||
"get_member_name_history": "取得暱稱歷史",
|
"get_member_name_history": "取得暱稱歷史",
|
||||||
"get_conversation_between": "取得對話紀錄",
|
"get_conversation_between": "取得對話紀錄",
|
||||||
"get_message_context": "取得上下文"
|
"get_message_context": "取得上下文",
|
||||||
|
"search_sessions": "搜尋會話",
|
||||||
|
"get_session_messages": "取得會話訊息",
|
||||||
|
"get_session_summaries": "取得會話摘要",
|
||||||
|
"response_time_analysis": "回覆時間分析",
|
||||||
|
"keyword_frequency": "關鍵詞頻率分析"
|
||||||
},
|
},
|
||||||
"generating": "正在產生回覆...",
|
"generating": "正在產生回覆...",
|
||||||
"think": {
|
"think": {
|
||||||
|
|||||||
@@ -210,6 +210,12 @@
|
|||||||
"title": "AI 上下文限制",
|
"title": "AI 上下文限制",
|
||||||
"description": "每次對話只保留最近幾輪內容(1 輪 = 使用者提問 + AI 回覆),避免上下文過長而消耗過多 Token"
|
"description": "每次對話只保留最近幾輪內容(1 輪 = 使用者提問 + AI 回覆),避免上下文過長而消耗過多 Token"
|
||||||
},
|
},
|
||||||
|
"searchContext": {
|
||||||
|
"title": "搜尋上下文視窗",
|
||||||
|
"description": "搜尋命中訊息時自動攜帶前後的對話上下文,幫助 AI 理解訊息背景。設為 0 則不攜帶上下文",
|
||||||
|
"before": "前",
|
||||||
|
"after": "後"
|
||||||
|
},
|
||||||
"exportFormat": {
|
"exportFormat": {
|
||||||
"title": "對話匯出格式",
|
"title": "對話匯出格式",
|
||||||
"description": "匯出 AI 對話時使用的檔案格式",
|
"description": "匯出 AI 對話時使用的檔案格式",
|
||||||
|
|||||||
@@ -71,6 +71,24 @@ const enableAutoSkill = computed({
|
|||||||
emit('config-changed')
|
emit('config-changed')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const searchContextBefore = computed({
|
||||||
|
get: () => aiGlobalSettings.value.searchContextBefore ?? 3,
|
||||||
|
set: (val: number) => {
|
||||||
|
const clampedVal = Math.max(0, Math.min(20, val ?? 3))
|
||||||
|
promptStore.updateAIGlobalSettings({ searchContextBefore: clampedVal })
|
||||||
|
emit('config-changed')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const searchContextAfter = computed({
|
||||||
|
get: () => aiGlobalSettings.value.searchContextAfter ?? 3,
|
||||||
|
set: (val: number) => {
|
||||||
|
const clampedVal = Math.max(0, Math.min(20, val ?? 3))
|
||||||
|
promptStore.updateAIGlobalSettings({ searchContextAfter: clampedVal })
|
||||||
|
emit('config-changed')
|
||||||
|
},
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -107,6 +125,32 @@ const enableAutoSkill = computed({
|
|||||||
</div>
|
</div>
|
||||||
<UInputNumber v-model="globalMaxHistoryRounds" :min="1" :max="50" class="w-30" />
|
<UInputNumber v-model="globalMaxHistoryRounds" :min="1" :max="50" class="w-30" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索上下文窗口 -->
|
||||||
|
<div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<p class="text-sm font-medium text-gray-900 dark:text-white">
|
||||||
|
{{ t('settings.aiPrompt.searchContext.title') }}
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{{ t('settings.aiPrompt.searchContext.description') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{{ t('settings.aiPrompt.searchContext.before') }}
|
||||||
|
</span>
|
||||||
|
<UInputNumber v-model="searchContextBefore" :min="0" :max="20" class="w-24" />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{{ t('settings.aiPrompt.searchContext.after') }}
|
||||||
|
</span>
|
||||||
|
<UInputNumber v-model="searchContextAfter" :min="0" :max="20" class="w-24" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -804,6 +804,8 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
|||||||
: undefined,
|
: undefined,
|
||||||
mentionedMembers: currentMentionedMembers.length > 0 ? currentMentionedMembers : undefined,
|
mentionedMembers: currentMentionedMembers.length > 0 ? currentMentionedMembers : undefined,
|
||||||
preprocessConfig: serializablePreprocessConfig,
|
preprocessConfig: serializablePreprocessConfig,
|
||||||
|
searchContextBefore: aiGlobalSettings.value.searchContextBefore,
|
||||||
|
searchContextAfter: aiGlobalSettings.value.searchContextAfter,
|
||||||
}
|
}
|
||||||
|
|
||||||
const { requestId: agentReqId, promise: agentPromise } = window.agentApi.runStream(
|
const { requestId: agentReqId, promise: agentPromise } = window.agentApi.runStream(
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ export const usePromptStore = defineStore(
|
|||||||
exportFormat: 'markdown' as 'markdown' | 'txt',
|
exportFormat: 'markdown' as 'markdown' | 'txt',
|
||||||
sqlExportFormat: 'csv' as 'csv' | 'json',
|
sqlExportFormat: 'csv' as 'csv' | 'json',
|
||||||
enableAutoSkill: true,
|
enableAutoSkill: true,
|
||||||
|
searchContextBefore: 2,
|
||||||
|
searchContextAfter: 2,
|
||||||
})
|
})
|
||||||
const customKeywordTemplates = ref<KeywordTemplate[]>([])
|
const customKeywordTemplates = ref<KeywordTemplate[]>([])
|
||||||
const deletedPresetTemplateIds = ref<string[]>([])
|
const deletedPresetTemplateIds = ref<string[]>([])
|
||||||
@@ -105,6 +107,8 @@ export const usePromptStore = defineStore(
|
|||||||
exportFormat: 'markdown' | 'txt'
|
exportFormat: 'markdown' | 'txt'
|
||||||
sqlExportFormat: 'csv' | 'json'
|
sqlExportFormat: 'csv' | 'json'
|
||||||
enableAutoSkill: boolean
|
enableAutoSkill: boolean
|
||||||
|
searchContextBefore: number
|
||||||
|
searchContextAfter: number
|
||||||
}>
|
}>
|
||||||
) {
|
) {
|
||||||
aiGlobalSettings.value = { ...aiGlobalSettings.value, ...settings }
|
aiGlobalSettings.value = { ...aiGlobalSettings.value, ...settings }
|
||||||
|
|||||||
Reference in New Issue
Block a user