Compare commits

...

27 Commits

Author SHA1 Message Date
kangfenmao ebf61b1ce9 feat: plugins 2024-12-30 23:45:47 +08:00
kangfenmao 1a68587684 fix: Microsoft Visual C++ Redistributable #577 2024-12-30 15:07:31 +08:00
kangfenmao 47c455b125 feat: 增加保持并发送的功能 #527 2024-12-30 14:09:59 +08:00
kangfenmao 96124cf58e feat: 增加genspark小程序 #578 2024-12-30 13:10:27 +08:00
juzeon ef975add01 fix: 修复zh-tw语言文件中的乱码 (#579) 2024-12-30 11:49:40 +08:00
n2yt584v2t4nh7y ed49066bab feat: 添加自定义API参数功能 (#564)
* add custom api parameters

* allow more data types for custom api parameters

* pass parameter to api payload

* add custom parameter settings to sidebar

* remove unnecessary object and array types

* extract API custom parameter method to BaseProvider

* add i18n for custom parameter settings

---------

Co-authored-by: 亢奋猫 <kangfenmao@qq.com>
2024-12-29 20:19:07 +08:00
kangfenmao e7545c5a94 feat: 用户自定义话题总结Prompt #562
close #562
2024-12-29 10:20:45 +08:00
kangfenmao fc35df65b8 feat: add release notes pages 2024-12-29 09:49:22 +08:00
littel_penguin66 56ca81d245 Fix incorrect synchronization behavior of webdav auto sync (#568) 2024-12-29 08:44:21 +08:00
kangfenmao 6bc1f4b640 chore(version): 0.9.2 2024-12-27 23:03:17 +08:00
kangfenmao ccb216e76a fix: 模型名后面标注一下服务商 #557 2024-12-27 18:09:22 +08:00
kangfenmao 60931b85ff fix: model settings params step size 2024-12-27 16:47:44 +08:00
kangfenmao dc1dbc7bb6 feat: add jina provider 2024-12-27 16:29:17 +08:00
kangfenmao 5d2efbd62b fix: 需要只发送图片功能 #538 2024-12-27 14:40:44 +08:00
sommermorgentraum 5337017648 feat: Add capabilities for user to load custom CSS #548 2024-12-27 14:11:12 +08:00
kangfenmao c409256ae9 fix: azure openai embedding 2024-12-27 14:02:53 +08:00
kangfenmao 4ac608052c chore: update dependencies and improve project structure 2024-12-27 12:42:17 +08:00
kangfenmao 5e6aaabb23 fix: 小程序中增加 github copilot #547 2024-12-27 12:10:41 +08:00
kangfenmao 8812daeeee fix: 某些输出包含 sub 无法正常显示 #545 2024-12-27 11:54:11 +08:00
kangfenmao 13e3a8478c feat: added topic message update and search state management 2024-12-27 11:48:12 +08:00
kangfenmao 8687985ccb feat: add windows platform support for node file detection and npm package download 2024-12-26 12:38:51 +08:00
kangfenmao 7d54f9b4fa chore(version): 0.9.1 2024-12-26 12:25:58 +08:00
kangfenmao 6b7ba35183 fix: build native module script 2024-12-26 12:25:58 +08:00
kangfenmao 5b42a6d054 feat: add embeding tag to settings 2024-12-26 12:25:58 +08:00
kangfenmao 153e7a9299 refactor: knowledge base engine change to libsql 2024-12-26 10:00:37 +08:00
littel_penguin66 77e0c5172e Add Japanese localization for i18n (#533) 2024-12-25 22:04:29 +08:00
kangfenmao c50ac440c8 fix: knowledge base bugs 2024-12-25 21:54:46 +08:00
83 changed files with 2482 additions and 616 deletions
@@ -0,0 +1,25 @@
diff --git a/src/libsql-db.js b/src/libsql-db.js
index 58c42e4910bd0e53bc497ff9b9702b1f7a961266..250bc97c50a9b790e8798441d904d040f2d2af43 100644
--- a/src/libsql-db.js
+++ b/src/libsql-db.js
@@ -41,9 +41,9 @@ export class LibSqlDb {
}
async similaritySearch(query, k) {
const statement = `SELECT id, pageContent, uniqueLoaderId, source, metadata,
- vector_distance_cos(vector, vector32('[${query.join(',')}]'))
+ vector_distance_cos(vector, vector32('[${query.join(',')}]')) as distance
FROM ${this.tableName}
- ORDER BY vector_distance_cos(vector, vector32('[${query.join(',')}]')) ASC
+ ORDER BY distance ASC
LIMIT ${k};`;
this.debug(`Executing statement - ${truncateCenterString(statement, 700)}`);
const results = await this.client.execute(statement);
@@ -52,7 +52,7 @@ export class LibSqlDb {
return {
metadata,
pageContent: result.pageContent.toString(),
- score: 1,
+ score: 1 - result.distance,
};
});
}
+47
View File
@@ -0,0 +1,47 @@
;Inspired by:
; https://gist.github.com/bogdibota/062919938e1ed388b3db5ea31f52955c
; https://stackoverflow.com/questions/34177547/detect-if-visual-c-redistributable-for-visual-studio-2013-is-installed
; https://stackoverflow.com/a/54391388
; https://github.com/GitCommons/cpp-redist-nsis/blob/main/installer.nsh
;Find latests downloads here:
; https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist
!include LogicLib.nsh
; https://github.com/electron-userland/electron-builder/issues/1122
!ifndef BUILD_UNINSTALLER
Function checkVCRedist
ReadRegDWORD $0 HKLM "SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64" "Installed"
FunctionEnd
!endif
!macro customInit
Push $0
Call checkVCRedist
${If} $0 != "1"
MessageBox MB_YESNO "\
NOTE: ${PRODUCT_NAME} requires $\r$\n\
'Microsoft Visual C++ Redistributable'$\r$\n\
to function properly.$\r$\n$\r$\n\
Download and install now?" /SD IDYES IDYES InstallVCRedist IDNO DontInstall
InstallVCRedist:
inetc::get /CAPTION " " /BANNER "Downloading Microsoft Visual C++ Redistributable..." "https://aka.ms/vs/17/release/vc_redist.x64.exe" "$TEMP\vc_redist.x64.exe"
ExecWait "$TEMP\vc_redist.x64.exe /install /norestart"
;IfErrors InstallError ContinueInstall ; vc_redist exit code is unreliable :(
Call checkVCRedist
${If} $0 == "1"
Goto ContinueInstall
${EndIf}
;InstallError:
MessageBox MB_ICONSTOP "\
There was an unexpected error installing$\r$\n\
Microsoft Visual C++ Redistributable.$\r$\n\
The installation of ${PRODUCT_NAME} cannot continue."
DontInstall:
Abort
${EndIf}
ContinueInstall:
Pop $0
!macroend
+2 -1
View File
@@ -39,6 +39,7 @@ nsis:
createDesktopShortcut: always createDesktopShortcut: always
allowToChangeInstallationDirectory: true allowToChangeInstallationDirectory: true
oneClick: false oneClick: false
include: build/nsis-installer.nsh
mac: mac:
entitlementsInherit: build/entitlements.mac.plist entitlementsInherit: build/entitlements.mac.plist
notarize: false notarize: false
@@ -77,4 +78,4 @@ afterPack: scripts/after-pack.js
afterSign: scripts/notarize.js afterSign: scripts/notarize.js
releaseInfo: releaseInfo:
releaseNotes: | releaseNotes: |
增加知识库功能 增加 Genspark 小程序
+3 -3
View File
@@ -20,7 +20,7 @@ export default defineConfig({
'@llm-tools/embedjs-loader-xml', '@llm-tools/embedjs-loader-xml',
'@llm-tools/embedjs-loader-pdf', '@llm-tools/embedjs-loader-pdf',
'@llm-tools/embedjs-loader-sitemap', '@llm-tools/embedjs-loader-sitemap',
'@llm-tools/embedjs-lancedb' '@llm-tools/embedjs-libsql'
] ]
}), }),
...visualizerPlugin('main') ...visualizerPlugin('main')
@@ -34,7 +34,7 @@ export default defineConfig({
}, },
build: { build: {
rollupOptions: { rollupOptions: {
external: ['@lancedb/lancedb'] external: ['@libsql/client']
} }
} }
}, },
@@ -50,7 +50,7 @@ export default defineConfig({
} }
}, },
optimizeDeps: { optimizeDeps: {
exclude: [] exclude: ['chunk-QH6N6I7P.js', 'chunk-PB73W2YU.js']
} }
} }
}) })
+5 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "CherryStudio", "name": "CherryStudio",
"version": "0.9.0", "version": "0.9.2",
"private": true, "private": true,
"description": "A powerful AI assistant for producer.", "description": "A powerful AI assistant for producer.",
"main": "./out/main/index.js", "main": "./out/main/index.js",
@@ -25,6 +25,7 @@
"typecheck": "npm run typecheck:node && npm run typecheck:web", "typecheck": "npm run typecheck:node && npm run typecheck:web",
"start": "electron-vite preview", "start": "electron-vite preview",
"dev": "electron-vite dev", "dev": "electron-vite dev",
"build:check": "yarn typecheck",
"build": "npm run typecheck && electron-vite build", "build": "npm run typecheck && electron-vite build",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"build:unpack": "dotenv npm run build && electron-builder --dir", "build:unpack": "dotenv npm run build && electron-builder --dir",
@@ -50,7 +51,7 @@
"@electron-toolkit/utils": "^3.0.0", "@electron-toolkit/utils": "^3.0.0",
"@electron/notarize": "^2.5.0", "@electron/notarize": "^2.5.0",
"@llm-tools/embedjs": "patch:@llm-tools/embedjs@npm%3A0.1.25#~/.yarn/patches/@llm-tools-embedjs-npm-0.1.25-ec5645cf36.patch", "@llm-tools/embedjs": "patch:@llm-tools/embedjs@npm%3A0.1.25#~/.yarn/patches/@llm-tools-embedjs-npm-0.1.25-ec5645cf36.patch",
"@llm-tools/embedjs-lancedb": "^0.1.25", "@llm-tools/embedjs-libsql": "patch:@llm-tools/embedjs-libsql@npm%3A0.1.25#~/.yarn/patches/@llm-tools-embedjs-libsql-npm-0.1.25-fad000d74c.patch",
"@llm-tools/embedjs-loader-csv": "^0.1.25", "@llm-tools/embedjs-loader-csv": "^0.1.25",
"@llm-tools/embedjs-loader-markdown": "^0.1.25", "@llm-tools/embedjs-loader-markdown": "^0.1.25",
"@llm-tools/embedjs-loader-msoffice": "^0.1.25", "@llm-tools/embedjs-loader-msoffice": "^0.1.25",
@@ -93,8 +94,8 @@
"@types/react-infinite-scroll-component": "^5.0.0", "@types/react-infinite-scroll-component": "^5.0.0",
"@types/tinycolor2": "^1", "@types/tinycolor2": "^1",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
"antd": "^5.18.3", "antd": "^5.22.5",
"axios": "^1.7.3", "axios": "^1.7.9",
"browser-image-compression": "^2.0.2", "browser-image-compression": "^2.0.2",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"dexie": "^4.0.8", "dexie": "^4.0.8",
+202
View File
@@ -0,0 +1,202 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Github Releases Timeline</title>
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet" />
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/markdown-it@13.0.1/dist/markdown-it.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/typography@0.5.10/dist/typography.min.css"></script>
</head>
<body id="app">
<div :class="isDark ? 'dark-bg' : 'bg'" class="min-h-screen">
<div class="max-w-3xl mx-auto py-12 px-4">
<h1 class="text-3xl font-bold mb-8" :class="isDark ? 'text-white' : 'text-gray-900'">Release Timeline</h1>
<!-- Loading状态 -->
<div v-if="loading" class="text-center py-8">
<div class="inline-block animate-spin rounded-full h-8 w-8 border-4"
:class="isDark ? 'border-gray-700 border-t-blue-500' : 'border-gray-300 border-t-blue-500'"></div>
</div>
<!-- Error 状态 -->
<div v-else-if="error" class="text-red-500 text-center py-8">{{ error }}</div>
<!-- Release 列表 -->
<div v-else class="space-y-8">
<div v-for="release in releases" :key="release.id" class="relative pl-8"
:class="isDark ? 'border-l-2 border-gray-700' : 'border-l-2 border-gray-200'">
<div class="absolute -left-2 top-0 w-4 h-4 rounded-full bg-green-500"></div>
<div class="rounded-lg shadow-sm p-6 transition-shadow"
:class="isDark ? 'bg-black hover:shadow-md hover:shadow-black' : 'bg-white hover:shadow-md'">
<div class="flex items-start justify-between mb-4">
<div>
<h2 class="text-xl font-semibold" :class="isDark ? 'text-white' : 'text-gray-900'">
{{ release.name || release.tag_name }}
</h2>
<p class="text-sm mt-1" :class="isDark ? 'text-gray-400' : 'text-gray-500'">
{{ formatDate(release.published_at) }}
</p>
</div>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium"
:class="isDark ? 'bg-green-900 text-green-200' : 'bg-green-100 text-green-800'">
{{ release.tag_name }}
</span>
</div>
<div class="prose" :class="isDark ? 'text-gray-300 dark-prose' : 'text-gray-600'"
v-html="renderMarkdown(release.body)"></div>
</div>
</div>
</div>
</div>
</div>
<script>
const md = window.markdownit({
breaks: true,
linkify: true
})
const { createApp } = Vue
createApp({
data() {
return {
releases: [],
loading: true,
error: null,
isDark: false
}
},
methods: {
async fetchReleases() {
try {
this.loading = true
this.error = null
const response = await fetch('https://api.github.com/repos/kangfenmao/cherry-studio/releases')
if (!response.ok) {
throw new Error('Failed to fetch releases')
}
this.releases = await response.json()
} catch (err) {
this.error = 'Error loading releases: ' + err.message
} finally {
this.loading = false
}
},
formatDate(dateString) {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
},
renderMarkdown(content) {
if (!content) return ''
return md.render(content)
},
initTheme() {
// 从 URL 参数获取主题设置
const url = new URL(window.location.href)
const theme = url.searchParams.get('theme')
this.isDark = theme === 'dark'
}
},
mounted() {
this.initTheme()
this.fetchReleases()
}
}).mount('#app')
</script>
<style>
/* 基础的 Markdown 样式 */
.prose {
line-height: 1.6;
}
.prose h1 {
font-size: 1.5em;
margin: 1em 0;
}
.prose h2 {
font-size: 1.3em;
margin: 0.8em 0;
}
.prose h3 {
font-size: 1.1em;
margin: 0.6em 0;
}
.prose ul {
list-style-type: disc;
margin-left: 1.5em;
margin-bottom: 1em;
}
.prose ol {
list-style-type: decimal;
margin-left: 1.5em;
margin-bottom: 1em;
}
.prose code {
padding: 0.2em 0.4em;
border-radius: 0.2em;
font-size: 0.9em;
}
.dark .prose code {
background-color: #1f2937;
}
.prose code {
background-color: #f3f4f6;
}
.prose pre code {
display: block;
padding: 1em;
overflow-x: auto;
}
.prose a {
color: #3b82f6;
text-decoration: underline;
}
.dark .prose a {
color: #60a5fa;
}
.prose blockquote {
border-left: 4px solid #e5e7eb;
padding-left: 1em;
margin: 1em 0;
}
.dark .prose blockquote {
border-left-color: #374151;
color: #9ca3af;
}
.dark .prose {
color: #e5e7eb;
}
.dark-bg {
background-color: #151515;
}
.bg {
background-color: #f2f2f2;
}
</style>
</body>
</html>
+8 -10
View File
@@ -18,20 +18,18 @@ exports.default = async function (context) {
'node_modules' 'node_modules'
) )
removeDifferentArchNodeFiles( removeDifferentArchNodeFiles(node_modules_path, '@libsql', arch === Arch.arm64 ? ['darwin-arm64'] : ['darwin-x64'])
node_modules_path,
'@lancedb',
arch === Arch.arm64 ? ['lancedb-darwin-arm64'] : ['lancedb-darwin-x64']
)
} }
if (platform === 'linux') { if (platform === 'linux') {
const node_modules_path = path.join(context.appOutDir, 'resources', 'app.asar.unpacked', 'node_modules') const node_modules_path = path.join(context.appOutDir, 'resources', 'app.asar.unpacked', 'node_modules')
const _arch = const _arch = arch === Arch.arm64 ? ['linux-arm64-gnu', 'linux-arm64-musl'] : ['linux-x64-gnu', 'linux-x64-musl']
arch === Arch.arm64 removeDifferentArchNodeFiles(node_modules_path, '@libsql', _arch)
? ['lancedb-linux-arm64-gnu', 'lancedb-linux-arm64-musl'] }
: ['lancedb-linux-x64-gnu', 'lancedb-linux-x64-musl']
removeDifferentArchNodeFiles(node_modules_path, '@lancedb', _arch) if (platform === 'windows') {
const node_modules_path = path.join(context.appOutDir, 'resources', 'app.asar.unpacked', 'node_modules')
removeDifferentArchNodeFiles(node_modules_path, '@libsql', ['win32-x64-msvc'])
} }
} }
+22 -10
View File
@@ -3,23 +3,35 @@ const { downloadNpmPackage } = require('./utils')
async function downloadNpm(platform) { async function downloadNpm(platform) {
if (!platform || platform === 'mac') { if (!platform || platform === 'mac') {
downloadNpmPackage( downloadNpmPackage(
'@lancedb/lancedb-darwin-arm64', '@libsql/darwin-arm64',
'https://registry.npmjs.org/@lancedb/lancedb-darwin-arm64/-/lancedb-darwin-arm64-0.14.0.tgz' 'https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.4.7.tgz'
)
downloadNpmPackage(
'@lancedb/lancedb-darwin-x64',
'https://registry.npmjs.org/@lancedb/lancedb-darwin-x64/-/lancedb-darwin-x64-0.14.0.tgz'
) )
downloadNpmPackage('@libsql/darwin-x64', 'https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.4.7.tgz')
} }
if (!platform || platform === 'linux') { if (!platform || platform === 'linux') {
downloadNpmPackage( downloadNpmPackage(
'@lancedb/lancedb-linux-arm64-gnu', '@libsql/linux-arm64-gnu',
'https://registry.npmjs.org/@lancedb/lancedb-linux-arm64-gnu/-/lancedb-linux-arm64-gnu-0.14.0.tgz' 'https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.4.7.tgz'
) )
downloadNpmPackage( downloadNpmPackage(
'@lancedb/lancedb-linux-x64-gnu', '@libsql/linux-arm64-musl',
'https://registry.npmjs.org/@lancedb/lancedb-linux-x64-gnu/-/lancedb-linux-x64-gnu-0.14.0.tgz' 'https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.4.7.tgz'
)
downloadNpmPackage(
'@libsql/linux-x64-gnu',
'https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.4.7.tgz'
)
downloadNpmPackage(
'@libsql/linux-x64-musl',
'https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.4.7.tgz'
)
}
if (!platform || platform === 'windows') {
downloadNpmPackage(
'@libsql/win32-x64-msvc',
'https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.4.7.tgz'
) )
} }
} }
+8
View File
@@ -1,7 +1,9 @@
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
import vm from 'node:vm'
import { Shortcut, ThemeMode } from '@types' import { Shortcut, ThemeMode } from '@types'
import axios from 'axios'
import { BrowserWindow, ipcMain, ProxyConfig, session, shell } from 'electron' import { BrowserWindow, ipcMain, ProxyConfig, session, shell } from 'electron'
import log from 'electron-log' import log from 'electron-log'
@@ -154,4 +156,10 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
ipcMain.handle('knowledge-base:add', KnowledgeService.add) ipcMain.handle('knowledge-base:add', KnowledgeService.add)
ipcMain.handle('knowledge-base:remove', KnowledgeService.remove) ipcMain.handle('knowledge-base:remove', KnowledgeService.remove)
ipcMain.handle('knowledge-base:search', KnowledgeService.search) ipcMain.handle('knowledge-base:search', KnowledgeService.search)
// vm
ipcMain.handle('run-js', (_, code: string) => {
const context = vm.createContext(Object.assign({ fetch: fetch, URL: URL, axios: axios }, global))
return vm.runInContext(code, context)
})
} }
+22 -11
View File
@@ -2,14 +2,15 @@ import * as fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
import { LocalPathLoader, RAGApplication, RAGApplicationBuilder, TextLoader } from '@llm-tools/embedjs' import { LocalPathLoader, RAGApplication, RAGApplicationBuilder, TextLoader } from '@llm-tools/embedjs'
import { AddLoaderReturn, ExtractChunkData } from '@llm-tools/embedjs-interfaces' import type { AddLoaderReturn, ExtractChunkData } from '@llm-tools/embedjs-interfaces'
import { LanceDb } from '@llm-tools/embedjs-lancedb' import { LibSqlDb } from '@llm-tools/embedjs-libsql'
import { MarkdownLoader } from '@llm-tools/embedjs-loader-markdown' import { MarkdownLoader } from '@llm-tools/embedjs-loader-markdown'
import { DocxLoader, ExcelLoader, PptLoader } from '@llm-tools/embedjs-loader-msoffice' import { DocxLoader, ExcelLoader, PptLoader } from '@llm-tools/embedjs-loader-msoffice'
import { PdfLoader } from '@llm-tools/embedjs-loader-pdf' import { PdfLoader } from '@llm-tools/embedjs-loader-pdf'
import { SitemapLoader } from '@llm-tools/embedjs-loader-sitemap' import { SitemapLoader } from '@llm-tools/embedjs-loader-sitemap'
import { WebLoader } from '@llm-tools/embedjs-loader-web' import { WebLoader } from '@llm-tools/embedjs-loader-web'
import { OpenAiEmbeddings } from '@llm-tools/embedjs-openai' import { AzureOpenAiEmbeddings, OpenAiEmbeddings } from '@llm-tools/embedjs-openai'
import { getInstanceName } from '@main/utils'
import { FileType, KnowledgeBaseParams, KnowledgeItem } from '@types' import { FileType, KnowledgeBaseParams, KnowledgeItem } from '@types'
import { app } from 'electron' import { app } from 'electron'
@@ -30,21 +31,31 @@ class KnowledgeService {
id, id,
model, model,
apiKey, apiKey,
apiVersion,
baseURL, baseURL,
dimensions dimensions
}: KnowledgeBaseParams): Promise<RAGApplication> => { }: KnowledgeBaseParams): Promise<RAGApplication> => {
return new RAGApplicationBuilder() return new RAGApplicationBuilder()
.setModel('NO_MODEL') .setModel('NO_MODEL')
.setEmbeddingModel( .setEmbeddingModel(
new OpenAiEmbeddings({ apiVersion
model, ? new AzureOpenAiEmbeddings({
apiKey, azureOpenAIApiKey: apiKey,
configuration: { baseURL }, azureOpenAIApiVersion: apiVersion,
dimensions, azureOpenAIApiDeploymentName: model,
batchSize: 20 azureOpenAIApiInstanceName: getInstanceName(baseURL),
}) dimensions,
batchSize: 15
})
: new OpenAiEmbeddings({
model,
apiKey,
configuration: { baseURL },
dimensions,
batchSize: 15
})
) )
.setVectorDatabase(new LanceDb({ path: path.join(this.storageDir, id) })) .setVectorDatabase(new LibSqlDb({ path: path.join(this.storageDir, id) }))
.build() .build()
} }
+4 -3
View File
@@ -3,7 +3,7 @@ import { isLinux, isWin } from '@main/constant'
import { app, BrowserWindow, Menu, MenuItem, shell } from 'electron' import { app, BrowserWindow, Menu, MenuItem, shell } from 'electron'
import Logger from 'electron-log' import Logger from 'electron-log'
import windowStateKeeper from 'electron-window-state' import windowStateKeeper from 'electron-window-state'
import { join } from 'path' import path, { join } from 'path'
import icon from '../../../build/icon.png?asset' import icon from '../../../build/icon.png?asset'
import { titleBarOverlayDark, titleBarOverlayLight } from '../config' import { titleBarOverlayDark, titleBarOverlayLight } from '../config'
@@ -137,8 +137,9 @@ export class WindowService {
const { url } = details const { url } = details
if (url.includes('http://file/')) { if (url.includes('http://file/')) {
const fileUrl = url.replace('http://file/', '') const fileName = url.replace('http://file/', '')
const filePath = decodeURIComponent(fileUrl) const storageDir = path.join(app.getPath('userData'), 'Data', 'Files')
const filePath = storageDir + '/' + fileName
shell.openPath(filePath).catch((err) => Logger.error('Failed to open file:', err)) shell.openPath(filePath).catch((err) => Logger.error('Failed to open file:', err))
} else { } else {
shell.openExternal(details.url) shell.openExternal(details.url)
+8
View File
@@ -14,3 +14,11 @@ export function getDataPath() {
} }
return dataPath return dataPath
} }
export function getInstanceName(baseURL: string) {
try {
return new URL(baseURL).host.split('.')[0]
} catch (error) {
return ''
}
}
+2
View File
@@ -1,4 +1,5 @@
import EnUs from '../../renderer/src/i18n/locales/en-us.json' import EnUs from '../../renderer/src/i18n/locales/en-us.json'
import JaJP from '../../renderer/src/i18n/locales/ja-jp.json'
import RuRu from '../../renderer/src/i18n/locales/ru-ru.json' import RuRu from '../../renderer/src/i18n/locales/ru-ru.json'
import ZhCn from '../../renderer/src/i18n/locales/zh-cn.json' import ZhCn from '../../renderer/src/i18n/locales/zh-cn.json'
import ZhTw from '../../renderer/src/i18n/locales/zh-tw.json' import ZhTw from '../../renderer/src/i18n/locales/zh-tw.json'
@@ -7,6 +8,7 @@ const locales = {
'en-US': EnUs, 'en-US': EnUs,
'zh-CN': ZhCn, 'zh-CN': ZhCn,
'zh-TW': ZhTw, 'zh-TW': ZhTw,
'ja-JP': JaJP,
'ru-RU': RuRu 'ru-RU': RuRu
} }
+3
View File
@@ -76,6 +76,9 @@ declare global {
remove: ({ uniqueId, base }: { uniqueId: string; base: KnowledgeBaseParams }) => Promise<void> remove: ({ uniqueId, base }: { uniqueId: string; base: KnowledgeBaseParams }) => Promise<void>
search: ({ search, base }: { search: string; base: KnowledgeBaseParams }) => Promise<ExtractChunkData[]> search: ({ search, base }: { search: string; base: KnowledgeBaseParams }) => Promise<ExtractChunkData[]>
} }
vm: {
run: (code: string) => Promise<any>
}
} }
} }
} }
+3
View File
@@ -70,6 +70,9 @@ const api = {
ipcRenderer.invoke('knowledge-base:remove', { uniqueId, base }), ipcRenderer.invoke('knowledge-base:remove', { uniqueId, base }),
search: ({ search, base }: { search: string; base: KnowledgeBaseParams }) => search: ({ search, base }: { search: string; base: KnowledgeBaseParams }) =>
ipcRenderer.invoke('knowledge-base:search', { search, base }) ipcRenderer.invoke('knowledge-base:search', { search, base })
},
vm: {
run: (code: string) => ipcRenderer.invoke('run-js', code)
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

@@ -58,8 +58,8 @@ const PopoverContent = styled(Scrollbar)``
const AppsContainer = styled.div` const AppsContainer = styled.div`
display: grid; display: grid;
grid-template-columns: repeat(6, 1fr); grid-template-columns: repeat(6, minmax(90px, 1fr));
gap: 25px 35px; gap: 25px;
` `
export default AppStorePopover export default AppStorePopover
@@ -115,7 +115,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
.flatMap((p) => p.models || []) .flatMap((p) => p.models || [])
.filter((m) => pinnedModels.includes(getModelUniqId(m))) .filter((m) => pinnedModels.includes(getModelUniqId(m)))
.map((m) => ({ .map((m) => ({
key: getModelUniqId(m), key: getModelUniqId(m) + '_pinned',
label: ( label: (
<ModelItem> <ModelItem>
{m?.name} {isVisionModel(m) && <VisionIcon />} {m?.name} {isVisionModel(m) && <VisionIcon />}
@@ -4,6 +4,7 @@ import { TextAreaProps } from 'antd/lib/input'
import { TextAreaRef } from 'antd/lib/input/TextArea' import { TextAreaRef } from 'antd/lib/input/TextArea'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { TopView } from '../TopView' import { TopView } from '../TopView'
@@ -11,13 +12,14 @@ interface ShowParams {
text: string text: string
textareaProps?: TextAreaProps textareaProps?: TextAreaProps
modalProps?: ModalProps modalProps?: ModalProps
children?: (props: { onOk?: () => void; onCancel?: () => void }) => React.ReactNode
} }
interface Props extends ShowParams { interface Props extends ShowParams {
resolve: (data: any) => void resolve: (data: any) => void
} }
const PopupContainer: React.FC<Props> = ({ text, textareaProps, modalProps, resolve }) => { const PopupContainer: React.FC<Props> = ({ text, textareaProps, modalProps, resolve, children }) => {
const [open, setOpen] = useState(true) const [open, setOpen] = useState(true)
const { t } = useTranslation() const { t } = useTranslation()
const [textValue, setTextValue] = useState(text) const [textValue, setTextValue] = useState(text)
@@ -73,12 +75,17 @@ const PopupContainer: React.FC<Props> = ({ text, textareaProps, modalProps, reso
onInput={resizeTextArea} onInput={resizeTextArea}
onChange={(e) => setTextValue(e.target.value)} onChange={(e) => setTextValue(e.target.value)}
/> />
<ChildrenContainer>{children && children({ onOk, onCancel })}</ChildrenContainer>
</Modal> </Modal>
) )
} }
const TopViewKey = 'TextEditPopup' const TopViewKey = 'TextEditPopup'
const ChildrenContainer = styled.div`
position: relative;
`
export default class TextEditPopup { export default class TextEditPopup {
static topviewId = 0 static topviewId = 0
static hide() { static hide() {
+14
View File
@@ -6,6 +6,8 @@ import DoubaoAppLogo from '@renderer/assets/images/apps/doubao.png'
import DuckDuckGoAppLogo from '@renderer/assets/images/apps/duckduckgo.webp' import DuckDuckGoAppLogo from '@renderer/assets/images/apps/duckduckgo.webp'
import FeloAppLogo from '@renderer/assets/images/apps/felo.png' import FeloAppLogo from '@renderer/assets/images/apps/felo.png'
import GeminiAppLogo from '@renderer/assets/images/apps/gemini.png' import GeminiAppLogo from '@renderer/assets/images/apps/gemini.png'
import GensparkLogo from '@renderer/assets/images/apps/genspark.jpg'
import GithubCopilotLogo from '@renderer/assets/images/apps/github-copilot.webp'
import HuggingChatLogo from '@renderer/assets/images/apps/huggingchat.svg' import HuggingChatLogo from '@renderer/assets/images/apps/huggingchat.svg'
import KimiAppLogo from '@renderer/assets/images/apps/kimi.jpg' import KimiAppLogo from '@renderer/assets/images/apps/kimi.jpg'
import MetasoAppLogo from '@renderer/assets/images/apps/metaso.webp' import MetasoAppLogo from '@renderer/assets/images/apps/metaso.webp'
@@ -223,6 +225,18 @@ const _apps: MinAppType[] = [
logo: ThinkAnyLogo, logo: ThinkAnyLogo,
url: 'https://thinkany.ai/', url: 'https://thinkany.ai/',
bodered: true bodered: true
},
{
id: 'github-copilot',
name: 'GitHub Copilot',
logo: GithubCopilotLogo,
url: 'https://github.com/copilot'
},
{
id: 'genspark',
name: 'Genspark',
logo: GensparkLogo,
url: 'https://www.genspark.ai/'
} }
] ]
+59 -4
View File
@@ -152,7 +152,7 @@ export const VISION_REGEX = new RegExp(
) )
export const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview/i export const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview/i
export const EMBEDDING_REGEX = /(?:^text-|embed|rerank|davinci|babbage|bge-|base|retrieval|uae-|gte-)/i export const EMBEDDING_REGEX = /(?:^text-|embed|rerank|davinci|babbage|bge-|e5-|LLM2Vec|retrieval|uae-|gte-|jina)/i
export const NOT_SUPPORTED_REGEX = /(?:^tts|rerank|whisper|speech)/i export const NOT_SUPPORTED_REGEX = /(?:^tts|rerank|whisper|speech)/i
export function getModelLogo(modelId: string) { export function getModelLogo(modelId: string) {
@@ -347,8 +347,14 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
{ {
id: 'deepseek-chat', id: 'deepseek-chat',
provider: 'deepseek', provider: 'deepseek',
name: 'DeepSeek V2.5', name: 'DeepSeek Chat',
group: 'DeepSeek V2.5' group: 'DeepSeek Chat'
},
{
id: 'deepseek-coder',
provider: 'deepseek',
name: 'DeepSeek Coder',
group: 'DeepSeek Coder'
} }
], ],
together: [ together: [
@@ -694,7 +700,56 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
group: 'Mistral' group: 'Mistral'
} }
], ],
jina: [], jina: [
{
id: 'jina-clip-v1',
provider: 'jina',
name: 'jina-clip-v1',
group: 'Jina Clip'
},
{
id: 'jina-clip-v2',
provider: 'jina',
name: 'jina-clip-v2',
group: 'Jina Clip'
},
{
id: 'jina-embeddings-v2-base-en',
provider: 'jina',
name: 'jina-embeddings-v2-base-en',
group: 'Jina Embeddings V2'
},
{
id: 'jina-embeddings-v2-base-es',
provider: 'jina',
name: 'jina-embeddings-v2-base-es',
group: 'Jina Embeddings V2'
},
{
id: 'jina-embeddings-v2-base-de',
provider: 'jina',
name: 'jina-embeddings-v2-base-de',
group: 'Jina Embeddings V2'
},
{
id: 'jina-embeddings-v2-base-zh',
provider: 'jina',
name: 'jina-embeddings-v2-base-zh',
group: 'Jina Embeddings V2'
},
{
id: 'jina-embeddings-v2-base-code',
provider: 'jina',
name: 'jina-embeddings-v2-base-code',
group: 'Jina Embeddings V2'
},
{
id: 'jina-embeddings-v3',
provider: 'jina',
name: 'jina-embeddings-v3',
group: 'Jina Embeddings V3'
}
],
aihubmix: [ aihubmix: [
{ {
id: 'gpt-4o-mini', id: 'gpt-4o-mini',
+2 -1
View File
@@ -45,7 +45,7 @@ export const AGENT_PROMPT = `
` `
export const SUMMARIZE_PROMPT = export const SUMMARIZE_PROMPT =
'你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不要使用标点符号和其他特殊符号' '你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,标题语言与用户的首要语言一致,不要使用标点符号和其他特殊符号'
export const TRANSLATE_PROMPT = export const TRANSLATE_PROMPT =
'You are a translation expert. Translate from input language to {{target_language}}, provide the translation result directly without any explanation and keep original format. Do not translate if the target language is the same as the source language.' 'You are a translation expert. Translate from input language to {{target_language}}, provide the translation result directly without any explanation and keep original format. Do not translate if the target language is the same as the source language.'
@@ -56,6 +56,7 @@ export const REFERENCE_PROMPT = `请根据参考资料回答问题,并使用
1. **脚注标记**:在正文中使用 [^数字] 的形式标记脚注,例如 [^1]。 1. **脚注标记**:在正文中使用 [^数字] 的形式标记脚注,例如 [^1]。
2. **脚注内容**:在文档末尾使用 [^数字]: 脚注内容 的形式定义脚注的具体内容 2. **脚注内容**:在文档末尾使用 [^数字]: 脚注内容 的形式定义脚注的具体内容
3. **脚注内容**:应该尽量简洁
## 我的问题是: ## 我的问题是:
+1 -1
View File
@@ -358,7 +358,7 @@ export const PROVIDER_CONFIG = {
url: 'https://aihubmix.com?aff=SJyh' url: 'https://aihubmix.com?aff=SJyh'
}, },
websites: { websites: {
official: 'https://aihubmix.com/', official: 'https://aihubmix.com?aff=SJyh',
apiKey: 'https://aihubmix.com?aff=SJyh', apiKey: 'https://aihubmix.com?aff=SJyh',
docs: 'https://doc.aihubmix.com/', docs: 'https://doc.aihubmix.com/',
models: 'https://aihubmix.com/models' models: 'https://aihubmix.com/models'
@@ -2,6 +2,7 @@ import { useSettings } from '@renderer/hooks/useSettings'
import { LanguageVarious } from '@renderer/types' import { LanguageVarious } from '@renderer/types'
import { ConfigProvider, theme } from 'antd' import { ConfigProvider, theme } from 'antd'
import enUS from 'antd/locale/en_US' import enUS from 'antd/locale/en_US'
import jaJP from 'antd/locale/ja_JP'
import ruRU from 'antd/locale/ru_RU' import ruRU from 'antd/locale/ru_RU'
import zhCN from 'antd/locale/zh_CN' import zhCN from 'antd/locale/zh_CN'
import zhTW from 'antd/locale/zh_TW' import zhTW from 'antd/locale/zh_TW'
@@ -59,6 +60,8 @@ function getAntdLocale(language: LanguageVarious) {
return enUS return enUS
case 'ru-RU': case 'ru-RU':
return ruRU return ruRU
case 'ja-JP':
return jaJP
default: default:
return zhCN return zhCN
+24 -2
View File
@@ -16,8 +16,16 @@ import useUpdateHandler from './useUpdateHandler'
export function useAppInit() { export function useAppInit() {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { proxyUrl, language, windowStyle, manualUpdateCheck, proxyMode, webdavAutoSync, webdavSyncInterval } = const {
useSettings() proxyUrl,
language,
windowStyle,
manualUpdateCheck,
proxyMode,
webdavAutoSync,
webdavSyncInterval,
customCss
} = useSettings()
const { minappShow } = useRuntime() const { minappShow } = useRuntime()
const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel() const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel()
const avatar = useLiveQuery(() => db.settings.get('image://avatar')) const avatar = useLiveQuery(() => db.settings.get('image://avatar'))
@@ -83,4 +91,18 @@ export function useAppInit() {
useEffect(() => { useEffect(() => {
import('@renderer/queue/KnowledgeQueue') import('@renderer/queue/KnowledgeQueue')
}, []) }, [])
useEffect(() => {
const oldCustomCss = document.getElementById('user-defined-custom-css')
if (oldCustomCss) {
oldCustomCss.remove()
}
if (customCss) {
const style = document.createElement('style')
style.id = 'user-defined-custom-css'
style.textContent = customCss
document.head.appendChild(style)
}
}, [customCss])
} }
+14 -1
View File
@@ -1,3 +1,4 @@
import { db } from '@renderer/databases'
import { getDefaultTopic } from '@renderer/services/AssistantService' import { getDefaultTopic } from '@renderer/services/AssistantService'
import { useAppDispatch, useAppSelector } from '@renderer/store' import { useAppDispatch, useAppSelector } from '@renderer/store'
import { import {
@@ -50,8 +51,20 @@ export function useAssistant(id: string) {
dispatch(removeTopic({ assistantId: assistant.id, topic })) dispatch(removeTopic({ assistantId: assistant.id, topic }))
}, },
moveTopic: (topic: Topic, toAssistant: Assistant) => { moveTopic: (topic: Topic, toAssistant: Assistant) => {
dispatch(addTopic({ assistantId: toAssistant.id, topic: { ...topic } })) dispatch(addTopic({ assistantId: toAssistant.id, topic: { ...topic, assistantId: toAssistant.id } }))
dispatch(removeTopic({ assistantId: assistant.id, topic })) dispatch(removeTopic({ assistantId: assistant.id, topic }))
// update topic messages in database
db.topics
.where('id')
.equals(topic.id)
.modify((dbTopic) => {
if (dbTopic.messages) {
dbTopic.messages = dbTopic.messages.map((message) => ({
...message,
assistantId: toAssistant.id
}))
}
})
}, },
updateTopic: (topic: Topic) => dispatch(updateTopic({ assistantId: assistant.id, topic })), updateTopic: (topic: Topic) => dispatch(updateTopic({ assistantId: assistant.id, topic })),
updateTopics: (topics: Topic[]) => dispatch(updateTopics({ assistantId: assistant.id, topics })), updateTopics: (topics: Topic[]) => dispatch(updateTopics({ assistantId: assistant.id, topics })),
+6 -1
View File
@@ -1,8 +1,9 @@
import { useAppDispatch, useAppSelector } from '@renderer/store' import store, { useAppDispatch, useAppSelector } from '@renderer/store'
import { import {
SendMessageShortcut, SendMessageShortcut,
setSendMessageShortcut as _setSendMessageShortcut, setSendMessageShortcut as _setSendMessageShortcut,
setTheme, setTheme,
SettingsState,
setTopicPosition, setTopicPosition,
setTray, setTray,
setWindowStyle setWindowStyle
@@ -41,3 +42,7 @@ export function useMessageStyle() {
isBubbleStyle isBubbleStyle
} }
} }
export const getStoreSetting = (key: keyof SettingsState) => {
return store.getState().settings[key]
}
+2
View File
@@ -2,6 +2,7 @@ import i18n from 'i18next'
import { initReactI18next } from 'react-i18next' import { initReactI18next } from 'react-i18next'
import enUS from './locales/en-us.json' import enUS from './locales/en-us.json'
import jaJP from './locales/ja-jp.json'
import ruRU from './locales/ru-ru.json' import ruRU from './locales/ru-ru.json'
import zhCN from './locales/zh-cn.json' import zhCN from './locales/zh-cn.json'
import zhTW from './locales/zh-tw.json' import zhTW from './locales/zh-tw.json'
@@ -10,6 +11,7 @@ const resources = {
'en-US': enUS, 'en-US': enUS,
'zh-CN': zhCN, 'zh-CN': zhCN,
'zh-TW': zhTW, 'zh-TW': zhTW,
'ja-JP': jaJP,
'ru-RU': ruRU 'ru-RU': ruRU
} }
+21 -4
View File
@@ -112,7 +112,8 @@
"topics.list": "Topic List", "topics.list": "Topic List",
"topics.move_to": "Move to", "topics.move_to": "Move to",
"topics.title": "Topics", "topics.title": "Topics",
"translate": "Translate" "translate": "Translate",
"resend": "Resend"
}, },
"common": { "common": {
"and": "and", "and": "and",
@@ -229,6 +230,7 @@
"error.enter.api.host": "Please enter your API host first", "error.enter.api.host": "Please enter your API host first",
"error.enter.api.key": "Please enter your API key first", "error.enter.api.key": "Please enter your API key first",
"error.enter.model": "Please select a model first", "error.enter.model": "Please select a model first",
"error.enter.name": "Please enter the name of the knowledge base",
"error.invalid.proxy.url": "Invalid proxy URL", "error.invalid.proxy.url": "Invalid proxy URL",
"error.invalid.webdav": "Invalid WebDAV settings", "error.invalid.webdav": "Invalid WebDAV settings",
"message.code_style": "Code style", "message.code_style": "Code style",
@@ -249,7 +251,7 @@
"upgrade.success.title": "Upgrade successfully", "upgrade.success.title": "Upgrade successfully",
"regenerate.confirm": "Regenerating will replace current message", "regenerate.confirm": "Regenerating will replace current message",
"copy.success": "Copied!", "copy.success": "Copied!",
"get_embedding_dimensions": "Failed to get embedding dimensions" "error.get_embedding_dimensions": "Failed to get embedding dimensions"
}, },
"minapp": { "minapp": {
"title": "MinApp" "title": "MinApp"
@@ -380,6 +382,8 @@
"display.sidebar.files.icon": "Show Files icon", "display.sidebar.files.icon": "Show Files icon",
"display.sidebar.title": "Sidebar Settings", "display.sidebar.title": "Sidebar Settings",
"display.topic.title": "Topic Settings", "display.topic.title": "Topic Settings",
"display.custom.css": "Custom CSS",
"display.custom.css.placeholder": "/* Put custom CSS here */",
"input.auto_translate_with_space": "Quickly translate with 3 spaces", "input.auto_translate_with_space": "Quickly translate with 3 spaces",
"messages.divider": "Show divider between messages", "messages.divider": "Show divider between messages",
"messages.input.paste_long_text_as_file": "Paste long text as file", "messages.input.paste_long_text_as_file": "Paste long text as file",
@@ -414,6 +418,7 @@
"models.translate_model_prompt_title": "Translate Model Prompt", "models.translate_model_prompt_title": "Translate Model Prompt",
"models.topic_naming_model_setting_title": "Topic Naming Model Settings", "models.topic_naming_model_setting_title": "Topic Naming Model Settings",
"models.enable_topic_naming": "Topic Auto Naming", "models.enable_topic_naming": "Topic Auto Naming",
"models.topic_naming_prompt": "Topic Naming Prompt",
"provider": { "provider": {
"add.name": "Provider Name", "add.name": "Provider Name",
"add.name.placeholder": "Example: OpenAI", "add.name.placeholder": "Example: OpenAI",
@@ -562,7 +567,8 @@
"add_directory": "Add Directory", "add_directory": "Add Directory",
"directory_placeholder": "Enter Directory Path", "directory_placeholder": "Enter Directory Path",
"model_info": "Model Info", "model_info": "Model Info",
"not_support": "Knowledge base database engine updated, the knowledge base will no longer be supported, please create a new knowledge base" "not_support": "Knowledge base database engine updated, the knowledge base will no longer be supported, please create a new knowledge base",
"source": "Source"
}, },
"models": { "models": {
"pinned": "Pinned", "pinned": "Pinned",
@@ -581,7 +587,18 @@
"embedding": "Embedding", "embedding": "Embedding",
"embedding_model": "Embedding Model", "embedding_model": "Embedding Model",
"embedding_model_tooltip": "Add in Settings->Model Provider->Manage", "embedding_model_tooltip": "Add in Settings->Model Provider->Manage",
"dimensions": "Dimensions {{dimensions}}" "dimensions": "Dimensions {{dimensions}}",
"custom_parameters": "Custom Parameters",
"add_parameter": "Add Parameter",
"parameter_name": "Parameter Name",
"parameter_type": {
"string": "Text",
"number": "Number",
"boolean": "Boolean"
}
},
"prompts": {
"summarize": "You are an assistant who is good at conversation. You need to summarize the user's conversation into a title of 10 characters or less, ensuring it matches the user's primary language without using punctuation or other special symbols."
} }
} }
} }
+587
View File
@@ -0,0 +1,587 @@
{
"translation": {
"agents": {
"add.button": "アシスタントに追加",
"add.name": "名前",
"add.name.placeholder": "名前を入力",
"add.prompt": "プロンプト",
"add.prompt.placeholder": "プロンプトを入力",
"add.title": "エージェントを作成",
"delete.popup.content": "このエージェントを削除してもよろしいですか?",
"edit.message.add.title": "追加",
"edit.message.assistant.placeholder": "アシスタントのメッセージを入力",
"edit.message.assistant.title": "アシスタント",
"edit.message.empty.content": "会話の入力内容が空です",
"edit.message.group.title": "メッセージグループ",
"edit.message.title": "プリセットメッセージ",
"edit.message.user.placeholder": "ユーザーメッセージを入力",
"edit.message.user.title": "ユーザー",
"edit.model.select.title": "モデルを選択",
"edit.settings.hide_preset_messages": "プリセットメッセージを非表示",
"edit.title": "エージェントを編集",
"manage.title": "エージェントを管理",
"my_agents": "マイエージェント",
"search.no_results": "結果が見つかりません",
"sorting.title": "並び替え",
"tag.agent": "エージェント",
"tag.default": "デフォルト",
"tag.new": "新規",
"tag.system": "システム",
"title": "エージェント"
},
"assistants": {
"abbr": "アシスタント",
"clear.content": "トピックをクリアすると、アシスタント内のすべてのトピックとファイルが削除されます。続行しますか?",
"clear.title": "トピックをクリア",
"copy.title": "アシスタントをコピー",
"delete.content": "アシスタントを削除すると、そのアシスタントのすべてのトピックとファイルが削除されます。削除しますか?",
"delete.title": "アシスタントを削除",
"edit.title": "アシスタントを編集",
"save.success": "保存に成功しました",
"save.title": "エージェントに保存",
"search": "アシスタントを検索...",
"settings.auto_reset_model": "自動リセットモデル",
"settings.auto_reset_model.tip": "新しいトピックを作成する際にモデルを自動的にリセットします",
"settings.default_model": "デフォルトモデル",
"settings.model": "モデル設定",
"settings.preset_messages": "プリセットメッセージ",
"settings.prompt": "プロンプト設定",
"title": "アシスタント"
},
"button": {
"add": "追加",
"added": "追加済み",
"collapse": "折りたたむ",
"manage": "管理",
"select_model": "モデルを選択",
"show.all": "すべて表示"
},
"chat": {
"add.assistant.title": "アシスタントを追加",
"artifacts.button.download": "ダウンロード",
"artifacts.button.preview": "プレビュー",
"assistant.search.placeholder": "検索",
"default.description": "こんにちは、私はデフォルトのアシスタントです。すぐにチャットを始められます。",
"default.name": "⭐️ デフォルトアシスタント",
"default.topic.name": "デフォルトトピック",
"input.clear": "クリア",
"input.clear.content": "現在のトピックのすべてのメッセージをクリアしますか?",
"input.clear.title": "すべてのメッセージをクリアしますか?",
"input.collapse": "折りたたむ",
"input.context_count.tip": "コンテキスト数",
"input.estimated_tokens.tip": "推定トークン数",
"input.expand": "展開",
"input.new.context": "コンテキストをクリア",
"input.new_topic": "新しいトピック {{Command}}",
"input.pause": "一時停止",
"input.placeholder": "ここにメッセージを入力...",
"input.send": "送信",
"input.settings": "設定",
"input.topics": " トピック ",
"input.translate": "英語に翻訳",
"input.upload": "画像またはドキュメントをアップロード",
"input.web_search": "ウェブ検索を有効にする",
"input.knowledge_base": "ナレッジベース",
"message.new.branch": "新しいブランチ",
"message.new.branch.created": "新しいブランチが作成されました",
"message.regenerate.model": "モデルを切り替え",
"message.new.context": "新しいコンテキスト",
"save": "保存",
"settings.code_collapsible": "コードブロックを折りたたむ",
"settings.context_count": "コンテキスト",
"settings.context_count.tip": "コンテキストに保持する以前のメッセージの数",
"settings.max": "最大",
"settings.max_tokens": "最大トークン制限を有効にする",
"settings.max_tokens.tip": "モデルが生成できる最大トークン数。通常のチャットでは500-800、短いテキスト生成では800-2000、コード生成では2000-3600、長いテキスト生成では4000以上を推奨",
"settings.reset": "リセット",
"settings.set_as_default": "デフォルトのアシスタントに適用",
"settings.show_line_numbers": "コードに行番号を表示",
"settings.temperature": "温度",
"settings.temperature.tip": "低い値はモデルをより創造的で予測不可能にし、高い値はより決定論的で正確にします",
"settings.top_p": "Top-P",
"settings.top_p.tip": "デフォルト値は1で、値が小さいほど回答の多様性が減り、理解しやすくなります。値が大きいほど、AIの語彙範囲が広がり、多様性が増します",
"suggestions.title": "提案された質問",
"topics.auto_rename": "自動リネーム",
"topics.clear.title": "メッセージをクリア",
"topics.edit.placeholder": "新しい名前を入力",
"topics.edit.title": "名前を編集",
"topics.export.image": "画像としてエクスポート",
"topics.export.md": "Markdownとしてエクスポート",
"topics.export.title": "エクスポート",
"topics.export.word": "Wordとしてエクスポート",
"topics.list": "トピックリスト",
"topics.move_to": "移動先",
"topics.title": "トピック",
"translate": "翻訳",
"resend": "再送信"
},
"common": {
"and": "と",
"assistant": "アシスタント",
"avatar": "アバター",
"back": "戻る",
"cancel": "キャンセル",
"chat": "チャット",
"close": "閉じる",
"copy": "コピー",
"cut": "切り取り",
"default": "デフォルト",
"delete": "削除",
"description": "説明",
"docs": "ドキュメント",
"download": "ダウンロード",
"duplicate": "複製",
"edit": "編集",
"footnotes": "脚注",
"language": "言語",
"model": "モデル",
"models": "モデル",
"name": "名前",
"paste": "貼り付け",
"prompt": "プロンプト",
"provider": "プロバイダー",
"regenerate": "再生成",
"rename": "名前を変更",
"reset": "リセット",
"save": "保存",
"search": "検索",
"select": "選択",
"topics": "トピック",
"warning": "警告",
"you": "あなた",
"clear": "クリア",
"add": "追加"
},
"error": {
"backup.file_format": "バックアップファイルの形式エラー",
"chat.response": "エラーが発生しました。APIキーが設定されていない場合は、設定 > プロバイダーでキーを設定してください",
"no_api_key": "APIキーが設定されていません",
"provider_disabled": "モデルプロバイダーが有効になっていません",
"render": {
"title": "レンダリングエラー",
"description": "数式のレンダリングに失敗しました。数式の形式が正しいか確認してください"
}
},
"export": {
"assistant": "アシスタント",
"attached_files": "添付ファイル",
"conversation_details": "会話の詳細",
"conversation_history": "会話履歴",
"created": "作成日",
"last_updated": "最終更新日",
"messages": "メッセージ",
"user": "ユーザー"
},
"files": {
"actions": "操作",
"all": "すべてのファイル",
"count": "数",
"created_at": "作成日",
"document": "ドキュメント",
"file": "ファイル",
"image": "画像",
"name": "名前",
"open": "開く",
"size": "サイズ",
"text": "テキスト",
"title": "ファイル"
},
"history": {
"continue_chat": "チャットを続ける",
"locate.message": "メッセージを探す",
"search.messages": "すべてのメッセージを検索",
"search.placeholder": "トピックまたはメッセージを検索...",
"search.topics.empty": "トピックが見つかりませんでした。Enterキーを押してすべてのメッセージを検索",
"title": "トピック検索"
},
"languages": {
"arabic": "アラビア語",
"chinese": "中国語",
"chinese-traditional": "繁体字中国語",
"english": "英語",
"french": "フランス語",
"italian": "イタリア語",
"japanese": "日本語",
"korean": "韓国語",
"portuguese": "ポルトガル語",
"russian": "ロシア語",
"spanish": "スペイン語"
},
"mermaid": {
"download": {
"png": "PNGをダウンロード",
"svg": "SVGをダウンロード"
},
"tabs": {
"preview": "プレビュー",
"source": "ソース"
},
"title": "Mermaid図"
},
"message": {
"api.connection.failed": "接続に失敗しました",
"api.connection.success": "接続に成功しました",
"assistant.added.content": "アシスタントが追加されました",
"backup.failed": "バックアップに失敗しました",
"backup.success": "バックアップに成功しました",
"backup.start.success": "バックアップを開始しました",
"chat.completion.paused": "チャットの完了が一時停止されました",
"copied": "コピーしました!",
"error.enter.api.host": "APIホストを入力してください",
"error.enter.api.key": "APIキーを入力してください",
"error.enter.model": "モデルを選択してください",
"error.invalid.proxy.url": "無効なプロキシURL",
"error.invalid.webdav": "無効なWebDAV設定",
"message.code_style": "コードスタイル",
"message.delete.content": "このメッセージを削除してもよろしいですか?",
"message.delete.title": "メッセージを削除",
"message.style": "メッセージスタイル",
"message.style.bubble": "バブル",
"message.style.plain": "プレーン",
"reset.confirm.content": "すべてのデータをリセットしてもよろしいですか?",
"reset.double.confirm.content": "すべてのデータが失われます。続行しますか?",
"reset.double.confirm.title": "データが失われます!!!",
"restore.success": "復元に成功しました",
"save.success.title": "保存に成功しました",
"switch.disabled": "アシスタントが生成中は切り替えが無効です",
"topic.added": "新しいトピックが追加されました",
"upgrade.success.button": "再起動",
"upgrade.success.content": "アップグレードを完了するためにアプリケーションを再起動してください",
"upgrade.success.title": "アップグレードに成功しました",
"regenerate.confirm": "再生成すると現在のメッセージが置き換えられます",
"copy.success": "コピーしました!"
},
"minapp": {
"title": "ミニアプリ"
},
"ollama": {
"keep_alive_time.description": "モデルがメモリに保持される時間(デフォルト:5分)",
"keep_alive_time.placeholder": "分",
"keep_alive_time.title": "保持時間",
"title": "Ollama"
},
"paintings": {
"button.delete.image": "画像を削除",
"button.delete.image.confirm": "この画像を削除してもよろしいですか?",
"button.new.image": "新しい画像",
"guidance_scale": "ガイダンススケール",
"guidance_scale_tip": "分類器なしのガイダンス。モデルが関連する画像を探す際にプロンプトにどれだけ従うかを制御します",
"image.size": "画像サイズ",
"inference_steps": "推論ステップ数",
"inference_steps_tip": "実行する推論ステップ数。ステップ数が多いほど品質が向上しますが、時間がかかります",
"negative_prompt": "ネガティブプロンプト",
"negative_prompt_tip": "画像に含めたくない内容を説明します",
"number_images": "生成数",
"number_images_tip": "生成する画像の数(1-4",
"prompt_placeholder": "作成したい画像を説明します。例:夕日の湖畔、遠くに山々",
"regenerate.confirm": "これにより、既存の生成画像が置き換えられます。続行しますか?",
"seed": "シード",
"seed_tip": "同じシードとプロンプトで似た画像を生成できます",
"title": "画像"
},
"provider": {
"aihubmix": "AiHubMix",
"anthropic": "Anthropic",
"azure-openai": "Azure OpenAI",
"baichuan": "百川",
"dashscope": "Alibaba Cloud",
"deepseek": "DeepSeek",
"doubao": "豆包",
"fireworks": "Fireworks",
"gemini": "Gemini",
"github": "GitHub Models",
"graphrag-kylin-mountain": "GraphRAG",
"grok": "Grok",
"groq": "Groq",
"hunyuan": "腾讯混元",
"hyperbolic": "Hyperbolic",
"jina": "Jina",
"minimax": "MiniMax",
"mistral": "Mistral",
"moonshot": "月の暗面",
"nvidia": "NVIDIA",
"ocoolai": "ocoolAI",
"ollama": "Ollama",
"openai": "OpenAI",
"openrouter": "OpenRouter",
"silicon": "SiliconFlow",
"stepfun": "StepFun",
"together": "Together",
"yi": "零一万物",
"zhinao": "360智脳",
"zhipu": "智譜AI"
},
"settings": {
"about": "について",
"about.checkUpdate": "更新を確認",
"about.checkUpdate.available": "今すぐ更新",
"about.checkingUpdate": "更新を確認中...",
"about.contact.button": "メール",
"about.contact.title": "連絡先",
"about.description": "クリエイターのための強力なAIアシスタント",
"about.downloading": "ダウンロード中...",
"about.feedback.button": "フィードバック",
"about.feedback.title": "フィードバック",
"about.license.button": "ライセンス",
"about.license.title": "ライセンス",
"about.releases.button": "リリース",
"about.releases.title": "リリースノート",
"about.title": "について",
"about.updateAvailable": "新しいバージョン {{version}} が見つかりました",
"about.updateError": "更新エラー",
"about.updateNotAvailable": "最新バージョンを使用しています",
"about.website.button": "ウェブサイト",
"about.website.title": "公式ウェブサイト",
"about.social.title": "ソーシャルアカウント",
"advanced.auto_switch_to_topics": "トピックに自動的に切り替える",
"advanced.title": "詳細設定",
"assistant": "デフォルトアシスタント",
"assistant.model_params": "モデルパラメータ",
"assistant.title": "デフォルトアシスタント",
"data": {
"app_data": "アプリデータ",
"app_logs": "アプリログ",
"clear_cache": {
"button": "キャッシュをクリア",
"confirm": "キャッシュをクリアすると、アプリのキャッシュデータ(ミニアプリデータを含む)が削除されます。この操作は元に戻せません。続行しますか?",
"error": "キャッシュのクリアに失敗しました",
"success": "キャッシュがクリアされました",
"title": "キャッシュをクリア"
},
"data.title": "データディレクトリ",
"title": "データ設定",
"webdav.backup.button": "WebDAVにバックアップ",
"webdav.host": "WebDAVホスト",
"webdav.host.placeholder": "http://localhost:8080",
"webdav.password": "WebDAVパスワード",
"webdav.path": "WebDAVパス",
"webdav.path.placeholder": "/backup",
"webdav.autoSync": "自動同期",
"webdav.minutes": "分",
"webdav.restore.button": "WebDAVから復元",
"webdav.title": "WebDAV",
"webdav.user": "WebDAVユーザー"
},
"display.title": "表示設定",
"font_size.title": "メッセージのフォントサイズ",
"general": "一般設定",
"general.backup.button": "バックアップ",
"general.backup.title": "データのバックアップと復元",
"general.manually_check_update.title": "更新チェックを無効にする",
"general.reset.button": "リセット",
"general.reset.title": "データをリセット",
"general.restore.button": "復元",
"general.title": "一般設定",
"general.user_name": "ユーザー名",
"general.user_name.placeholder": "ユーザー名を入力",
"general.view_webdav_settings": "WebDAV設定を表示",
"general.display.title": "表示設定",
"display.sidebar.minapp.icon": "ミニアプリのアイコンを表示",
"display.sidebar.files.icon": "ファイルのアイコンを表示",
"display.sidebar.title": "サイドバー設定",
"display.topic.title": "トピック設定",
"display.custom.css": "カスタムCSS",
"display.custom.css.placeholder": "/* ここにカスタムCSSを入力 */",
"input.auto_translate_with_space": "スペースを3回押して翻訳",
"messages.divider": "メッセージ間に区切り線を表示",
"messages.input.paste_long_text_as_file": "長いテキストをファイルとして貼り付け",
"messages.input.send_shortcuts": "送信ショートカット",
"messages.input.show_estimated_tokens": "推定トークン数を表示",
"messages.metrics": "最初のトークンまでの時間 {{time_first_token_millsec}}ms | トークン速度 {{token_speed}} tok/sec",
"messages.input.title": "入力設定",
"messages.markdown_rendering_input_message": "Markdownで入力メッセージをレンダリング",
"messages.math_engine": "数式エンジン",
"messages.model.title": "モデル設定",
"messages.title": "メッセージ設定",
"messages.use_serif_font": "セリフフォントを使用",
"messages.input.paste_long_text_threshold": "長いテキストの長さ",
"model": "デフォルトモデル",
"models.add.add_model": "モデルを追加",
"models.add.group_name": "グループ名",
"models.add.group_name.placeholder": "例:ChatGPT",
"models.add.group_name.tooltip": "例:ChatGPT",
"models.add.model_id": "モデルID",
"models.add.model_id.placeholder": "必須 例:gpt-3.5-turbo",
"models.add.model_id.tooltip": "例:gpt-3.5-turbo",
"models.add.model_name": "モデル名",
"models.add.model_name.placeholder": "例:GPT-3.5",
"models.default_assistant_model": "デフォルトアシスタントモデル",
"models.default_assistant_model_description": "新しいアシスタントを作成する際に使用されるモデル。アシスタントがモデルを設定していない場合、このモデルが使用されます",
"models.empty": "モデルが見つかりません",
"models.topic_naming_model": "トピック命名モデル",
"models.topic_naming_model_description": "新しいトピックを自動的に命名する際に使用されるモデル",
"models.translate_model": "翻訳モデル",
"models.translate_model_description": "翻訳サービスに使用されるモデル",
"models.translate_model_prompt_message": "翻訳モデルのプロンプトを入力してください",
"models.translate_model_prompt_title": "翻訳モデルのプロンプト",
"models.topic_naming_model_setting_title": "トピック命名モデルの設定",
"models.enable_topic_naming": "トピックの自動命名",
"models.topic_naming_prompt": "トピック命名プロンプト",
"provider": {
"add.name": "プロバイダー名",
"add.name.placeholder": "例:OpenAI",
"add.title": "プロバイダーを追加",
"add.type": "プロバイダータイプ",
"api.url.preview": "プレビュー: {{url}}",
"api.url.reset": "リセット",
"api.url.tip": "/で終わる場合、v1を無視します。#で終わる場合、入力されたアドレスを強制的に使用します",
"api_host": "APIホスト",
"api_key": "APIキー",
"api_key.tip": "複数のキーはカンマで区切ります",
"api_version": "APIバージョン",
"check": "チェック",
"check_all_keys": "すべてのキーをチェック",
"check_multiple_keys": "複数のAPIキーをチェック",
"delete.content": "このプロバイダーを削除してもよろしいですか?",
"delete.title": "プロバイダーを削除",
"docs_check": "チェック",
"docs_more_details": "詳細を確認",
"get_api_key": "APIキーを取得",
"no_models": "API接続をチェックする前に、モデルを追加してください",
"not_checked": "未チェック",
"remove_duplicate_keys": "重複キーを削除",
"remove_invalid_keys": "無効なキーを削除",
"search_placeholder": "モデルIDまたは名前を検索",
"title": "モデルプロバイダー"
},
"proxy": {
"mode": {
"custom": "カスタムプロキシ",
"none": "プロキシを使用しない",
"system": "システムプロキシ",
"title": "プロキシモード"
},
"title": "プロキシ設定"
},
"proxy.title": "プロキシアドレス",
"shortcuts": {
"action": "操作",
"key": "キー",
"new_topic": "新しいトピック",
"title": "ショートカット",
"zoom_in": "ズームイン",
"zoom_out": "ズームアウト",
"zoom_reset": "ズームをリセット",
"show_app": "アプリを表示",
"reset_defaults": "デフォルトのショートカットをリセット",
"reset_defaults_confirm": "すべてのショートカットをリセットしてもよろしいですか?",
"press_shortcut": "ショートカットを押す",
"alt_warning": "MacではOption + 文字をショートカットとして使用できません",
"reset_to_default": "デフォルトにリセット",
"clear_shortcut": "ショートカットをクリア",
"toggle_show_assistants": "アシスタントの表示を切り替え",
"toggle_show_topics": "トピックの表示を切り替え",
"copy_last_message": "最後のメッセージをコピー"
},
"theme.auto": "自動",
"theme.dark": "ダークテーマ",
"theme.light": "ライトテーマ",
"theme.title": "テーマ",
"theme.window.style.opaque": "不透明ウィンドウ",
"theme.window.style.title": "ウィンドウスタイル",
"theme.window.style.transparent": "透明ウィンドウ",
"title": "設定",
"topic.position": "トピックの位置",
"topic.position.left": "左",
"topic.position.right": "右",
"topic.show.time": "トピックの時間を表示",
"tray.title": "システムトレイアイコンを有効にする"
},
"translate": {
"any.language": "任意の言語",
"button.translate": "翻訳",
"confirm": {
"content": "翻訳すると元のテキストが上書きされます。続行しますか?",
"title": "翻訳確認"
},
"error.not_configured": "翻訳モデルが設定されていません",
"error.failed": "翻訳に失敗しました",
"input.placeholder": "翻訳するテキストを入力",
"output.placeholder": "翻訳",
"processing": "翻訳中...",
"title": "翻訳",
"close": "閉じる"
},
"tray": {
"quit": "終了",
"show_window": "ウィンドウを表示"
},
"words": {
"knowledgeGraph": "ナレッジグラフ",
"visualization": "可視化",
"show_window": "ウィンドウを表示",
"quit": "終了"
},
"knowledge_base": {
"title": "ナレッジベース",
"search": "ナレッジベースを検索",
"empty": "ナレッジベースが見つかりません",
"drag_file": "ファイルをここにドラッグ",
"file_hint": "{{file_types}} 形式をサポート",
"add": {
"title": "ナレッジベースを追加"
},
"notes": "ノート",
"notes_placeholder": "このナレッジベースの追加情報やコンテキストを入力...",
"delete": "削除",
"rename": "名前を変更",
"urls": "URL",
"add_url": "URLを追加",
"url_placeholder": "URLを入力",
"invalid_url": "無効なURL",
"add_file": "ファイルを追加",
"status": "状態",
"index_all": "すべてをインデックス",
"index_started": "インデックスを開始",
"cancel_index": "インデックスをキャンセル",
"index_cancelled": "インデックスがキャンセルされました",
"status_new": "追加済み",
"status_pending": "保留中",
"status_processing": "処理中",
"status_completed": "完了",
"status_failed": "失敗",
"url_added": "URLが追加されました",
"search_placeholder": "検索するテキストを入力",
"add_note": "ノートを追加",
"no_bases": "ナレッジベースがありません",
"clear_selection": "選択をクリア",
"delete_confirm": "このナレッジベースを削除してもよろしいですか?",
"sitemaps": "サイトマップ",
"add_sitemap": "サイトマップを追加",
"sitemap_placeholder": "サイトマップURLを入力",
"directories": "ディレクトリ",
"add_directory": "ディレクトリを追加",
"directory_placeholder": "ディレクトリパスを入力"
},
"models": {
"pinned": "固定済み",
"search": "モデルを検索...",
"stream_output": "ストリーム出力",
"type": {
"select": "モデルタイプを選択",
"text": "テキスト",
"vision": "画像",
"embedding": "埋め込み"
},
"all": "すべて",
"vision": "画像モデル",
"websearch": "ウェブ検索モデル",
"free": "無料モデル",
"embedding": "埋め込みモデル",
"embedding_model": "埋め込みモデル",
"embedding_model_tooltip": "設定->モデルサービス->管理で追加",
"dimensions": "{{dimensions}} 次元",
"custom_parameters": "カスタムパラメータ",
"add_parameter": "パラメータを追加",
"parameter_name": "パラメータ名",
"parameter_type": {
"string": "テキスト",
"number": "数値",
"boolean": "真偽値"
}
},
"prompts": {
"summarize": "あなたは会話を得意とするアシスタントです。ユーザーの会話を10文字以内のタイトルに要約し、ユーザーの主言語と一致していることを確認してください。句読点や特殊記号は使用しないでください。"
}
}
}
+21 -4
View File
@@ -112,7 +112,8 @@
"topics.list": "Список топиков", "topics.list": "Список топиков",
"topics.move_to": "Переместить в", "topics.move_to": "Переместить в",
"topics.title": "Топики", "topics.title": "Топики",
"translate": "Перевести" "translate": "Перевести",
"resend": "Переотправить"
}, },
"common": { "common": {
"and": "и", "and": "и",
@@ -229,6 +230,7 @@
"error.enter.api.host": "Пожалуйста, введите ваш API хост", "error.enter.api.host": "Пожалуйста, введите ваш API хост",
"error.enter.api.key": "Пожалуйста, введите ваш API ключ", "error.enter.api.key": "Пожалуйста, введите ваш API ключ",
"error.enter.model": "Пожалуйста, выберите модель", "error.enter.model": "Пожалуйста, выберите модель",
"error.enter.name": "Пожалуйста, введите название базы знаний",
"error.invalid.proxy.url": "Неверный URL прокси", "error.invalid.proxy.url": "Неверный URL прокси",
"error.invalid.webdav": "Неверные настройки WebDAV", "error.invalid.webdav": "Неверные настройки WebDAV",
"message.code_style": "Стиль кода", "message.code_style": "Стиль кода",
@@ -249,7 +251,7 @@
"upgrade.success.title": "Обновление успешно", "upgrade.success.title": "Обновление успешно",
"regenerate.confirm": "Перегенерация заменит текущее сообщение", "regenerate.confirm": "Перегенерация заменит текущее сообщение",
"copy.success": "Скопировано!", "copy.success": "Скопировано!",
"get_embedding_dimensions": "Не удалось получить размерность встраивания" "error.get_embedding_dimensions": "Не удалось получить размерность встраивания"
}, },
"minapp": { "minapp": {
"title": "Встроенные приложения" "title": "Встроенные приложения"
@@ -380,6 +382,8 @@
"display.sidebar.files.icon": "Показывать иконку файлов", "display.sidebar.files.icon": "Показывать иконку файлов",
"display.sidebar.title": "Настройки боковой панели", "display.sidebar.title": "Настройки боковой панели",
"display.topic.title": "Настройки топиков", "display.topic.title": "Настройки топиков",
"display.custom.css": "Пользовательский CSS",
"display.custom.css.placeholder": "/* Здесь введите пользовательский CSS */",
"input.auto_translate_with_space": "Быстрый перевод с помощью 3-х пробелов", "input.auto_translate_with_space": "Быстрый перевод с помощью 3-х пробелов",
"messages.divider": "Показывать разделитель между сообщениями", "messages.divider": "Показывать разделитель между сообщениями",
"messages.input.paste_long_text_as_file": "Вставлять длинный текст как файл", "messages.input.paste_long_text_as_file": "Вставлять длинный текст как файл",
@@ -414,6 +418,7 @@
"models.translate_model_prompt_title": "Модель перевода", "models.translate_model_prompt_title": "Модель перевода",
"models.topic_naming_model_setting_title": "Настройки модели именования топика", "models.topic_naming_model_setting_title": "Настройки модели именования топика",
"models.enable_topic_naming": "Автоматическое переименование топика", "models.enable_topic_naming": "Автоматическое переименование топика",
"models.topic_naming_prompt": "Подсказка для именования топика",
"provider": { "provider": {
"add.name": "Имя провайдера", "add.name": "Имя провайдера",
"add.name.placeholder": "Пример: OpenAI", "add.name.placeholder": "Пример: OpenAI",
@@ -562,7 +567,8 @@
"add_directory": "Добавить директорию", "add_directory": "Добавить директорию",
"directory_placeholder": "Введите путь к директории", "directory_placeholder": "Введите путь к директории",
"model_info": "Модель информации", "model_info": "Модель информации",
"not_support": "База знаний базы данных движок обновлен, база знаний больше не поддерживается, пожалуйста, создайте новую базу знаний" "not_support": "База знаний базы данных движок обновлен, база знаний больше не поддерживается, пожалуйста, создайте новую базу знаний",
"source": "Источник"
}, },
"models": { "models": {
"pinned": "Закреплено", "pinned": "Закреплено",
@@ -581,7 +587,18 @@
"embedding": "Встраиваемые модели", "embedding": "Встраиваемые модели",
"embedding_model": "Встраиваемые модели", "embedding_model": "Встраиваемые модели",
"embedding_model_tooltip": "Добавьте в настройки->модель сервиса->управление", "embedding_model_tooltip": "Добавьте в настройки->модель сервиса->управление",
"dimensions": "{{dimensions}} мер" "dimensions": "{{dimensions}} мер",
"custom_parameters": "Пользовательские параметры",
"add_parameter": "Добавить параметр",
"parameter_name": "Имя параметра",
"parameter_type": {
"string": "Текст",
"number": "Число",
"boolean": "Логическое"
}
},
"prompts": {
"summarize": "Вы - эксперт в общении, который суммирует разговоры пользователя в 10-символьном заголовке, совпадающем с языком пользователя, без использования знаков препинания и других специальных символов"
} }
} }
} }
+21 -4
View File
@@ -112,7 +112,8 @@
"topics.list": "话题列表", "topics.list": "话题列表",
"topics.move_to": "移动到", "topics.move_to": "移动到",
"topics.title": "话题", "topics.title": "话题",
"translate": "翻译" "translate": "翻译",
"resend": "重新发送"
}, },
"common": { "common": {
"and": "和", "and": "和",
@@ -230,6 +231,7 @@
"error.enter.api.host": "请输入您的 API 地址", "error.enter.api.host": "请输入您的 API 地址",
"error.enter.api.key": "请输入您的 API 密钥", "error.enter.api.key": "请输入您的 API 密钥",
"error.enter.model": "请选择一个模型", "error.enter.model": "请选择一个模型",
"error.enter.name": "请输入知识库名称",
"error.invalid.proxy.url": "无效的代理地址", "error.invalid.proxy.url": "无效的代理地址",
"error.invalid.webdav": "无效的 WebDAV 设置", "error.invalid.webdav": "无效的 WebDAV 设置",
"message.code_style": "代码风格", "message.code_style": "代码风格",
@@ -250,7 +252,7 @@
"upgrade.success.title": "升级成功", "upgrade.success.title": "升级成功",
"regenerate.confirm": "重新生成会覆盖当前消息", "regenerate.confirm": "重新生成会覆盖当前消息",
"copy.success": "复制成功", "copy.success": "复制成功",
"get_embedding_dimensions": "获取嵌入维度失败" "error.get_embedding_dimensions": "获取嵌入维度失败"
}, },
"minapp": { "minapp": {
"title": "小程序" "title": "小程序"
@@ -381,6 +383,8 @@
"display.sidebar.files.icon": "显示文件图标", "display.sidebar.files.icon": "显示文件图标",
"display.sidebar.title": "侧边栏设置", "display.sidebar.title": "侧边栏设置",
"display.topic.title": "话题设置", "display.topic.title": "话题设置",
"display.custom.css": "自定义 CSS",
"display.custom.css.placeholder": "/* 这里写自定义CSS */",
"input.auto_translate_with_space": "快速敲击3次空格翻译", "input.auto_translate_with_space": "快速敲击3次空格翻译",
"messages.divider": "消息分割线", "messages.divider": "消息分割线",
"messages.input.paste_long_text_as_file": "长文本粘贴为文件", "messages.input.paste_long_text_as_file": "长文本粘贴为文件",
@@ -415,6 +419,7 @@
"models.translate_model_prompt_title": "翻译模型提示词", "models.translate_model_prompt_title": "翻译模型提示词",
"models.topic_naming_model_setting_title": "话题命名模型设置", "models.topic_naming_model_setting_title": "话题命名模型设置",
"models.enable_topic_naming": "话题自动重命名", "models.enable_topic_naming": "话题自动重命名",
"models.topic_naming_prompt": "话题命名提示词",
"provider": { "provider": {
"add.name": "提供商名称", "add.name": "提供商名称",
"add.name.placeholder": "例如 OpenAI", "add.name.placeholder": "例如 OpenAI",
@@ -551,7 +556,8 @@
"add_directory": "添加目录", "add_directory": "添加目录",
"directory_placeholder": "请输入目录路径", "directory_placeholder": "请输入目录路径",
"model_info": "模型信息", "model_info": "模型信息",
"not_support": "知识库数据库引擎已更新,该知识库将不再支持,请重新创建知识库" "not_support": "知识库数据库引擎已更新,该知识库将不再支持,请重新创建知识库",
"source": "来源"
}, },
"models": { "models": {
"pinned": "已固定", "pinned": "已固定",
@@ -570,7 +576,18 @@
"embedding": "嵌入模型", "embedding": "嵌入模型",
"embedding_model": "嵌入模型", "embedding_model": "嵌入模型",
"embedding_model_tooltip": "在设置->模型服务中点击管理按钮添加", "embedding_model_tooltip": "在设置->模型服务中点击管理按钮添加",
"dimensions": "{{dimensions}} 维" "dimensions": "{{dimensions}} 维",
"custom_parameters": "自定义参数",
"add_parameter": "添加参数",
"parameter_name": "参数名称",
"parameter_type": {
"string": "文本",
"number": "数字",
"boolean": "布尔值"
}
},
"prompts": {
"summarize": "你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,标题语言与用户的首要语言一致,不要使用标点符号和其他特殊符号"
} }
} }
} }
+22 -5
View File
@@ -112,7 +112,8 @@
"topics.list": "話題列表", "topics.list": "話題列表",
"topics.move_to": "移動到", "topics.move_to": "移動到",
"topics.title": "話題", "topics.title": "話題",
"translate": "翻譯" "translate": "翻譯",
"resend": "重新發送"
}, },
"common": { "common": {
"and": "與", "and": "與",
@@ -167,7 +168,7 @@
"conversation_details": "會話詳情", "conversation_details": "會話詳情",
"conversation_history": "會話歷史", "conversation_history": "會話歷史",
"created": "創建時間", "created": "創建時間",
"last_updated": "最後新", "last_updated": "最後新",
"messages": "訊息數", "messages": "訊息數",
"user": "用戶" "user": "用戶"
}, },
@@ -229,6 +230,7 @@
"error.enter.api.host": "請先輸入您的 API 主機地址", "error.enter.api.host": "請先輸入您的 API 主機地址",
"error.enter.api.key": "請先輸入您的 API 密鑰", "error.enter.api.key": "請先輸入您的 API 密鑰",
"error.enter.model": "請先選擇一個模型", "error.enter.model": "請先選擇一個模型",
"error.enter.name": "請先輸入知識庫名稱",
"error.invalid.proxy.url": "無效的代理 URL", "error.invalid.proxy.url": "無效的代理 URL",
"error.invalid.webdav": "無效的 WebDAV 設定", "error.invalid.webdav": "無效的 WebDAV 設定",
"message.code_style": "程式碼風格", "message.code_style": "程式碼風格",
@@ -249,7 +251,7 @@
"upgrade.success.title": "升級成功", "upgrade.success.title": "升級成功",
"regenerate.confirm": "重新生成會覆蓋當前訊息", "regenerate.confirm": "重新生成會覆蓋當前訊息",
"copy.success": "複製成功", "copy.success": "複製成功",
"get_embedding_dimensions": "獲取嵌入維度失敗" "error.get_embedding_dimensions": "獲取嵌入維度失敗"
}, },
"minapp": { "minapp": {
"title": "小程序" "title": "小程序"
@@ -380,6 +382,8 @@
"display.sidebar.files.icon": "顯示文件圖示", "display.sidebar.files.icon": "顯示文件圖示",
"display.sidebar.title": "側邊欄設定", "display.sidebar.title": "側邊欄設定",
"display.topic.title": "話題設定", "display.topic.title": "話題設定",
"display.custom.css": "自定義 CSS",
"display.custom.css.placeholder": "/* 這裡寫自定義 CSS */",
"input.auto_translate_with_space": "快速敲擊3次空格翻譯", "input.auto_translate_with_space": "快速敲擊3次空格翻譯",
"messages.divider": "訊息間顯示分隔線", "messages.divider": "訊息間顯示分隔線",
"messages.input.paste_long_text_as_file": "將長文本貼上為檔案", "messages.input.paste_long_text_as_file": "將長文本貼上為檔案",
@@ -414,6 +418,7 @@
"models.translate_model_prompt_title": "翻譯模型提示詞", "models.translate_model_prompt_title": "翻譯模型提示詞",
"models.topic_naming_model_setting_title": "話題命名模型設定", "models.topic_naming_model_setting_title": "話題命名模型設定",
"models.enable_topic_naming": "話題自動重命名", "models.enable_topic_naming": "話題自動重命名",
"models.topic_naming_prompt": "話題命名提示詞",
"provider": { "provider": {
"add.name": "提供者名稱", "add.name": "提供者名稱",
"add.name.placeholder": "例如:OpenAI", "add.name.placeholder": "例如:OpenAI",
@@ -550,7 +555,8 @@
"add_directory": "添加目錄", "add_directory": "添加目錄",
"directory_placeholder": "請輸入目錄路徑", "directory_placeholder": "請輸入目錄路徑",
"model_info": "模型信息", "model_info": "模型信息",
"not_support": "知識庫數據庫引擎已更新,該知識庫將不再支持,請重新創建知識庫" "not_support": "知識庫數據庫引擎已更新,該知識庫將不再支持,請重新創建知識庫",
"source": "來源"
}, },
"models": { "models": {
"pinned": "已固定", "pinned": "已固定",
@@ -569,7 +575,18 @@
"embedding": "嵌入模型", "embedding": "嵌入模型",
"embedding_model": "嵌入模型", "embedding_model": "嵌入模型",
"embedding_model_tooltip": "在设置->模型服务中点击管理按钮添加", "embedding_model_tooltip": "在设置->模型服务中点击管理按钮添加",
"dimensions": "{{dimensions}} 維" "dimensions": "{{dimensions}} 維",
"custom_parameters": "自定義參數",
"add_parameter": "添加參數",
"parameter_name": "參數名稱",
"parameter_type": {
"string": "文字",
"number": "數字",
"boolean": "布林值"
}
},
"prompts": {
"summarize": "你是一名擅長會話的助理,你需要將用戶的會話總結為 10 個字以內的標題,標題語言與用戶的首要語言一致,不要使用標點符號和其他特殊符號"
} }
} }
} }
@@ -4,6 +4,7 @@ export type GroupTranslations = {
'zh-CN': string 'zh-CN': string
'zh-TW': string 'zh-TW': string
'ru-RU': string 'ru-RU': string
'ja-JP': string
} }
} }
@@ -12,204 +13,238 @@ export const groupTranslations: GroupTranslations = {
'en-US': 'My Agents', 'en-US': 'My Agents',
'zh-CN': '我的', 'zh-CN': '我的',
'zh-TW': '我的', 'zh-TW': '我的',
'ru-RU': 'Мои агенты' 'ru-RU': 'Мои агенты',
'ja-JP': '私のエージェント'
}, },
: { : {
'en-US': 'Career', 'en-US': 'Career',
'zh-CN': '职业', 'zh-CN': '职业',
'zh-TW': '職業', 'zh-TW': '職業',
'ru-RU': 'Карьера' 'ru-RU': 'Карьера',
'ja-JP': 'キャリア'
}, },
: { : {
'en-US': 'Business', 'en-US': 'Business',
'zh-CN': '商业', 'zh-CN': '商业',
'zh-TW': '商業', 'zh-TW': '商業',
'ru-RU': 'Бизнес' 'ru-RU': 'Бизнес',
'ja-JP': 'ビジネス'
}, },
: { : {
'en-US': 'Tools', 'en-US': 'Tools',
'zh-CN': '工具', 'zh-CN': '工具',
'zh-TW': '工具', 'zh-TW': '工具',
'ru-RU': 'Инструменты' 'ru-RU': 'Инструменты',
'ja-JP': 'ツール'
}, },
: { : {
'en-US': 'Language', 'en-US': 'Language',
'zh-CN': '语言', 'zh-CN': '语言',
'zh-TW': '語言', 'zh-TW': '語言',
'ru-RU': 'Язык' 'ru-RU': 'Язык',
'ja-JP': '言語'
}, },
: { : {
'en-US': 'Office', 'en-US': 'Office',
'zh-CN': '办公', 'zh-CN': '办公',
'zh-TW': '辦公', 'zh-TW': '辦公',
'ru-RU': 'Офис' 'ru-RU': 'Офис',
'ja-JP': 'オフィス'
}, },
: { : {
'en-US': 'General', 'en-US': 'General',
'zh-CN': '通用', 'zh-CN': '通用',
'zh-TW': '通用', 'zh-TW': '通用',
'ru-RU': 'Общее' 'ru-RU': 'Общее',
'ja-JP': '一般'
}, },
: { : {
'en-US': 'Writing', 'en-US': 'Writing',
'zh-CN': '写作', 'zh-CN': '写作',
'zh-TW': '寫作', 'zh-TW': '寫作',
'ru-RU': 'Письмо' 'ru-RU': 'Письмо',
'ja-JP': '書き込み'
}, },
: { : {
'en-US': 'Featured', 'en-US': 'Featured',
'zh-CN': '精选', 'zh-CN': '精选',
'zh-TW': '精選', 'zh-TW': '精選',
'ru-RU': 'Избранное' 'ru-RU': 'Избранное',
'ja-JP': '特集'
}, },
: { : {
'en-US': 'Programming', 'en-US': 'Programming',
'zh-CN': '编程', 'zh-CN': '编程',
'zh-TW': '編程', 'zh-TW': '編程',
'ru-RU': 'Программирование' 'ru-RU': 'Программирование',
'ja-JP': 'プログラミング'
}, },
: { : {
'en-US': 'Emotion', 'en-US': 'Emotion',
'zh-CN': '情感', 'zh-CN': '情感',
'zh-TW': '情感', 'zh-TW': '情感',
'ru-RU': 'Эмоции' 'ru-RU': 'Эмоции',
'ja-JP': '感情'
}, },
: { : {
'en-US': 'Education', 'en-US': 'Education',
'zh-CN': '教育', 'zh-CN': '教育',
'zh-TW': '教育', 'zh-TW': '教育',
'ru-RU': 'Образование' 'ru-RU': 'Образование',
'ja-JP': '教育'
}, },
: { : {
'en-US': 'Creative', 'en-US': 'Creative',
'zh-CN': '创意', 'zh-CN': '创意',
'zh-TW': '創意', 'zh-TW': '創意',
'ru-RU': 'Креатив' 'ru-RU': 'Креатив',
'ja-JP': 'クリエイティブ'
}, },
: { : {
'en-US': 'Academic', 'en-US': 'Academic',
'zh-CN': '学术', 'zh-CN': '学术',
'zh-TW': '學術', 'zh-TW': '學術',
'ru-RU': 'Академический' 'ru-RU': 'Академический',
'ja-JP': 'アカデミック'
}, },
: { : {
'en-US': 'Design', 'en-US': 'Design',
'zh-CN': '设计', 'zh-CN': '设计',
'zh-TW': '設計', 'zh-TW': '設計',
'ru-RU': 'Дизайн' 'ru-RU': 'Дизайн',
'ja-JP': 'デザイン'
}, },
: { : {
'en-US': 'Art', 'en-US': 'Art',
'zh-CN': '艺术', 'zh-CN': '艺术',
'zh-TW': '藝術', 'zh-TW': '藝術',
'ru-RU': 'Искусство' 'ru-RU': 'Искусство',
'ja-JP': 'アート'
}, },
: { : {
'en-US': 'Entertainment', 'en-US': 'Entertainment',
'zh-CN': '娱乐', 'zh-CN': '娱乐',
'zh-TW': '娛樂', 'zh-TW': '娛樂',
'ru-RU': 'Развлечения' 'ru-RU': 'Развлечения',
'ja-JP': 'エンターテイメント'
}, },
: { : {
'en-US': 'Life', 'en-US': 'Life',
'zh-CN': '生活', 'zh-CN': '生活',
'zh-TW': '生活', 'zh-TW': '生活',
'ru-RU': 'Жизнь' 'ru-RU': 'Жизнь',
'ja-JP': '生活'
}, },
: { : {
'en-US': 'Medical', 'en-US': 'Medical',
'zh-CN': '医疗', 'zh-CN': '医疗',
'zh-TW': '醫療', 'zh-TW': '醫療',
'ru-RU': 'Медицина' 'ru-RU': 'Медицина',
'ja-JP': '医療'
}, },
: { : {
'en-US': 'Games', 'en-US': 'Games',
'zh-CN': '游戏', 'zh-CN': '游戏',
'zh-TW': '遊戲', 'zh-TW': '遊戲',
'ru-RU': 'Игры' 'ru-RU': 'Игры',
'ja-JP': 'ゲーム'
}, },
: { : {
'en-US': 'Translation', 'en-US': 'Translation',
'zh-CN': '翻译', 'zh-CN': '翻译',
'zh-TW': '翻譯', 'zh-TW': '翻譯',
'ru-RU': 'Перевод' 'ru-RU': 'Перевод',
'ja-JP': '翻訳'
}, },
: { : {
'en-US': 'Music', 'en-US': 'Music',
'zh-CN': '音乐', 'zh-CN': '音乐',
'zh-TW': '音樂', 'zh-TW': '音樂',
'ru-RU': 'Музыка' 'ru-RU': 'Музыка',
'ja-JP': '音楽'
}, },
: { : {
'en-US': 'Review', 'en-US': 'Review',
'zh-CN': '点评', 'zh-CN': '点评',
'zh-TW': '點評', 'zh-TW': '點評',
'ru-RU': 'Обзор' 'ru-RU': 'Обзор',
'ja-JP': 'レビュー'
}, },
: { : {
'en-US': 'Copywriting', 'en-US': 'Copywriting',
'zh-CN': '文案', 'zh-CN': '文案',
'zh-TW': '文案', 'zh-TW': '文案',
'ru-RU': 'Копирайтинг' 'ru-RU': 'Копирайтинг',
'ja-JP': 'コピーライティング'
}, },
: { : {
'en-US': 'Encyclopedia', 'en-US': 'Encyclopedia',
'zh-CN': '百科', 'zh-CN': '百科',
'zh-TW': '百科', 'zh-TW': '百科',
'ru-RU': 'Энциклопедия' 'ru-RU': 'Энциклопедия',
'ja-JP': '百科事典'
}, },
: { : {
'en-US': 'Health', 'en-US': 'Health',
'zh-CN': '健康', 'zh-CN': '健康',
'zh-TW': '健康', 'zh-TW': '健康',
'ru-RU': 'Здоровье' 'ru-RU': 'Здоровье',
'ja-JP': '健康'
}, },
: { : {
'en-US': 'Marketing', 'en-US': 'Marketing',
'zh-CN': '营销', 'zh-CN': '营销',
'zh-TW': '營銷', 'zh-TW': '營銷',
'ru-RU': 'Маркетинг' 'ru-RU': 'Маркетинг',
'ja-JP': 'マーケティング'
}, },
: { : {
'en-US': 'Science', 'en-US': 'Science',
'zh-CN': '科学', 'zh-CN': '科学',
'zh-TW': '科學', 'zh-TW': '科學',
'ru-RU': 'Наука' 'ru-RU': 'Наука',
'ja-JP': '科学'
}, },
: { : {
'en-US': 'Analysis', 'en-US': 'Analysis',
'zh-CN': '分析', 'zh-CN': '分析',
'zh-TW': '分析', 'zh-TW': '分析',
'ru-RU': 'Анализ' 'ru-RU': 'Анализ',
'ja-JP': '分析'
}, },
: { : {
'en-US': 'Legal', 'en-US': 'Legal',
'zh-CN': '法律', 'zh-CN': '法律',
'zh-TW': '法律', 'zh-TW': '法律',
'ru-RU': 'Право' 'ru-RU': 'Право',
'ja-JP': '法律'
}, },
: { : {
'en-US': 'Consulting', 'en-US': 'Consulting',
'zh-CN': '咨询', 'zh-CN': '咨询',
'zh-TW': '諮詢', 'zh-TW': '諮詢',
'ru-RU': 'Консалтинг' 'ru-RU': 'Консалтинг',
'ja-JP': 'コンサルティング'
}, },
: { : {
'en-US': 'Finance', 'en-US': 'Finance',
'zh-CN': '金融', 'zh-CN': '金融',
'zh-TW': '金融', 'zh-TW': '金融',
'ru-RU': 'Финансы' 'ru-RU': 'Финансы',
'ja-JP': '金融'
}, },
: { : {
'en-US': 'Travel', 'en-US': 'Travel',
'zh-CN': '旅游', 'zh-CN': '旅游',
'zh-TW': '旅遊', 'zh-TW': '旅遊',
'ru-RU': 'Путешествия' 'ru-RU': 'Путешествия',
'ja-JP': '旅行'
}, },
: { : {
'en-US': 'Management', 'en-US': 'Management',
'zh-CN': '管理', 'zh-CN': '管理',
'zh-TW': '管理', 'zh-TW': '管理',
'ru-RU': 'Управление' 'ru-RU': 'Управление',
'ja-JP': '管理'
} }
} }
@@ -1,6 +1,6 @@
import { DeleteOutlined, EditOutlined, PlusOutlined, SortAscendingOutlined } from '@ant-design/icons' import { DeleteOutlined, EditOutlined, PlusOutlined, SortAscendingOutlined } from '@ant-design/icons'
import AssistantSettingsPopup from '@renderer/components/AssistantSettings'
import { useAgents } from '@renderer/hooks/useAgents' import { useAgents } from '@renderer/hooks/useAgents'
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
import { createAssistantFromAgent } from '@renderer/services/AssistantService' import { createAssistantFromAgent } from '@renderer/services/AssistantService'
import { Agent } from '@renderer/types' import { Agent } from '@renderer/types'
import { Col } from 'antd' import { Col } from 'antd'
-2
View File
@@ -36,8 +36,6 @@ const Container = styled.div`
justify-content: center; justify-content: center;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
max-width: 80px;
width: 72px;
overflow: hidden; overflow: hidden;
` `
+6 -6
View File
@@ -74,12 +74,12 @@ const ContentContainer = styled.div`
const AppsContainer = styled.div` const AppsContainer = styled.div`
display: flex; display: flex;
min-width: 950px; min-width: 930px;
max-width: 950px; max-width: 930px;
flex-direction: row; max-height: 500px;
flex-wrap: wrap; display: grid;
align-content: flex-start; grid-template-columns: repeat(8, minmax(90px, 1fr));
gap: 50px; gap: 25px 25px;
` `
export default AppsPage export default AppsPage
@@ -21,6 +21,7 @@ let _message: Message | undefined
const TopicsPage: FC = () => { const TopicsPage: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const [search, setSearch] = useState(_search) const [search, setSearch] = useState(_search)
const [searchKeywords, setSearchKeywords] = useState(_search)
const [stack, setStack] = useState<Route[]>(_stack) const [stack, setStack] = useState<Route[]>(_stack)
const [topic, setTopic] = useState<Topic | undefined>(_topic) const [topic, setTopic] = useState<Topic | undefined>(_topic)
const [message, setMessage] = useState<Message | undefined>(_message) const [message, setMessage] = useState<Message | undefined>(_message)
@@ -40,6 +41,7 @@ const TopicsPage: FC = () => {
} }
const onSearch = () => { const onSearch = () => {
setSearchKeywords(search)
setStack(['topics', 'search']) setStack(['topics', 'search'])
setTopic(undefined) setTopic(undefined)
} }
@@ -84,7 +86,7 @@ const TopicsPage: FC = () => {
/> />
<TopicMessages topic={topic} style={{ display: isShow('topic') }} /> <TopicMessages topic={topic} style={{ display: isShow('topic') }} />
<SearchResults <SearchResults
keywords={isShow('search') ? search : ''} keywords={isShow('search') ? searchKeywords : ''}
onMessageClick={onMessageClick} onMessageClick={onMessageClick}
onTopicClick={onTopicClick} onTopicClick={onTopicClick}
style={{ display: isShow('search') }} style={{ display: isShow('search') }}
@@ -1,5 +1,6 @@
import { ArrowRightOutlined } from '@ant-design/icons' import { ArrowRightOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import { useSettings } from '@renderer/hooks/useSettings'
import { default as MessageItem } from '@renderer/pages/home/Messages/Message' import { default as MessageItem } from '@renderer/pages/home/Messages/Message'
import { locateToMessage } from '@renderer/services/MessagesService' import { locateToMessage } from '@renderer/services/MessagesService'
import NavigationService from '@renderer/services/NavigationService' import NavigationService from '@renderer/services/NavigationService'
@@ -15,6 +16,7 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
const SearchMessage: FC<Props> = ({ message, ...props }) => { const SearchMessage: FC<Props> = ({ message, ...props }) => {
const navigate = NavigationService.navigate! const navigate = NavigationService.navigate!
const { messageStyle } = useSettings()
const { t } = useTranslation() const { t } = useTranslation()
if (!message) { if (!message) {
@@ -22,7 +24,7 @@ const SearchMessage: FC<Props> = ({ message, ...props }) => {
} }
return ( return (
<MessagesContainer {...props}> <MessagesContainer {...props} className={messageStyle}>
<ContainerWrapper style={{ paddingTop: 20, paddingBottom: 20, position: 'relative' }}> <ContainerWrapper style={{ paddingTop: 20, paddingBottom: 20, position: 'relative' }}>
<MessageItem message={message} /> <MessageItem message={message} />
<Button <Button
@@ -45,6 +47,7 @@ const SearchMessage: FC<Props> = ({ message, ...props }) => {
const MessagesContainer = styled.div` const MessagesContainer = styled.div`
width: 100%; width: 100%;
display: flex; display: flex;
flex: 1;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
overflow-y: scroll; overflow-y: scroll;
@@ -90,6 +90,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
[estimateTextTokens, showInputEstimatedTokens, text] [estimateTextTokens, showInputEstimatedTokens, text]
) )
const newTopicShortcut = useShortcutDisplay('new_topic') const newTopicShortcut = useShortcutDisplay('new_topic')
const inputEmpty = isEmpty(text.trim()) && files.length === 0
_text = text _text = text
_files = files _files = files
@@ -100,7 +101,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
return return
} }
if (isEmpty(text.trim())) { if (inputEmpty) {
return return
} }
@@ -131,7 +132,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
setTimeout(() => resizeTextArea(), 0) setTimeout(() => resizeTextArea(), 0)
setExpend(false) setExpend(false)
}, [assistant.id, assistant.topics, generating, files, text, selectedKnowledgeBase]) }, [generating, inputEmpty, text, assistant.id, assistant.topics, selectedKnowledgeBase, files])
const translate = async () => { const translate = async () => {
if (isTranslating) { if (isTranslating) {
@@ -492,7 +493,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
</ToolbarButton> </ToolbarButton>
</Tooltip> </Tooltip>
)} )}
{!generating && <SendMessageButton sendMessage={sendMessage} disabled={generating || !text} />} {!generating && <SendMessageButton sendMessage={sendMessage} disabled={generating || inputEmpty} />}
</ToolbarMenu> </ToolbarMenu>
</Toolbar> </Toolbar>
</InputBarContainer> </InputBarContainer>
@@ -19,7 +19,7 @@ import ImagePreview from './ImagePreview'
import Link from './Link' import Link from './Link'
const ALLOWED_ELEMENTS = const ALLOWED_ELEMENTS =
/<(style|p|div|span|b|i|strong|em|ul|ol|li|table|tr|td|th|thead|tbody|h[1-6]|blockquote|pre|code|br|hr|svg|path|circle|rect|line|polyline|polygon|text|g|defs|title|desc|tspan)/i /<(style|p|div|span|b|i|strong|em|ul|ol|li|table|tr|td|th|thead|tbody|h[1-6]|blockquote|pre|code|br|hr|svg|path|circle|rect|line|polyline|polygon|text|g|defs|title|desc|tspan|sub|sup)/i
interface Props { interface Props {
message: Message message: Message
@@ -86,9 +86,12 @@ const MessageItem: FC<Props> = ({
} }
useEffect(() => { useEffect(() => {
const unsubscribes = [EventEmitter.on(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id, messageHighlightHandler)] const unsubscribes = [
EventEmitter.on(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id, messageHighlightHandler),
EventEmitter.on(EVENT_NAMES.RESEND_MESSAGE + ':' + message.id, onEditMessage)
]
return () => unsubscribes.forEach((unsub) => unsub()) return () => unsubscribes.forEach((unsub) => unsub())
}, [message]) }, [message, onEditMessage])
useEffect(() => { useEffect(() => {
if (message.role === 'user' && !message.usage) { if (message.role === 'user' && !message.usage) {
@@ -178,6 +181,7 @@ const MessageItem: FC<Props> = ({
setModel={setModel} setModel={setModel}
onEditMessage={onEditMessage} onEditMessage={onEditMessage}
onDeleteMessage={onDeleteMessage} onDeleteMessage={onDeleteMessage}
onGetMessages={onGetMessages}
/> />
</MessageFooter> </MessageFooter>
)} )}
@@ -15,7 +15,7 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { translateText } from '@renderer/services/TranslateService' import { translateText } from '@renderer/services/TranslateService'
import { Message, Model } from '@renderer/types' import { Message, Model } from '@renderer/types'
import { removeTrailingDoubleSpaces } from '@renderer/utils' import { removeTrailingDoubleSpaces } from '@renderer/utils'
import { Dropdown, Popconfirm, Tooltip } from 'antd' import { Button, Dropdown, Popconfirm, Tooltip } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { FC, useCallback, useMemo, useState } from 'react' import { FC, useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@@ -31,6 +31,7 @@ interface Props {
setModel: (model: Model) => void setModel: (model: Model) => void
onEditMessage?: (message: Message) => void onEditMessage?: (message: Message) => void
onDeleteMessage?: (message: Message) => void onDeleteMessage?: (message: Message) => void
onGetMessages?: () => Message[]
} }
const MessageMenubar: FC<Props> = (props) => { const MessageMenubar: FC<Props> = (props) => {
@@ -43,7 +44,8 @@ const MessageMenubar: FC<Props> = (props) => {
assistantModel, assistantModel,
setModel, setModel,
onEditMessage, onEditMessage,
onDeleteMessage onDeleteMessage,
onGetMessages
} = props } = props
const { t } = useTranslation() const { t } = useTranslation()
const [copied, setCopied] = useState(false) const [copied, setCopied] = useState(false)
@@ -75,10 +77,43 @@ const MessageMenubar: FC<Props> = (props) => {
}) })
}, [index, t]) }, [index, t])
const onResend = useCallback(() => {
const _messages = onGetMessages?.() || []
const index = _messages.findIndex((m) => m.id === message.id)
const nextIndex = index + 1
const nextMessage = _messages[nextIndex]
if (nextMessage && nextMessage.role === 'assistant') {
EventEmitter.emit(EVENT_NAMES.RESEND_MESSAGE + ':' + nextMessage.id, {
...nextMessage,
content: '',
status: 'sending',
modelId: assistantModel?.id || model?.id,
translatedContent: undefined
})
}
}, [assistantModel?.id, message.id, model?.id, onGetMessages])
const onEdit = useCallback(async () => { const onEdit = useCallback(async () => {
const editedText = await TextEditPopup.show({ text: message.content }) let resendMessage = false
const editedText = await TextEditPopup.show({
text: message.content,
children: (props) => (
<ReSendButton
icon={<i className="iconfont icon-ic_send" style={{ color: 'var(--color-primary)' }} />}
onClick={() => {
props.onOk?.()
resendMessage = true
}}>
{t('chat.resend')}
</ReSendButton>
)
})
editedText && onEditMessage?.({ ...message, content: editedText }) editedText && onEditMessage?.({ ...message, content: editedText })
}, [message, onEditMessage]) resendMessage && onResend()
}, [message, onEditMessage, onResend, t])
const handleTranslate = useCallback( const handleTranslate = useCallback(
async (language: string) => { async (language: string) => {
@@ -287,4 +322,10 @@ const ActionButton = styled.div`
} }
` `
const ReSendButton = styled(Button)`
position: absolute;
top: 10px;
left: 0;
`
export default MessageMenubar export default MessageMenubar
@@ -1,4 +1,4 @@
import AssistantSettingsPopup from '@renderer/components/AssistantSettings' import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
import { Assistant } from '@renderer/types' import { Assistant } from '@renderer/types'
import { FC } from 'react' import { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
+1 -1
View File
@@ -1,6 +1,5 @@
import { FormOutlined, SearchOutlined } from '@ant-design/icons' import { FormOutlined, SearchOutlined } from '@ant-design/icons'
import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar' import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
import AssistantSettingsPopup from '@renderer/components/AssistantSettings'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import AppStorePopover from '@renderer/components/Popups/AppStorePopover' import AppStorePopover from '@renderer/components/Popups/AppStorePopover'
import SearchPopup from '@renderer/components/Popups/SearchPopup' import SearchPopup from '@renderer/components/Popups/SearchPopup'
@@ -9,6 +8,7 @@ import { useAssistant } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { useShortcut } from '@renderer/hooks/useShortcuts' import { useShortcut } from '@renderer/hooks/useShortcuts'
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore' import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { Assistant, Topic } from '@renderer/types' import { Assistant, Topic } from '@renderer/types'
import { FC } from 'react' import { FC } from 'react'
@@ -1,11 +1,11 @@
import { DeleteOutlined, EditOutlined, MinusCircleOutlined, PlusOutlined, SaveOutlined } from '@ant-design/icons' import { DeleteOutlined, EditOutlined, MinusCircleOutlined, PlusOutlined, SaveOutlined } from '@ant-design/icons'
import AssistantSettingsPopup from '@renderer/components/AssistantSettings'
import DragableList from '@renderer/components/DragableList' import DragableList from '@renderer/components/DragableList'
import CopyIcon from '@renderer/components/Icons/CopyIcon' import CopyIcon from '@renderer/components/Icons/CopyIcon'
import Scrollbar from '@renderer/components/Scrollbar' import Scrollbar from '@renderer/components/Scrollbar'
import { useAgents } from '@renderer/hooks/useAgents' import { useAgents } from '@renderer/hooks/useAgents'
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant' import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
import { getDefaultTopic } from '@renderer/services/AssistantService' import { getDefaultTopic } from '@renderer/services/AssistantService'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { useAppSelector } from '@renderer/store' import { useAppSelector } from '@renderer/store'
@@ -1,4 +1,4 @@
import { CheckOutlined, QuestionCircleOutlined, ReloadOutlined } from '@ant-design/icons' import { CheckOutlined, DeleteOutlined, PlusOutlined, QuestionCircleOutlined, ReloadOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import Scrollbar from '@renderer/components/Scrollbar' import Scrollbar from '@renderer/components/Scrollbar'
import { import {
@@ -29,7 +29,7 @@ import {
setShowMessageDivider setShowMessageDivider
} from '@renderer/store/settings' } from '@renderer/store/settings'
import { Assistant, AssistantSettings, ThemeMode } from '@renderer/types' import { Assistant, AssistantSettings, ThemeMode } from '@renderer/types'
import { Col, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd' import { Button, Col, Input, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
import { FC, useEffect, useState } from 'react' import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@@ -102,7 +102,8 @@ const SettingsTab: FC<Props> = (props) => {
maxTokens: DEFAULT_MAX_TOKENS, maxTokens: DEFAULT_MAX_TOKENS,
streamOutput: true, streamOutput: true,
hideMessages: false, hideMessages: false,
autoResetModel: false autoResetModel: false,
customParameters: []
} }
}) })
} }
@@ -202,6 +203,106 @@ const SettingsTab: FC<Props> = (props) => {
/> />
</Col> </Col>
</Row> </Row>
{assistant?.settings?.customParameters?.map((param, index) => (
<ParameterCard key={index}>
<Row align="middle" gutter={8} style={{ marginBottom: 8 }}>
<Col span={14}>
<Input
placeholder={t('models.parameter_name')}
value={param.name}
onChange={(e) => {
const newParams = [...(assistant?.settings?.customParameters || [])]
newParams[index] = { ...param, name: e.target.value }
onUpdateAssistantSettings({ customParameters: newParams })
}}
/>
</Col>
<Col span={10}>
<Select
value={param.type}
onChange={(value: 'string' | 'number' | 'boolean') => {
const newParams = [...(assistant?.settings?.customParameters || [])]
let defaultValue: any = ''
switch (value) {
case 'number':
defaultValue = 0
break
case 'boolean':
defaultValue = false
break
default:
defaultValue = ''
}
newParams[index] = { ...param, type: value, value: defaultValue }
onUpdateAssistantSettings({ customParameters: newParams })
}}
style={{ width: '100%' }}>
<Select.Option value="string">{t('models.parameter_type.string')}</Select.Option>
<Select.Option value="number">{t('models.parameter_type.number')}</Select.Option>
<Select.Option value="boolean">{t('models.parameter_type.boolean')}</Select.Option>
</Select>
</Col>
</Row>
<Row align="middle" gutter={10}>
<Col span={20}>
{param.type === 'boolean' ? (
<Switch
checked={param.value as boolean}
onChange={(checked) => {
const newParams = [...(assistant?.settings?.customParameters || [])]
newParams[index] = { ...param, value: checked }
onUpdateAssistantSettings({ customParameters: newParams })
}}
/>
) : param.type === 'number' ? (
<InputNumber
style={{ width: '100%' }}
value={param.value as number}
onChange={(value) => {
const newParams = [...(assistant?.settings?.customParameters || [])]
newParams[index] = { ...param, value: value || 0 }
onUpdateAssistantSettings({ customParameters: newParams })
}}
step={0.01}
/>
) : (
<Input
value={typeof param.value === 'string' ? param.value : JSON.stringify(param.value)}
onChange={(e) => {
const newParams = [...(assistant?.settings?.customParameters || [])]
newParams[index] = { ...param, value: e.target.value }
onUpdateAssistantSettings({ customParameters: newParams })
}}
/>
)}
</Col>
<Col span={4}>
<Button
icon={<DeleteOutlined />}
onClick={() => {
const newParams = [...(assistant?.settings?.customParameters || [])]
newParams.splice(index, 1)
onUpdateAssistantSettings({ customParameters: newParams })
}}
danger
style={{ width: '100%' }}
/>
</Col>
</Row>
</ParameterCard>
))}
<Button
icon={<PlusOutlined />}
onClick={() => {
const newParams = [
...(assistant?.settings?.customParameters || []),
{ name: '', value: '', type: 'string' as const }
]
onUpdateAssistantSettings({ customParameters: newParams })
}}
style={{ marginBottom: 0, width: '100%', borderStyle: 'dashed' }}>
{t('models.add_parameter')}
</Button>
</SettingGroup> </SettingGroup>
<SettingGroup> <SettingGroup>
<SettingSubtitle style={{ marginTop: 0 }}>{t('settings.messages.title')}</SettingSubtitle> <SettingSubtitle style={{ marginTop: 0 }}>{t('settings.messages.title')}</SettingSubtitle>
@@ -418,4 +519,15 @@ export const SettingGroup = styled.div<{ theme?: ThemeMode }>`
background: var(--color-group-background); background: var(--color-group-background);
` `
const ParameterCard = styled.div`
margin-bottom: 8px;
padding: 8px;
border: 1px solid var(--color-border);
border-radius: 6px;
background: var(--color-background);
&:last-child {
margin-bottom: 12px;
}
`
export default SettingsTab export default SettingsTab
@@ -4,6 +4,7 @@ import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
import { isLocalAi } from '@renderer/config/env' import { isLocalAi } from '@renderer/config/env'
import { isVisionModel } from '@renderer/config/models' import { isVisionModel } from '@renderer/config/models'
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
import { getProviderByModel } from '@renderer/services/AssistantService'
import { Assistant } from '@renderer/types' import { Assistant } from '@renderer/types'
import { Button } from 'antd' import { Button } from 'antd'
import { FC } from 'react' import { FC } from 'react'
@@ -32,9 +33,14 @@ const SelectModelButton: FC<Props> = ({ assistant }) => {
return ( return (
<DropdownButton size="small" type="default" onClick={onSelectModel}> <DropdownButton size="small" type="default" onClick={onSelectModel}>
<ModelAvatar model={model} size={20} /> <ButtonContent>
<ModelName>{model ? model.name : t('button.select_model')}</ModelName> <ModelAvatar model={model} size={20} />
{isVisionModel(model) && <VisionIcon style={{ marginLeft: 0 }} />} <ModelName>
{model ? model.name : t('button.select_model')} |{' '}
{t(`provider.${model?.provider}`, { defaultValue: getProviderByModel(model)?.name })}
</ModelName>
{isVisionModel(model) && <VisionIcon style={{ marginLeft: 0 }} />}
</ButtonContent>
</DropdownButton> </DropdownButton>
) )
} }
@@ -49,6 +55,12 @@ const DropdownButton = styled(Button)`
border: 1px solid transparent; border: 1px solid transparent;
` `
const ButtonContent = styled.div`
display: flex;
align-items: center;
gap: 5px;
`
const ModelName = styled.span` const ModelName = styled.span`
margin-left: -2px; margin-left: -2px;
font-weight: 500; font-weight: 500;
@@ -177,7 +177,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
return ( return (
<MainContent> <MainContent>
{!base.dimensions && ( {!base?.version && (
<Alert message={t('knowledge_base.not_support')} type="error" style={{ marginBottom: 20 }} showIcon /> <Alert message={t('knowledge_base.not_support')} type="error" style={{ marginBottom: 20 }} showIcon />
)} )}
<FileSection> <FileSection>
@@ -82,7 +82,8 @@ const PopupContainer: React.FC<Props> = ({ title, resolve }) => {
dimensions, dimensions,
items: [], items: [],
created_at: Date.now(), created_at: Date.now(),
updated_at: Date.now() updated_at: Date.now(),
version: 1
} }
await window.api.knowledgeBase.create(getKnowledgeBaseParams(newBase)) await window.api.knowledgeBase.create(getKnowledgeBaseParams(newBase))
@@ -1,7 +1,7 @@
import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces' import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces'
import { TopView } from '@renderer/components/TopView' import { TopView } from '@renderer/components/TopView'
import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService' import { getFileFromUrl, getKnowledgeBaseParams } from '@renderer/services/KnowledgeService'
import { KnowledgeBase } from '@renderer/types' import { FileType, KnowledgeBase } from '@renderer/types'
import { Input, List, Modal, Spin, Typography } from 'antd' import { Input, List, Modal, Spin, Typography } from 'antd'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@@ -21,7 +21,7 @@ interface Props extends ShowParams {
const PopupContainer: React.FC<Props> = ({ base, resolve }) => { const PopupContainer: React.FC<Props> = ({ base, resolve }) => {
const [open, setOpen] = useState(true) const [open, setOpen] = useState(true)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [results, setResults] = useState<ExtractChunkData[]>([]) const [results, setResults] = useState<Array<ExtractChunkData & { file: FileType | null }>>([])
const [searchKeyword, setSearchKeyword] = useState('') const [searchKeyword, setSearchKeyword] = useState('')
const { t } = useTranslation() const { t } = useTranslation()
@@ -39,7 +39,13 @@ const PopupContainer: React.FC<Props> = ({ base, resolve }) => {
search: value, search: value,
base: getKnowledgeBaseParams(base) base: getKnowledgeBaseParams(base)
}) })
setResults(searchResults) const results = await Promise.all(
searchResults.map(async (item) => {
const file = await getFileFromUrl(item.metadata.source)
return { ...item, file }
})
)
setResults(results)
} catch (error) { } catch (error) {
console.error('Search failed:', error) console.error('Search failed:', error)
} finally { } finally {
@@ -102,7 +108,16 @@ const PopupContainer: React.FC<Props> = ({ base, resolve }) => {
<ScoreTag>Score: {(item.score * 100).toFixed(1)}%</ScoreTag> <ScoreTag>Score: {(item.score * 100).toFixed(1)}%</ScoreTag>
<Paragraph>{highlightText(item.pageContent)}</Paragraph> <Paragraph>{highlightText(item.pageContent)}</Paragraph>
<MetadataContainer> <MetadataContainer>
<Text type="secondary">Source: {item.metadata.source}</Text> <Text type="secondary">
{t('knowledge_base.source')}:{' '}
{item.file ? (
<a href={`http://file/${item.file.name}`} target="_blank" rel="noreferrer">
{item.file.origin_name}
</a>
) : (
item.metadata.source
)}
</Text>
</MetadataContainer> </MetadataContainer>
</ResultItem> </ResultItem>
</List.Item> </List.Item>
@@ -10,6 +10,7 @@ import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { setUpdateState } from '@renderer/store/runtime' import { setUpdateState } from '@renderer/store/runtime'
import { setManualUpdateCheck } from '@renderer/store/settings' import { setManualUpdateCheck } from '@renderer/store/settings'
import { ThemeMode } from '@renderer/types'
import { compareVersions, runAsyncFunction } from '@renderer/utils' import { compareVersions, runAsyncFunction } from '@renderer/utils'
import { Avatar, Button, Progress, Row, Switch, Tag } from 'antd' import { Avatar, Button, Progress, Row, Switch, Tag } from 'antd'
import { debounce } from 'lodash' import { debounce } from 'lodash'
@@ -71,6 +72,15 @@ const AboutSettings: FC = () => {
}) })
} }
const showReleases = async () => {
const { appPath } = await window.api.getAppInfo()
MinApp.start({
name: t('settings.about.releases.title'),
url: `file://${appPath}/resources/cherry-studio/releases.html?theme=${theme === ThemeMode.dark ? 'dark' : 'light'}`,
logo: AppLogo
})
}
const hasNewVersion = update?.info?.version && version ? compareVersions(update.info.version, version) > 0 : false const hasNewVersion = update?.info?.version && version ? compareVersions(update.info.version, version) > 0 : false
useEffect(() => { useEffect(() => {
@@ -158,16 +168,7 @@ const AboutSettings: FC = () => {
<SoundOutlined /> <SoundOutlined />
{t('settings.about.releases.title')} {t('settings.about.releases.title')}
</SettingRowTitle> </SettingRowTitle>
<Button <Button onClick={showReleases}>{t('settings.about.releases.button')}</Button>
onClick={() =>
MinApp.start({
name: t('settings.about.releases.title'),
url: 'https://github.com/kangfenmao/cherry-studio/releases',
logo: AppLogo
})
}>
{t('settings.about.releases.button')}
</Button>
</SettingRow> </SettingRow>
<SettingDivider /> <SettingDivider />
<SettingRow> <SettingRow>
@@ -1,16 +1,15 @@
import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons' import { DeleteOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant' import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
import { SettingRow } from '@renderer/pages/settings' import { SettingRow } from '@renderer/pages/settings'
import { Assistant, AssistantSettings } from '@renderer/types' import { Assistant, AssistantSettings } from '@renderer/types'
import { Button, Col, Divider, InputNumber, Row, Slider, Switch, Tooltip } from 'antd' import { Button, Col, Divider, Input, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
import { FC, useState } from 'react' import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import ModelAvatar from '../Avatar/ModelAvatar'
import SelectModelPopup from '../Popups/SelectModelPopup'
interface Props { interface Props {
assistant: Assistant assistant: Assistant
updateAssistant: (assistant: Assistant) => void updateAssistant: (assistant: Assistant) => void
@@ -26,6 +25,13 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true) const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true)
const [defaultModel, setDefaultModel] = useState(assistant?.defaultModel) const [defaultModel, setDefaultModel] = useState(assistant?.defaultModel)
const [topP, setTopP] = useState(assistant?.settings?.topP ?? 1) const [topP, setTopP] = useState(assistant?.settings?.topP ?? 1)
const [customParameters, setCustomParameters] = useState<
Array<{
name: string
value: string | number | boolean
type: 'string' | 'number' | 'boolean'
}>
>(assistant?.settings?.customParameters ?? [])
const { t } = useTranslation() const { t } = useTranslation()
const onTemperatureChange = (value) => { const onTemperatureChange = (value) => {
@@ -52,6 +58,77 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
} }
} }
const onAddCustomParameter = () => {
const newParam = { name: '', value: '', type: 'string' as const }
const newParams = [...customParameters, newParam]
setCustomParameters(newParams)
updateAssistantSettings({ customParameters: newParams })
}
const onUpdateCustomParameter = (
index: number,
field: 'name' | 'value' | 'type',
value: string | number | boolean
) => {
const newParams = [...customParameters]
if (field === 'type') {
let defaultValue: any = ''
switch (value) {
case 'number':
defaultValue = 0
break
case 'boolean':
defaultValue = false
break
default:
defaultValue = ''
}
newParams[index] = {
...newParams[index],
type: value as any,
value: defaultValue
}
} else {
newParams[index] = { ...newParams[index], [field]: value }
}
setCustomParameters(newParams)
updateAssistantSettings({ customParameters: newParams })
}
const renderParameterValueInput = (param: (typeof customParameters)[0], index: number) => {
switch (param.type) {
case 'number':
return (
<InputNumber
style={{ width: '100%' }}
value={param.value as number}
onChange={(value) => onUpdateCustomParameter(index, 'value', value || 0)}
step={0.01}
/>
)
case 'boolean':
return (
<Switch
checked={param.value as boolean}
onChange={(checked) => onUpdateCustomParameter(index, 'value', checked)}
/>
)
default:
return (
<Input
value={param.value as string}
onChange={(e) => onUpdateCustomParameter(index, 'value', e.target.value)}
/>
)
}
}
const onDeleteCustomParameter = (index: number) => {
const newParams = customParameters.filter((_, i) => i !== index)
setCustomParameters(newParams)
updateAssistantSettings({ customParameters: newParams })
}
const onReset = () => { const onReset = () => {
setTemperature(DEFAULT_TEMPERATURE) setTemperature(DEFAULT_TEMPERATURE)
setContextCount(DEFAULT_CONTEXTCOUNT) setContextCount(DEFAULT_CONTEXTCOUNT)
@@ -59,13 +136,15 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
setMaxTokens(0) setMaxTokens(0)
setStreamOutput(true) setStreamOutput(true)
setTopP(1) setTopP(1)
setCustomParameters([])
updateAssistantSettings({ updateAssistantSettings({
temperature: DEFAULT_TEMPERATURE, temperature: DEFAULT_TEMPERATURE,
contextCount: DEFAULT_CONTEXTCOUNT, contextCount: DEFAULT_CONTEXTCOUNT,
enableMaxTokens: false, enableMaxTokens: false,
maxTokens: 0, maxTokens: 0,
streamOutput: true, streamOutput: true,
topP: 1 topP: 1,
customParameters: []
}) })
} }
@@ -126,14 +205,14 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
onChangeComplete={onTemperatureChange} onChangeComplete={onTemperatureChange}
value={typeof temperature === 'number' ? temperature : 0} value={typeof temperature === 'number' ? temperature : 0}
marks={{ 0: '0', 0.7: '0.7', 2: '2' }} marks={{ 0: '0', 0.7: '0.7', 2: '2' }}
step={0.1} step={0.01}
/> />
</Col> </Col>
<Col span={3}> <Col span={3}>
<InputNumber <InputNumber
min={0} min={0}
max={2} max={2}
step={0.1} step={0.01}
value={temperature} value={temperature}
onChange={onTemperatureChange} onChange={onTemperatureChange}
style={{ width: '100%' }} style={{ width: '100%' }}
@@ -155,7 +234,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
onChangeComplete={onTopPChange} onChangeComplete={onTopPChange}
value={typeof topP === 'number' ? topP : 1} value={typeof topP === 'number' ? topP : 1}
marks={{ 0: '0', 1: '1' }} marks={{ 0: '0', 1: '1' }}
step={0.1} step={0.01}
/> />
</Col> </Col>
<Col span={3}> <Col span={3}>
@@ -250,6 +329,38 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
}} }}
/> />
</SettingRow> </SettingRow>
<Divider style={{ margin: '10px 0' }} />
<SettingRow style={{ minHeight: 30 }}>
<Label>{t('models.custom_parameters')}</Label>
<Button icon={<PlusOutlined />} onClick={onAddCustomParameter}>
{t('models.add_parameter')}
</Button>
</SettingRow>
{customParameters.map((param, index) => (
<Row key={index} align="middle" gutter={10} style={{ marginTop: 10 }}>
<Col span={6}>
<Input
placeholder={t('models.parameter_name')}
value={param.name}
onChange={(e) => onUpdateCustomParameter(index, 'name', e.target.value)}
/>
</Col>
<Col span={4}>
<Select
value={param.type}
onChange={(value) => onUpdateCustomParameter(index, 'type', value)}
style={{ width: '100%' }}>
<Select.Option value="string">{t('models.parameter_type.string')}</Select.Option>
<Select.Option value="number">{t('models.parameter_type.number')}</Select.Option>
<Select.Option value="boolean">{t('models.parameter_type.boolean')}</Select.Option>
</Select>
</Col>
<Col span={11}>{renderParameterValueInput(param, index)}</Col>
<Col span={3}>
<Button icon={<DeleteOutlined />} onClick={() => onDeleteCustomParameter(index)} danger />
</Col>
</Row>
))}
<Divider style={{ margin: '15px 0' }} /> <Divider style={{ margin: '15px 0' }} />
<HStack justifyContent="flex-end"> <HStack justifyContent="flex-end">
<Button onClick={onReset} style={{ width: 80 }} danger type="primary"> <Button onClick={onReset} style={{ width: 80 }} danger type="primary">
@@ -1,3 +1,4 @@
import { Box, HStack } from '@renderer/components/Layout'
import { Assistant, AssistantSettings } from '@renderer/types' import { Assistant, AssistantSettings } from '@renderer/types'
import { Button, Input } from 'antd' import { Button, Input } from 'antd'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
@@ -5,8 +6,6 @@ import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import { Box, HStack } from '../Layout'
interface Props { interface Props {
assistant: Assistant assistant: Assistant
updateAssistant: (assistant: Assistant) => void updateAssistant: (assistant: Assistant) => void
@@ -1,3 +1,5 @@
import { HStack } from '@renderer/components/Layout'
import { TopView } from '@renderer/components/TopView'
import { useAgent } from '@renderer/hooks/useAgents' import { useAgent } from '@renderer/hooks/useAgents'
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
import { Assistant } from '@renderer/types' import { Assistant } from '@renderer/types'
@@ -6,8 +8,6 @@ import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import { HStack } from '../Layout'
import { TopView } from '../TopView'
import AssistantMessagesSettings from './AssistantMessagesSettings' import AssistantMessagesSettings from './AssistantMessagesSettings'
import AssistantModelSettings from './AssistantModelSettings' import AssistantModelSettings from './AssistantModelSettings'
import AssistantPromptSettings from './AssistantPromptSettings' import AssistantPromptSettings from './AssistantPromptSettings'
@@ -1,7 +1,7 @@
import { FolderOpenOutlined, SaveOutlined } from '@ant-design/icons' import { FolderOpenOutlined, SaveOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { backupToWebdav, restoreFromWebdav } from '@renderer/services/BackupService' import { backupToWebdav, restoreFromWebdav, startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { import {
setWebdavAutoSync, setWebdavAutoSync,
@@ -66,6 +66,11 @@ const WebDavSettings: FC = () => {
const onToggleAutoSync = (checked: boolean) => { const onToggleAutoSync = (checked: boolean) => {
dispatch(setWebdavAutoSync(checked)) dispatch(setWebdavAutoSync(checked))
if (checked) {
startAutoSync()
} else {
stopAutoSync()
}
} }
const onSyncIntervalChange = (value: number) => { const onSyncIntervalChange = (value: number) => {
@@ -4,12 +4,13 @@ import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { import {
setClickAssistantToShowTopic, setClickAssistantToShowTopic,
setCustomCss,
setShowFilesIcon, setShowFilesIcon,
setShowMinappIcon, setShowMinappIcon,
setShowTopicTime setShowTopicTime
} from '@renderer/store/settings' } from '@renderer/store/settings'
import { ThemeMode } from '@renderer/types' import { ThemeMode } from '@renderer/types'
import { Select, Switch } from 'antd' import { Input, Select, Switch } from 'antd'
import { FC } from 'react' import { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@@ -26,7 +27,8 @@ const DisplaySettings: FC = () => {
topicPosition, topicPosition,
setTopicPosition, setTopicPosition,
clickAssistantToShowTopic, clickAssistantToShowTopic,
showTopicTime showTopicTime,
customCss
} = useSettings() } = useSettings()
const { theme: themeMode } = useTheme() const { theme: themeMode } = useTheme()
@@ -111,6 +113,19 @@ const DisplaySettings: FC = () => {
<Switch checked={showFilesIcon} onChange={(value) => dispatch(setShowFilesIcon(value))} /> <Switch checked={showFilesIcon} onChange={(value) => dispatch(setShowFilesIcon(value))} />
</SettingRow> </SettingRow>
</SettingGroup> </SettingGroup>
<SettingGroup theme={theme}>
<SettingTitle>{t('settings.display.custom.css')}</SettingTitle>
<SettingDivider />
<Input.TextArea
defaultValue={customCss}
onBlur={(e) => dispatch(setCustomCss(e.target.value))}
placeholder={t('settings.display.custom.css.placeholder')}
style={{
minHeight: 200,
fontFamily: 'monospace'
}}
/>
</SettingGroup>
</SettingContainer> </SettingContainer>
) )
} }
@@ -63,6 +63,7 @@ const GeneralSettings: FC = () => {
{ value: 'zh-CN', label: '中文', flag: '🇨🇳' }, { value: 'zh-CN', label: '中文', flag: '🇨🇳' },
{ value: 'zh-TW', label: '中文(繁体)', flag: '🇭🇰' }, { value: 'zh-TW', label: '中文(繁体)', flag: '🇭🇰' },
{ value: 'en-US', label: 'English', flag: '🇺🇸' }, { value: 'en-US', label: 'English', flag: '🇺🇸' },
{ value: 'ja-JP', label: '日本語', flag: '🇯🇵' },
{ value: 'ru-RU', label: 'Russian', flag: '🇷🇺' } { value: 'ru-RU', label: 'Russian', flag: '🇷🇺' }
] ]
@@ -116,14 +116,14 @@ const AssistantSettings: FC = () => {
onChangeComplete={onTemperatureChange} onChangeComplete={onTemperatureChange}
value={typeof temperature === 'number' ? temperature : 0} value={typeof temperature === 'number' ? temperature : 0}
marks={{ 0: '0', 0.7: '0.7', 2: '2' }} marks={{ 0: '0', 0.7: '0.7', 2: '2' }}
step={0.1} step={0.01}
/> />
</Col> </Col>
<Col span={3}> <Col span={3}>
<InputNumber <InputNumber
min={0} min={0}
max={2} max={2}
step={0.1} step={0.01}
value={temperature} value={temperature}
onChange={onTemperatureChange} onChange={onTemperatureChange}
style={{ width: '100%' }} style={{ width: '100%' }}
@@ -145,7 +145,7 @@ const AssistantSettings: FC = () => {
onChangeComplete={onTopPChange} onChangeComplete={onTopPChange}
value={typeof topP === 'number' ? topP : 1} value={typeof topP === 'number' ? topP : 1}
marks={{ 0: '0', 0.5: '0.5', 1: '1' }} marks={{ 0: '0', 0.5: '0.5', 1: '1' }}
step={0.1} step={0.01}
/> />
</Col> </Col>
<Col span={3}> <Col span={3}>
@@ -1,6 +1,7 @@
import { EditOutlined, MessageOutlined, RedoOutlined, SettingOutlined, TranslationOutlined } from '@ant-design/icons' import { EditOutlined, MessageOutlined, RedoOutlined, SettingOutlined, TranslationOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import PromptPopup from '@renderer/components/Popups/PromptPopup' import PromptPopup from '@renderer/components/Popups/PromptPopup'
import { isEmbeddingModel } from '@renderer/config/models'
import { TRANSLATE_PROMPT } from '@renderer/config/prompts' import { TRANSLATE_PROMPT } from '@renderer/config/prompts'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { useDefaultModel } from '@renderer/hooks/useAssistant' import { useDefaultModel } from '@renderer/hooks/useAssistant'
@@ -35,10 +36,12 @@ const ModelSettings: FC = () => {
.map((p) => ({ .map((p) => ({
label: p.isSystem ? t(`provider.${p.id}`) : p.name, label: p.isSystem ? t(`provider.${p.id}`) : p.name,
title: p.name, title: p.name,
options: sortBy(p.models, 'name').map((m) => ({ options: sortBy(p.models, 'name')
label: m.name, .filter((m) => !isEmbeddingModel(m))
value: getModelUniqId(m) .map((m) => ({
})) label: m.name,
value: getModelUniqId(m)
}))
})) }))
const defaultModelValue = useMemo( const defaultModelValue = useMemo(
@@ -1,8 +1,8 @@
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { setEnableTopicNaming } from '@renderer/store/settings' import { setEnableTopicNaming, setTopicNamingPrompt } from '@renderer/store/settings'
import { Divider, Modal, Switch } from 'antd' import { Button, Divider, Input, Modal, Switch } from 'antd'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@@ -15,7 +15,7 @@ interface Props {
const PopupContainer: React.FC<Props> = ({ resolve }) => { const PopupContainer: React.FC<Props> = ({ resolve }) => {
const [open, setOpen] = useState(true) const [open, setOpen] = useState(true)
const { t } = useTranslation() const { t } = useTranslation()
const { enableTopicNaming } = useSettings() const { enableTopicNaming, topicNamingPrompt } = useSettings()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const onOk = () => { const onOk = () => {
@@ -30,6 +30,10 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
resolve({}) resolve({})
} }
const handleReset = () => {
dispatch(setTopicNamingPrompt(''))
}
TopicNamingModalPopup.hide = onCancel TopicNamingModalPopup.hide = onCancel
return ( return (
@@ -47,6 +51,21 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
<div>{t('settings.models.enable_topic_naming')}</div> <div>{t('settings.models.enable_topic_naming')}</div>
<Switch checked={enableTopicNaming} onChange={(v) => dispatch(setEnableTopicNaming(v))} /> <Switch checked={enableTopicNaming} onChange={(v) => dispatch(setEnableTopicNaming(v))} />
</HStack> </HStack>
<Divider style={{ margin: '10px 0' }} />
<div style={{ marginBottom: 20 }}>
<div style={{ marginBottom: 10 }}>{t('settings.models.topic_naming_prompt')}</div>
<Input.TextArea
rows={4}
value={topicNamingPrompt || t('prompts.summarize')}
onChange={(e) => dispatch(setTopicNamingPrompt(e.target.value.trim()))}
placeholder={t('prompts.summarize')}
/>
{topicNamingPrompt && (
<Button style={{ marginTop: 10 }} onClick={handleReset}>
{t('common.reset')}
</Button>
)}
</div>
</Modal> </Modal>
) )
} }
@@ -7,7 +7,7 @@ import { useProvider } from '@renderer/hooks/useProvider'
import { fetchModels } from '@renderer/services/ApiService' import { fetchModels } from '@renderer/services/ApiService'
import { Model, Provider } from '@renderer/types' import { Model, Provider } from '@renderer/types'
import { getDefaultGroupName, isFreeModel, runAsyncFunction } from '@renderer/utils' import { getDefaultGroupName, isFreeModel, runAsyncFunction } from '@renderer/utils'
import { Avatar, Button, Empty, Flex, Modal, Popover, Radio, Tag } from 'antd' import { Avatar, Button, Empty, Flex, Modal, Popover, Radio, Tag, Tooltip } from 'antd'
import Search from 'antd/es/input/Search' import Search from 'antd/es/input/Search'
import { groupBy, isEmpty, uniqBy } from 'lodash' import { groupBy, isEmpty, uniqBy } from 'lodash'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
@@ -153,7 +153,9 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
{model.name[0].toUpperCase()} {model.name[0].toUpperCase()}
</Avatar> </Avatar>
<ListItemName> <ListItemName>
{model.name} <Tooltip title={model.id} placement="top">
<span style={{ cursor: 'help' }}>{model.name}</span>
</Tooltip>
{isVisionModel(model) && <VisionIcon />} {isVisionModel(model) && <VisionIcon />}
{isWebSearchModel(model) && <WebSearchIcon />} {isWebSearchModel(model) && <WebSearchIcon />}
{isFreeModel(model) && ( {isFreeModel(model) && (
@@ -196,9 +198,9 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
const SearchContainer = styled.div` const SearchContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 15px;
padding: 0 22px; padding: 0 22px;
padding-bottom: 20px; padding-bottom: 10px;
margin-top: -10px; margin-top: -10px;
.ant-radio-group { .ant-radio-group {
@@ -9,7 +9,14 @@ import {
} from '@ant-design/icons' } from '@ant-design/icons'
import VisionIcon from '@renderer/components/Icons/VisionIcon' import VisionIcon from '@renderer/components/Icons/VisionIcon'
import WebSearchIcon from '@renderer/components/Icons/WebSearchIcon' import WebSearchIcon from '@renderer/components/Icons/WebSearchIcon'
import { EMBEDDING_REGEX, getModelLogo, isVisionModel, isWebSearchModel, VISION_REGEX } from '@renderer/config/models' import {
EMBEDDING_REGEX,
getModelLogo,
isEmbeddingModel,
isVisionModel,
isWebSearchModel,
VISION_REGEX
} from '@renderer/config/models'
import { PROVIDER_CONFIG } from '@renderer/config/providers' import { PROVIDER_CONFIG } from '@renderer/config/providers'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { useAssistants, useDefaultModel } from '@renderer/hooks/useAssistant' import { useAssistants, useDefaultModel } from '@renderer/hooks/useAssistant'
@@ -20,7 +27,7 @@ import { checkApi } from '@renderer/services/ApiService'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { setModel } from '@renderer/store/assistants' import { setModel } from '@renderer/store/assistants'
import { Model, ModelType, Provider } from '@renderer/types' import { Model, ModelType, Provider } from '@renderer/types'
import { Avatar, Button, Card, Checkbox, Divider, Flex, Input, Popover, Space, Switch } from 'antd' import { Avatar, Button, Card, Checkbox, Divider, Flex, Input, Popover, Space, Switch, Tag } from 'antd'
import Link from 'antd/es/typography/Link' import Link from 'antd/es/typography/Link'
import { groupBy, isEmpty } from 'lodash' import { groupBy, isEmpty } from 'lodash'
import { FC, useEffect, useState } from 'react' import { FC, useEffect, useState } from 'react'
@@ -273,6 +280,11 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
</Avatar> </Avatar>
{model.name} {isVisionModel(model) && <VisionIcon />} {model.name} {isVisionModel(model) && <VisionIcon />}
{isWebSearchModel(model) && <WebSearchIcon />} {isWebSearchModel(model) && <WebSearchIcon />}
{isEmbeddingModel(model) && (
<Tag style={{ marginLeft: 10 }} color="orange">
{t('models.embedding')}
</Tag>
)}
<Popover content={modelTypeContent(model)} title={t('models.type.select')} trigger="click"> <Popover content={modelTypeContent(model)} title={t('models.type.select')} trigger="click">
<SettingIcon /> <SettingIcon />
</Popover> </Popover>
@@ -2,7 +2,8 @@ import Anthropic from '@anthropic-ai/sdk'
import { MessageCreateParamsNonStreaming, MessageParam } from '@anthropic-ai/sdk/resources' import { MessageCreateParamsNonStreaming, MessageParam } from '@anthropic-ai/sdk/resources'
import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant' import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
import { isEmbeddingModel } from '@renderer/config/models' import { isEmbeddingModel } from '@renderer/config/models'
import { SUMMARIZE_PROMPT } from '@renderer/config/prompts' import { getStoreSetting } from '@renderer/hooks/useSettings'
import i18n from '@renderer/i18n'
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService' import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService'
import { EVENT_NAMES } from '@renderer/services/EventService' import { EVENT_NAMES } from '@renderer/services/EventService'
import { filterContextMessages } from '@renderer/services/MessagesService' import { filterContextMessages } from '@renderer/services/MessagesService'
@@ -86,7 +87,8 @@ export default class AnthropicProvider extends BaseProvider {
max_tokens: maxTokens || DEFAULT_MAX_TOKENS, max_tokens: maxTokens || DEFAULT_MAX_TOKENS,
temperature: assistant?.settings?.temperature, temperature: assistant?.settings?.temperature,
top_p: assistant?.settings?.topP, top_p: assistant?.settings?.topP,
system: assistant.prompt system: assistant.prompt,
...this.getCustomParameters(assistant)
} }
let time_first_token_millsec = 0 let time_first_token_millsec = 0
@@ -188,7 +190,7 @@ export default class AnthropicProvider extends BaseProvider {
const systemMessage = { const systemMessage = {
role: 'system', role: 'system',
content: SUMMARIZE_PROMPT content: (getStoreSetting('topicNamingPrompt') as string) || i18n.t('prompts.summarize')
} }
const userMessage = { const userMessage = {
+14 -20
View File
@@ -1,10 +1,9 @@
import { REFERENCE_PROMPT } from '@renderer/config/prompts' import { REFERENCE_PROMPT } from '@renderer/config/prompts'
import { getOllamaKeepAliveTime } from '@renderer/hooks/useOllama' import { getOllamaKeepAliveTime } from '@renderer/hooks/useOllama'
import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService' import { getKnowledgeReferences } from '@renderer/services/KnowledgeService'
import store from '@renderer/store' import store from '@renderer/store'
import { Assistant, Message, Model, Provider, Suggestion } from '@renderer/types' import { Assistant, Message, Model, Provider, Suggestion } from '@renderer/types'
import { delay } from '@renderer/utils' import { delay } from '@renderer/utils'
import { take } from 'lodash'
import OpenAI from 'openai' import OpenAI from 'openai'
import { CompletionsParams } from '.' import { CompletionsParams } from '.'
@@ -95,25 +94,20 @@ export default abstract class BaseProvider {
return message.content return message.content
} }
const searchResults = await window.api.knowledgeBase.search({ const references = await getKnowledgeReferences(base, message)
search: message.content,
base: getKnowledgeBaseParams(base)
})
const references = take(searchResults, 6).map((item, index) => { return REFERENCE_PROMPT.replace('{question}', message.content).replace('{references}', references)
const sourceUrl = item.metadata.source }
const baseItem = base.items.find((i) => i.uniqueId === item.metadata.uniqueLoaderId)
return { protected getCustomParameters(assistant: Assistant) {
id: index, return (
content: item.pageContent, assistant?.settings?.customParameters?.reduce(
sourceUrl: sourceUrl.startsWith('http') ? sourceUrl : encodeURIComponent(sourceUrl), (acc, param) => ({
type: baseItem?.type ...acc,
} [param.name]: param.value
}) }),
{}
const referencesContent = `\`\`\`json\n${JSON.stringify(references, null, 2)}\n\`\`\`` ) || {}
)
return REFERENCE_PROMPT.replace('{question}', message.content).replace('{references}', referencesContent)
} }
} }
+5 -3
View File
@@ -9,7 +9,8 @@ import {
TextPart TextPart
} from '@google/generative-ai' } from '@google/generative-ai'
import { isEmbeddingModel, isWebSearchModel } from '@renderer/config/models' import { isEmbeddingModel, isWebSearchModel } from '@renderer/config/models'
import { SUMMARIZE_PROMPT } from '@renderer/config/prompts' import { getStoreSetting } from '@renderer/hooks/useSettings'
import i18n from '@renderer/i18n'
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService' import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService'
import { EVENT_NAMES } from '@renderer/services/EventService' import { EVENT_NAMES } from '@renderer/services/EventService'
import { filterContextMessages } from '@renderer/services/MessagesService' import { filterContextMessages } from '@renderer/services/MessagesService'
@@ -95,7 +96,8 @@ export default class GeminiProvider extends BaseProvider {
generationConfig: { generationConfig: {
maxOutputTokens: maxTokens, maxOutputTokens: maxTokens,
temperature: assistant?.settings?.temperature, temperature: assistant?.settings?.temperature,
topP: assistant?.settings?.topP topP: assistant?.settings?.topP,
...this.getCustomParameters(assistant)
}, },
safetySettings: [ safetySettings: [
{ category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_NONE }, { category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_NONE },
@@ -198,7 +200,7 @@ export default class GeminiProvider extends BaseProvider {
const systemMessage = { const systemMessage = {
role: 'system', role: 'system',
content: SUMMARIZE_PROMPT content: (getStoreSetting('topicNamingPrompt') as string) || i18n.t('prompts.summarize')
} }
const userMessage = { const userMessage = {
+27 -7
View File
@@ -1,8 +1,11 @@
import { isEmbeddingModel, isSupportedModel, isVisionModel } from '@renderer/config/models' import { isEmbeddingModel, isSupportedModel, isVisionModel } from '@renderer/config/models'
import { SUMMARIZE_PROMPT } from '@renderer/config/prompts' import { getStoreSetting } from '@renderer/hooks/useSettings'
import i18n from '@renderer/i18n'
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService' import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService'
import { EVENT_NAMES } from '@renderer/services/EventService' import { EVENT_NAMES } from '@renderer/services/EventService'
import { filterContextMessages } from '@renderer/services/MessagesService' import { filterContextMessages } from '@renderer/services/MessagesService'
import DuckDuckGoLiteSearch from '@renderer/tools/DuckDuckGoLiteSearch/function.json'
import DuckDuckGoLiteSearchCode from '@renderer/tools/DuckDuckGoLiteSearch/index.js?raw'
import { Assistant, FileTypes, Message, Model, Provider, Suggestion } from '@renderer/types' import { Assistant, FileTypes, Message, Model, Provider, Suggestion } from '@renderer/types'
import { removeQuotes } from '@renderer/utils' import { removeQuotes } from '@renderer/utils'
import { last, takeRight } from 'lodash' import { last, takeRight } from 'lodash'
@@ -10,7 +13,8 @@ import OpenAI, { AzureOpenAI } from 'openai'
import { import {
ChatCompletionContentPart, ChatCompletionContentPart,
ChatCompletionCreateParamsNonStreaming, ChatCompletionCreateParamsNonStreaming,
ChatCompletionMessageParam ChatCompletionMessageParam,
ChatCompletionTool
} from 'openai/resources' } from 'openai/resources'
import { CompletionsParams } from '.' import { CompletionsParams } from '.'
@@ -41,7 +45,7 @@ export default class OpenAIProvider extends BaseProvider {
} }
private get isNotSupportFiles() { private get isNotSupportFiles() {
const providers = ['deepseek', 'baichuan', 'minimax', 'doubao'] const providers = ['deepseek', 'baichuan', 'minimax']
return providers.includes(this.provider.id) return providers.includes(this.provider.id)
} }
@@ -132,7 +136,6 @@ export default class OpenAIProvider extends BaseProvider {
} }
const isOpenAIo1 = model.id.includes('o1-') const isOpenAIo1 = model.id.includes('o1-')
const isSupportStreamOutput = streamOutput
let time_first_token_millsec = 0 let time_first_token_millsec = 0
const start_time_millsec = new Date().getTime() const start_time_millsec = new Date().getTime()
@@ -147,11 +150,28 @@ export default class OpenAIProvider extends BaseProvider {
top_p: assistant?.settings?.topP, top_p: assistant?.settings?.topP,
max_tokens: maxTokens, max_tokens: maxTokens,
keep_alive: this.keepAliveTime, keep_alive: this.keepAliveTime,
stream: isSupportStreamOutput stream: streamOutput,
tools: [DuckDuckGoLiteSearch as ChatCompletionTool],
...this.getCustomParameters(assistant)
}) })
if (!isSupportStreamOutput) { if (!streamOutput) {
const time_completion_millsec = new Date().getTime() - start_time_millsec const time_completion_millsec = new Date().getTime() - start_time_millsec
stream.choices[0].message?.tool_calls?.forEach(async (toolCall) => {
const functionName = toolCall.function.name
const params = toolCall.function.arguments
console.log(functionName, DuckDuckGoLiteSearchCode)
const result = await window.api.vm.run(`
var params = ${params};
${DuckDuckGoLiteSearchCode}
`)
console.log(result)
})
return onChunk({ return onChunk({
text: stream.choices[0].message?.content || '', text: stream.choices[0].message?.content || '',
usage: stream.usage, usage: stream.usage,
@@ -219,7 +239,7 @@ export default class OpenAIProvider extends BaseProvider {
const systemMessage = { const systemMessage = {
role: 'system', role: 'system',
content: SUMMARIZE_PROMPT content: getStoreSetting('topicNamingPrompt') || i18n.t('prompts.summarize')
} }
const userMessage = { const userMessage = {
+1 -36
View File
@@ -7,44 +7,10 @@ import { KnowledgeItem } from '@renderer/types'
class KnowledgeQueue { class KnowledgeQueue {
private processing: Map<string, boolean> = new Map() private processing: Map<string, boolean> = new Map()
private pollingInterval: NodeJS.Timeout | null = null private readonly MAX_RETRIES = 1
// private readonly POLLING_INTERVAL = 5000
private readonly MAX_RETRIES = 2
constructor() { constructor() {
this.checkAllBases().catch(console.error) this.checkAllBases().catch(console.error)
this.startPolling()
}
private startPolling(): void {
if (this.pollingInterval) return
const state = store.getState()
state.knowledge.bases.forEach((base) => {
base.items.forEach((item) => {
if (item.processingStatus === 'processing') {
store.dispatch(
updateItemProcessingStatus({
baseId: base.id,
itemId: item.id,
status: 'pending',
progress: 0
})
)
}
})
})
// this.pollingInterval = setInterval(() => {
// this.checkAllBases()
// }, this.POLLING_INTERVAL)
}
private stopPolling(): void {
if (this.pollingInterval) {
clearInterval(this.pollingInterval)
this.pollingInterval = null
}
} }
public async checkAllBases(): Promise<void> { public async checkAllBases(): Promise<void> {
@@ -111,7 +77,6 @@ class KnowledgeQueue {
} }
stopAllProcessing(): void { stopAllProcessing(): void {
this.stopPolling()
for (const baseId of this.processing.keys()) { for (const baseId of this.processing.keys()) {
this.processing.set(baseId, false) this.processing.set(baseId, false)
} }
@@ -100,7 +100,8 @@ export const getAssistantSettings = (assistant: Assistant): AssistantSettings =>
maxTokens: getAssistantMaxTokens(), maxTokens: getAssistantMaxTokens(),
streamOutput: assistant?.settings?.streamOutput ?? true, streamOutput: assistant?.settings?.streamOutput ?? true,
hideMessages: assistant?.settings?.hideMessages ?? false, hideMessages: assistant?.settings?.hideMessages ?? false,
autoResetModel: assistant?.settings?.autoResetModel ?? false autoResetModel: assistant?.settings?.autoResetModel ?? false,
customParameters: assistant?.settings?.customParameters ?? []
} }
} }
+56 -21
View File
@@ -59,6 +59,10 @@ export async function reset() {
// 备份到 webdav // 备份到 webdav
export async function backupToWebdav({ showMessage = true }: { showMessage?: boolean } = {}) { export async function backupToWebdav({ showMessage = true }: { showMessage?: boolean } = {}) {
if (isManualBackupRunning) {
console.log('[Backup] Manual backup already in progress')
return
}
const { webdavHost, webdavUser, webdavPass, webdavPath } = store.getState().settings const { webdavHost, webdavUser, webdavPass, webdavPath } = store.getState().settings
const backupData = await getBackupData() const backupData = await getBackupData()
@@ -83,6 +87,8 @@ export async function backupToWebdav({ showMessage = true }: { showMessage?: boo
title: i18n.t('message.backup.failed'), title: i18n.t('message.backup.failed'),
content: error.message content: error.message
}) })
} finally {
isManualBackupRunning = false
} }
} }
@@ -109,40 +115,69 @@ export async function restoreFromWebdav() {
} }
} }
let syncInterval: NodeJS.Timeout | null = null let autoSyncStarted = false
export function startAutoSync() { let syncTimeout: NodeJS.Timeout | null = null
const { webdavAutoSync, webdavHost, webdavSyncInterval } = store.getState().settings let isAutoBackupRunning = false
let isManualBackupRunning = false
if (syncInterval) { export function startAutoSync() {
stopAutoSync() if (autoSyncStarted) {
return
} }
if (webdavAutoSync && webdavHost) { const { webdavAutoSync, webdavHost, webdavSyncInterval } = store.getState().settings
console.log('[AutoSync] Starting auto sync with interval:', webdavSyncInterval, 'minutes')
const performBackup = async () => { if (!webdavAutoSync || !webdavHost || webdavSyncInterval <= 0) {
try { console.log('[AutoSync] Invalid sync settings, auto sync disabled')
console.log('[AutoSync] Performing backup...') return
await backupToWebdav({ showMessage: false }) }
window.message.success({ content: i18n.t('message.backup.success'), key: 'webdav-sync' })
} catch (error) { autoSyncStarted = true
console.error('[AutoSync] Backup failed:', error)
window.message.error({ content: i18n.t('message.backup.failed'), key: 'webdav-sync' }) stopAutoSync()
}
scheduleNextBackup()
function scheduleNextBackup() {
if (syncTimeout) {
clearTimeout(syncTimeout)
syncTimeout = null
} }
syncInterval = setInterval(performBackup, webdavSyncInterval * 60 * 1000) syncTimeout = setTimeout(performAutoBackup, webdavSyncInterval * 60 * 1000)
console.log(`[AutoSync] Next sync scheduled in ${webdavSyncInterval} minutes`)
}
console.log(`[AutoSync] Sync interval set up: ${webdavSyncInterval} minutes`) async function performAutoBackup() {
if (isAutoBackupRunning || isManualBackupRunning) {
console.log('[AutoSync] Backup already in progress, rescheduling')
scheduleNextBackup()
return
}
isAutoBackupRunning = true
try {
console.log('[AutoSync] Performing auto backup...')
await backupToWebdav({ showMessage: false })
window.message.success({ content: i18n.t('message.backup.success'), key: 'webdav-auto-sync' })
} catch (error) {
console.error('[AutoSync] Auto backup failed:', error)
window.message.error({ content: i18n.t('message.backup.failed'), key: 'webdav-auto-sync' })
} finally {
isAutoBackupRunning = false
scheduleNextBackup()
}
} }
} }
export function stopAutoSync() { export function stopAutoSync() {
if (syncInterval) { if (syncTimeout) {
console.log('[AutoSync] Stopping auto sync') console.log('[AutoSync] Stopping auto sync')
clearInterval(syncInterval) clearTimeout(syncTimeout)
syncInterval = null syncTimeout = null
} }
isAutoBackupRunning = false
autoSyncStarted = false
} }
async function getBackupData() { async function getBackupData() {
+2 -1
View File
@@ -20,5 +20,6 @@ export const EVENT_NAMES = {
NEW_BRANCH: 'NEW_BRANCH', NEW_BRANCH: 'NEW_BRANCH',
EXPORT_TOPIC_IMAGE: 'EXPORT_TOPIC_IMAGE', EXPORT_TOPIC_IMAGE: 'EXPORT_TOPIC_IMAGE',
LOCATE_MESSAGE: 'LOCATE_MESSAGE', LOCATE_MESSAGE: 'LOCATE_MESSAGE',
ADD_NEW_TOPIC: 'ADD_NEW_TOPIC' ADD_NEW_TOPIC: 'ADD_NEW_TOPIC',
RESEND_MESSAGE: 'RESEND_MESSAGE'
} }
+2 -2
View File
@@ -51,7 +51,7 @@ class FileManager {
if (file) { if (file) {
const filesPath = store.getState().runtime.filesPath const filesPath = store.getState().runtime.filesPath
file.path = filesPath + file.id file.path = filesPath + '/' + file.id + file.ext
} }
return file return file
@@ -91,7 +91,7 @@ class FileManager {
static getFileUrl(file: FileType) { static getFileUrl(file: FileType) {
const filesPath = store.getState().runtime.filesPath const filesPath = store.getState().runtime.filesPath
return 'file://' + filesPath + '/' + file.id + file.ext return 'file://' + filesPath + '/' + file.name
} }
} }
+72 -3
View File
@@ -1,8 +1,10 @@
import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces'
import AiProvider from '@renderer/providers/AiProvider' import AiProvider from '@renderer/providers/AiProvider'
import { KnowledgeBase, KnowledgeBaseParams } from '@renderer/types' import { FileType, KnowledgeBase, KnowledgeBaseParams, Message } from '@renderer/types'
import { isEmpty } from 'lodash' import { isEmpty, take } from 'lodash'
import { getProviderByModel } from './AssistantService' import { getProviderByModel } from './AssistantService'
import FileManager from './FileManager'
export const getKnowledgeBaseParams = (base: KnowledgeBase): KnowledgeBaseParams => { export const getKnowledgeBaseParams = (base: KnowledgeBase): KnowledgeBaseParams => {
const provider = getProviderByModel(base.model) const provider = getProviderByModel(base.model)
@@ -14,7 +16,7 @@ export const getKnowledgeBaseParams = (base: KnowledgeBase): KnowledgeBaseParams
let host = aiProvider.getBaseURL() let host = aiProvider.getBaseURL()
if (host.includes('generativelanguage.googleapis.com')) { if (provider.type === 'gemini') {
host = host + '/v1beta/openai/' host = host + '/v1beta/openai/'
} }
@@ -23,6 +25,73 @@ export const getKnowledgeBaseParams = (base: KnowledgeBase): KnowledgeBaseParams
model: base.model.id, model: base.model.id,
dimensions: base.dimensions, dimensions: base.dimensions,
apiKey: aiProvider.getApiKey(), apiKey: aiProvider.getApiKey(),
apiVersion: provider.apiVersion,
baseURL: host baseURL: host
} }
} }
export const getFileFromUrl = async (url: string): Promise<FileType | null> => {
let fileName = ''
if (url && url.includes('CherryStudio')) {
if (url.includes('/Data/Files')) {
fileName = url.split('/Data/Files/')[1]
}
if (url.includes('\\Data\\Files')) {
fileName = url.split('\\Data\\Files\\')[1]
}
}
if (fileName) {
const fileId = fileName.split('.')[0]
const file = await FileManager.getFile(fileId)
if (file) {
return file
}
}
return null
}
export const getKnowledgeSourceUrl = async (item: ExtractChunkData & { file: FileType | null }) => {
if (item.metadata.source.startsWith('http')) {
return item.metadata.source
}
if (item.file) {
return `[${item.file.origin_name}](http://file/${item.file.name})`
}
return item.metadata.source
}
export const getKnowledgeReferences = async (base: KnowledgeBase, message: Message) => {
const searchResults = await window.api.knowledgeBase.search({
search: message.content,
base: getKnowledgeBaseParams(base)
})
const _searchResults = await Promise.all(
searchResults.map(async (item) => {
const file = await getFileFromUrl(item.metadata.source)
return { ...item, file }
})
)
const references = await Promise.all(
take(_searchResults, 6).map(async (item, index) => {
const baseItem = base.items.find((i) => i.uniqueId === item.metadata.uniqueLoaderId)
return {
id: index,
content: item.pageContent,
sourceUrl: await getKnowledgeSourceUrl(item),
type: baseItem?.type
}
})
)
const referencesContent = `\`\`\`json\n${JSON.stringify(references, null, 2)}\n\`\`\``
return referencesContent
}
+1 -1
View File
@@ -28,7 +28,7 @@ const persistedReducer = persistReducer(
{ {
key: 'cherry-studio', key: 'cherry-studio',
storage, storage,
version: 49, version: 51,
blacklist: ['runtime'], blacklist: ['runtime'],
migrate migrate
}, },
+10 -9
View File
@@ -304,15 +304,16 @@ const initialState: LlmState = {
isSystem: true, isSystem: true,
enabled: false enabled: false
}, },
// { {
// id: 'jina', id: 'jina',
// name: 'Jina', name: 'Jina',
// apiKey: '', type: 'openai',
// apiHost: 'https://api.jina.ai', apiKey: '',
// models: SYSTEM_MODELS.jina, apiHost: 'https://api.jina.ai',
// isSystem: true, models: SYSTEM_MODELS.jina,
// enabled: false isSystem: true,
// }, enabled: false
},
{ {
id: 'aihubmix', id: 'aihubmix',
name: 'AiHubMix', name: 'AiHubMix',
+17 -9
View File
@@ -659,15 +659,6 @@ const migrateConfig = {
isSystem: true, isSystem: true,
enabled: false enabled: false
} }
// {
// id: 'jina',
// name: 'Jina',
// apiKey: '',
// apiHost: 'https://api.jina.ai',
// models: SYSTEM_MODELS.jina,
// isSystem: true,
// enabled: false
// }
] ]
} }
} }
@@ -767,6 +758,23 @@ const migrateConfig = {
] ]
} }
return state return state
},
'50': (state: RootState) => {
state.llm.providers.push({
id: 'jina',
name: 'Jina',
type: 'openai',
apiKey: '',
apiHost: 'https://api.jina.ai',
models: SYSTEM_MODELS.jina,
isSystem: true,
enabled: false
})
return state
},
'51': (state: RootState) => {
state.settings.topicNamingPrompt = ''
return state
} }
} }
+14 -2
View File
@@ -44,6 +44,8 @@ export interface SettingsState {
// Sidebar icons // Sidebar icons
showMinappIcon: boolean showMinappIcon: boolean
showFilesIcon: boolean showFilesIcon: boolean
customCss: string
topicNamingPrompt: string
} }
const initialState: SettingsState = { const initialState: SettingsState = {
@@ -83,7 +85,9 @@ const initialState: SettingsState = {
autoTranslateWithSpace: false, autoTranslateWithSpace: false,
enableTopicNaming: true, enableTopicNaming: true,
showMinappIcon: true, showMinappIcon: true,
showFilesIcon: true showFilesIcon: true,
customCss: '',
topicNamingPrompt: ''
} }
const settingsSlice = createSlice({ const settingsSlice = createSlice({
@@ -207,6 +211,12 @@ const settingsSlice = createSlice({
}, },
setPasteLongTextThreshold: (state, action: PayloadAction<number>) => { setPasteLongTextThreshold: (state, action: PayloadAction<number>) => {
state.pasteLongTextThreshold = action.payload state.pasteLongTextThreshold = action.payload
},
setCustomCss: (state, action: PayloadAction<string>) => {
state.customCss = action.payload
},
setTopicNamingPrompt: (state, action: PayloadAction<string>) => {
state.topicNamingPrompt = action.payload
} }
} }
}) })
@@ -250,7 +260,9 @@ export const {
setEnableTopicNaming, setEnableTopicNaming,
setShowMinappIcon, setShowMinappIcon,
setShowFilesIcon, setShowFilesIcon,
setPasteLongTextThreshold setPasteLongTextThreshold,
setCustomCss,
setTopicNamingPrompt
} = settingsSlice.actions } = settingsSlice.actions
export default settingsSlice.reducer export default settingsSlice.reducer
@@ -0,0 +1,22 @@
{
"type": "function",
"function": {
"name": "DuckDuckGoLiteSearch",
"description": "A search engine useful for answering questions about current events.",
"parameters": {
"type": "object",
"properties": {
"q": {
"type": "string",
"description": "Keywords for query"
},
"kl": {
"type": "string",
"description": "Language/region code (e.g., wt-wt, us-en, uk-en)",
"default": "wt-wt"
}
},
"required": ["q"]
}
}
}
@@ -0,0 +1,23 @@
new Promise((resolve, reject) => {
async function makeRequest() {
try {
const response = await axios.request({
method: 'post',
maxBodyLength: Infinity,
url: 'https://google.serper.dev/search',
headers: {
'X-API-KEY': 'fa70255d0ab3402ee2ddb6455f6b317e73588fc7',
'Content-Type': 'application/json'
},
data: params
})
console.log(JSON.stringify(response.data))
resolve(response.data)
} catch (error) {
console.log(error)
reject(error.toString())
}
}
makeRequest()
})
+8 -1
View File
@@ -30,6 +30,11 @@ export type AssistantSettings = {
streamOutput: boolean streamOutput: boolean
hideMessages: boolean hideMessages: boolean
autoResetModel: boolean autoResetModel: boolean
customParameters?: {
name: string
value: string | number | boolean
type: 'string' | 'number' | 'boolean'
}[]
} }
export type Agent = Omit<Assistant, 'model'> export type Agent = Omit<Assistant, 'model'>
@@ -154,7 +159,7 @@ export enum ThemeMode {
dark = 'dark', dark = 'dark',
auto = 'auto' auto = 'auto'
} }
export type LanguageVarious = 'zh-CN' | 'zh-TW' | 'en-US' | 'ru-RU' export type LanguageVarious = 'zh-CN' | 'zh-TW' | 'en-US' | 'ru-RU' | 'ja-JP'
export type CodeStyleVarious = BuiltinTheme | 'auto' export type CodeStyleVarious = BuiltinTheme | 'auto'
export type WebDavConfig = { export type WebDavConfig = {
@@ -208,6 +213,7 @@ export interface KnowledgeBase {
items: KnowledgeItem[] items: KnowledgeItem[]
created_at: number created_at: number
updated_at: number updated_at: number
version: number
} }
export type KnowledgeBaseParams = { export type KnowledgeBaseParams = {
@@ -215,5 +221,6 @@ export type KnowledgeBaseParams = {
model: string model: string
dimensions: number dimensions: number
apiKey: string apiKey: string
apiVersion?: string
baseURL: string baseURL: string
} }
+524 -311
View File
File diff suppressed because it is too large Load Diff