Compare commits
1 Commits
v0.9.4
...
feat/plugi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebf61b1ce9 |
19
.github/workflows/auto-assign.yml
vendored
19
.github/workflows/auto-assign.yml
vendored
@@ -1,19 +0,0 @@
|
||||
name: Auto Assign
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
pull_request:
|
||||
types: [opened]
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: 'Auto-assign issue'
|
||||
uses: pozil/auto-assign-issue@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
assignees: kangfenmao
|
||||
numOfAssignee: 1
|
||||
@@ -1,19 +0,0 @@
|
||||
diff --git a/src/markdown-loader.js b/src/markdown-loader.js
|
||||
index 8a17cb7f5a68d90d2be21682db6e95ce22a3e71c..9ee868ef9d4ff3dc914b3abc3c8006deb1e9c6c6 100644
|
||||
--- a/src/markdown-loader.js
|
||||
+++ b/src/markdown-loader.js
|
||||
@@ -1,5 +1,4 @@
|
||||
import { micromark } from 'micromark';
|
||||
-import { mdxJsx } from 'micromark-extension-mdx-jsx';
|
||||
import { gfmHtml, gfm } from 'micromark-extension-gfm';
|
||||
import createDebugMessages from 'debug';
|
||||
import fs from 'node:fs';
|
||||
@@ -21,7 +20,7 @@ export class MarkdownLoader extends BaseLoader {
|
||||
? (await getSafe(this.filePathOrUrl, { format: 'buffer' })).body
|
||||
: await stream2buffer(fs.createReadStream(this.filePathOrUrl));
|
||||
this.debug('MarkdownLoader stream created');
|
||||
- const result = micromark(buffer, { extensions: [gfm(), mdxJsx()], htmlExtensions: [gfmHtml()] });
|
||||
+ const result = micromark(buffer, { extensions: [gfm()], htmlExtensions: [gfmHtml()] });
|
||||
this.debug('Markdown parsed...');
|
||||
const webLoader = new WebLoader({
|
||||
urlOrContent: result,
|
||||
@@ -78,8 +78,4 @@ afterPack: scripts/after-pack.js
|
||||
afterSign: scripts/notarize.js
|
||||
releaseInfo:
|
||||
releaseNotes: |
|
||||
文件支持删除
|
||||
增加 Hika 小程序
|
||||
增加 WebDAV 同步状态显示
|
||||
自定义参数增加 JSON 类型
|
||||
腾讯混元的联网开关
|
||||
增加 Genspark 小程序
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "CherryStudio",
|
||||
"version": "0.9.4",
|
||||
"version": "0.9.2",
|
||||
"private": true,
|
||||
"description": "A powerful AI assistant for producer.",
|
||||
"main": "./out/main/index.js",
|
||||
@@ -53,7 +53,7 @@
|
||||
"@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-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-markdown": "patch:@llm-tools/embedjs-loader-markdown@npm%3A0.1.25#~/.yarn/patches/@llm-tools-embedjs-loader-markdown-npm-0.1.25-d1d536d640.patch",
|
||||
"@llm-tools/embedjs-loader-markdown": "^0.1.25",
|
||||
"@llm-tools/embedjs-loader-msoffice": "^0.1.25",
|
||||
"@llm-tools/embedjs-loader-pdf": "^0.1.25",
|
||||
"@llm-tools/embedjs-loader-sitemap": "^0.1.25",
|
||||
@@ -64,7 +64,6 @@
|
||||
"adm-zip": "^0.5.16",
|
||||
"apache-arrow": "^18.1.0",
|
||||
"docx": "^9.0.2",
|
||||
"dompurify": "^3.2.3",
|
||||
"electron-log": "^5.1.5",
|
||||
"electron-store": "^8.2.0",
|
||||
"electron-updater": "^6.3.9",
|
||||
@@ -96,7 +95,7 @@
|
||||
"@types/tinycolor2": "^1",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"antd": "^5.22.5",
|
||||
"axios": "^1.7.3",
|
||||
"axios": "^1.7.9",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"dayjs": "^1.11.11",
|
||||
"dexie": "^4.0.8",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import vm from 'node:vm'
|
||||
|
||||
import { Shortcut, ThemeMode } from '@types'
|
||||
import axios from 'axios'
|
||||
import { BrowserWindow, ipcMain, ProxyConfig, session, shell } from 'electron'
|
||||
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:remove', KnowledgeService.remove)
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -45,14 +45,14 @@ class KnowledgeService {
|
||||
azureOpenAIApiDeploymentName: model,
|
||||
azureOpenAIApiInstanceName: getInstanceName(baseURL),
|
||||
dimensions,
|
||||
batchSize: 10
|
||||
batchSize: 15
|
||||
})
|
||||
: new OpenAiEmbeddings({
|
||||
model,
|
||||
apiKey,
|
||||
configuration: { baseURL },
|
||||
dimensions,
|
||||
batchSize: 10
|
||||
batchSize: 15
|
||||
})
|
||||
)
|
||||
.setVectorDatabase(new LibSqlDb({ path: path.join(this.storageDir, id) }))
|
||||
@@ -122,7 +122,7 @@ class KnowledgeService {
|
||||
return await ragApplication.addLoader(new ExcelLoader({ filePathOrUrl: file.path }) as any, forceReload)
|
||||
}
|
||||
|
||||
if (['.md'].includes(file.ext)) {
|
||||
if (['.md', '.mdx'].includes(file.ext)) {
|
||||
return await ragApplication.addLoader(new MarkdownLoader({ filePathOrUrl: file.path }) as any, forceReload)
|
||||
}
|
||||
|
||||
|
||||
3
src/preload/index.d.ts
vendored
3
src/preload/index.d.ts
vendored
@@ -76,6 +76,9 @@ declare global {
|
||||
remove: ({ uniqueId, base }: { uniqueId: string; base: KnowledgeBaseParams }) => Promise<void>
|
||||
search: ({ search, base }: { search: string; base: KnowledgeBaseParams }) => Promise<ExtractChunkData[]>
|
||||
}
|
||||
vm: {
|
||||
run: (code: string) => Promise<any>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,9 @@ const api = {
|
||||
ipcRenderer.invoke('knowledge-base:remove', { uniqueId, base }),
|
||||
search: ({ search, base }: { search: string; base: KnowledgeBaseParams }) =>
|
||||
ipcRenderer.invoke('knowledge-base:search', { search, base })
|
||||
},
|
||||
vm: {
|
||||
run: (code: string) => ipcRenderer.invoke('run-js', code)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -3,7 +3,7 @@ import { isMac } from '@renderer/config/constant'
|
||||
import { isLocalAi, UserAvatar } from '@renderer/config/env'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import useAvatar from '@renderer/hooks/useAvatar'
|
||||
import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime'
|
||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { Tooltip } from 'antd'
|
||||
import { Avatar } from 'antd'
|
||||
@@ -19,6 +19,7 @@ const Sidebar: FC = () => {
|
||||
const { pathname } = useLocation()
|
||||
const avatar = useAvatar()
|
||||
const { minappShow } = useRuntime()
|
||||
const { generating } = useRuntime()
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const { windowStyle, showMinappIcon, showFilesIcon } = useSettings()
|
||||
@@ -32,8 +33,11 @@ const Sidebar: FC = () => {
|
||||
const macTransparentWindow = isMac && windowStyle === 'transparent'
|
||||
const sidebarBgColor = macTransparentWindow ? 'transparent' : 'var(--navbar-background)'
|
||||
|
||||
const to = async (path: string) => {
|
||||
await modelGenerating()
|
||||
const to = (path: string) => {
|
||||
if (generating) {
|
||||
window.message.warning({ content: t('message.switch.disabled'), key: 'switch-assistant' })
|
||||
return
|
||||
}
|
||||
navigate(path)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import FeloAppLogo from '@renderer/assets/images/apps/felo.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 HikaLogo from '@renderer/assets/images/apps/hika.webp'
|
||||
import HuggingChatLogo from '@renderer/assets/images/apps/huggingchat.svg'
|
||||
import KimiAppLogo from '@renderer/assets/images/apps/kimi.jpg'
|
||||
import MetasoAppLogo from '@renderer/assets/images/apps/metaso.webp'
|
||||
@@ -227,13 +226,6 @@ const _apps: MinAppType[] = [
|
||||
url: 'https://thinkany.ai/',
|
||||
bodered: true
|
||||
},
|
||||
{
|
||||
id: 'hika',
|
||||
name: 'Hika',
|
||||
logo: HikaLogo,
|
||||
url: 'https://hika.fyi/',
|
||||
bodered: true
|
||||
},
|
||||
{
|
||||
id: 'github-copilot',
|
||||
name: 'GitHub Copilot',
|
||||
|
||||
@@ -961,11 +961,6 @@ export const TEXT_TO_IMAGES_MODELS = [
|
||||
}
|
||||
]
|
||||
|
||||
export const TEXT_TO_IMAGES_MODELS_SUPPORT_IMAGE_ENHANCEMENT = [
|
||||
'stabilityai/stable-diffusion-2-1',
|
||||
'stabilityai/stable-diffusion-xl-base-1.0'
|
||||
]
|
||||
|
||||
export function isTextToImageModel(model: Model): boolean {
|
||||
return TEXT_TO_IMAGE_REGEX.test(model.id)
|
||||
}
|
||||
@@ -1009,13 +1004,5 @@ export function isWebSearchModel(model: Model): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
if (provider.id === 'gemini' || provider?.type === 'gemini') {
|
||||
return model?.id === 'gemini-2.0-flash-exp'
|
||||
}
|
||||
|
||||
if (provider.id === 'hunyuan') {
|
||||
return model?.id !== 'hunyuan-lite'
|
||||
}
|
||||
|
||||
return false
|
||||
return (provider.id === 'gemini' || provider?.type === 'gemini') && model?.id === 'gemini-2.0-flash-exp'
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ export function usePaintings() {
|
||||
paintings,
|
||||
addPainting: () => {
|
||||
const newPainting: Painting = {
|
||||
model: TEXT_TO_IMAGES_MODELS[0].id,
|
||||
id: uuid(),
|
||||
urls: [],
|
||||
files: [],
|
||||
@@ -25,7 +24,7 @@ export function usePaintings() {
|
||||
seed: generateRandomSeed(),
|
||||
steps: 25,
|
||||
guidanceScale: 4.5,
|
||||
promptEnhancement: true
|
||||
model: TEXT_TO_IMAGES_MODELS[0].id
|
||||
}
|
||||
dispatch(addPainting(newPainting))
|
||||
return newPainting
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
import i18n from '@renderer/i18n'
|
||||
import store, { useAppSelector } from '@renderer/store'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
|
||||
export function useRuntime() {
|
||||
return useAppSelector((state) => state.runtime)
|
||||
}
|
||||
|
||||
export function modelGenerating() {
|
||||
const generating = store.getState().runtime.generating
|
||||
|
||||
if (generating) {
|
||||
window.message.warning({ content: i18n.t('message.switch.disabled'), key: 'model-generating' })
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
@@ -184,12 +184,7 @@
|
||||
"open": "Open",
|
||||
"size": "Size",
|
||||
"text": "Text",
|
||||
"title": "Files",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"delete.title": "Delete File",
|
||||
"delete.content": "Deleting a file will delete its reference from all messages. Are you sure you want to delete this file?",
|
||||
"delete.paintings.warning": "Image contains this file, deletion is not possible"
|
||||
"title": "Files"
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "Continue Chatting",
|
||||
@@ -284,9 +279,7 @@
|
||||
"regenerate.confirm": "This will replace your existing generated images. Do you want to continue?",
|
||||
"seed": "Seed",
|
||||
"seed_tip": "The same seed and prompt can produce similar images",
|
||||
"title": "Images",
|
||||
"prompt_enhancement": "Prompt Enhancement",
|
||||
"prompt_enhancement_tip": "Rewrite prompts into detailed, model-friendly versions when switched on"
|
||||
"title": "Images"
|
||||
},
|
||||
"provider": {
|
||||
"aihubmix": "AiHubMix",
|
||||
@@ -369,12 +362,7 @@
|
||||
"webdav.minutes": "Minutes",
|
||||
"webdav.restore.button": "Restore from WebDAV",
|
||||
"webdav.title": "WebDAV",
|
||||
"webdav.user": "WebDAV User",
|
||||
"webdav.syncStatus": "Sync Status",
|
||||
"webdav.autoSync.off": "Off",
|
||||
"webdav.noSync": "Waiting for next sync",
|
||||
"webdav.syncError": "Sync Error",
|
||||
"webdav.lastSync": "Last Sync"
|
||||
"webdav.user": "WebDAV User"
|
||||
},
|
||||
"display.title": "Display Settings",
|
||||
"font_size.title": "Message font size",
|
||||
@@ -580,7 +568,6 @@
|
||||
"directory_placeholder": "Enter Directory Path",
|
||||
"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",
|
||||
"no_provider": "Knowledge base model provider is not set, the knowledge base will no longer be supported, please create a new knowledge base",
|
||||
"source": "Source"
|
||||
},
|
||||
"models": {
|
||||
@@ -607,8 +594,7 @@
|
||||
"parameter_type": {
|
||||
"string": "Text",
|
||||
"number": "Number",
|
||||
"boolean": "Boolean",
|
||||
"json": "JSON"
|
||||
"boolean": "Boolean"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
|
||||
@@ -184,12 +184,7 @@
|
||||
"open": "開く",
|
||||
"size": "サイズ",
|
||||
"text": "テキスト",
|
||||
"title": "ファイル",
|
||||
"edit": "編集",
|
||||
"delete": "削除",
|
||||
"delete.title": "ファイルを削除",
|
||||
"delete.content": "ファイルを削除すると、ファイルがすべてのメッセージで参照されることを削除します。このファイルを削除してもよろしいですか?",
|
||||
"delete.paintings.warning": "画像に含まれているため、削除できません"
|
||||
"title": "ファイル"
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "チャットを続ける",
|
||||
@@ -282,9 +277,7 @@
|
||||
"regenerate.confirm": "これにより、既存の生成画像が置き換えられます。続行しますか?",
|
||||
"seed": "シード",
|
||||
"seed_tip": "同じシードとプロンプトで似た画像を生成できます",
|
||||
"title": "画像",
|
||||
"prompt_enhancement": "プロンプト強化",
|
||||
"prompt_enhancement_tip": "オンにすると、プロンプトを詳細でモデルに適したバージョンに書き直します"
|
||||
"title": "画像"
|
||||
},
|
||||
"provider": {
|
||||
"aihubmix": "AiHubMix",
|
||||
@@ -367,12 +360,7 @@
|
||||
"webdav.minutes": "分",
|
||||
"webdav.restore.button": "WebDAVから復元",
|
||||
"webdav.title": "WebDAV",
|
||||
"webdav.user": "WebDAVユーザー",
|
||||
"webdav.syncStatus": "同期状態",
|
||||
"webdav.autoSync.off": "オフ",
|
||||
"webdav.noSync": "次回の同期を待っています",
|
||||
"webdav.syncError": "同期エラー",
|
||||
"webdav.lastSync": "最終同期"
|
||||
"webdav.user": "WebDAVユーザー"
|
||||
},
|
||||
"display.title": "表示設定",
|
||||
"font_size.title": "メッセージのフォントサイズ",
|
||||
@@ -563,11 +551,7 @@
|
||||
"sitemap_placeholder": "サイトマップURLを入力",
|
||||
"directories": "ディレクトリ",
|
||||
"add_directory": "ディレクトリを追加",
|
||||
"directory_placeholder": "ディレクトリパスを入力",
|
||||
"model_info": "モデル情報",
|
||||
"not_support": "ナレッジベースデータベースエンジンが更新されました。このナレッジベースはもうサポートされていません。新しいナレッジベースを作成してください",
|
||||
"no_provider": "ナレッジベースモデルプロバイダーが設定されていません。ナレッジベースはもうサポートされていません。新しいナレッジベースを作成してください",
|
||||
"source": "ソース"
|
||||
"directory_placeholder": "ディレクトリパスを入力"
|
||||
},
|
||||
"models": {
|
||||
"pinned": "固定済み",
|
||||
@@ -593,8 +577,7 @@
|
||||
"parameter_type": {
|
||||
"string": "テキスト",
|
||||
"number": "数値",
|
||||
"boolean": "真偽値",
|
||||
"json": "JSON"
|
||||
"boolean": "真偽値"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
|
||||
@@ -184,12 +184,7 @@
|
||||
"open": "Открыть",
|
||||
"size": "Размер",
|
||||
"text": "Текст",
|
||||
"title": "Файлы",
|
||||
"edit": "Редактировать",
|
||||
"delete": "Удалить",
|
||||
"delete.title": "Удалить файл",
|
||||
"delete.content": "Удаление файла удалит его из всех сообщений, вы уверены, что хотите удалить этот файл?",
|
||||
"delete.paintings.warning": "В изображениях содержится этот файл, удаление невозможно"
|
||||
"title": "Файлы"
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "Продолжить чат",
|
||||
@@ -284,9 +279,7 @@
|
||||
"regenerate.confirm": "Это заменит ваши существующие сгенерированные изображения. Хотите продолжить?",
|
||||
"seed": "Ключ генерации",
|
||||
"seed_tip": "Одинаковый ключ генерации и промпт могут производить похожие изображения",
|
||||
"title": "Изображения",
|
||||
"prompt_enhancement": "Улучшение промпта",
|
||||
"prompt_enhancement_tip": "При включении переписывает промпт в более детальную, модель-ориентированную версию"
|
||||
"title": "Изображения"
|
||||
},
|
||||
"provider": {
|
||||
"aihubmix": "AiHubMix",
|
||||
@@ -369,12 +362,7 @@
|
||||
"webdav.minutes": "минут",
|
||||
"webdav.restore.button": "Восстановление с WebDAV",
|
||||
"webdav.title": "WebDAV",
|
||||
"webdav.user": "Пользователь WebDAV",
|
||||
"webdav.syncStatus": "Статус синхронизации",
|
||||
"webdav.autoSync.off": "Выключено",
|
||||
"webdav.noSync": "Ожидание следующей синхронизации",
|
||||
"webdav.syncError": "Ошибка синхронизации",
|
||||
"webdav.lastSync": "Последняя синхронизация"
|
||||
"webdav.user": "Пользователь WebDAV"
|
||||
},
|
||||
"display.title": "Настройки отображения",
|
||||
"font_size.title": "Размер шрифта сообщений",
|
||||
@@ -580,7 +568,6 @@
|
||||
"directory_placeholder": "Введите путь к директории",
|
||||
"model_info": "Модель информации",
|
||||
"not_support": "База знаний базы данных движок обновлен, база знаний больше не поддерживается, пожалуйста, создайте новую базу знаний",
|
||||
"no_provider": "База знаний модель поставщика не настроена, база знаний больше не поддерживается, пожалуйста, создайте новую базу знаний",
|
||||
"source": "Источник"
|
||||
},
|
||||
"models": {
|
||||
@@ -607,8 +594,7 @@
|
||||
"parameter_type": {
|
||||
"string": "Текст",
|
||||
"number": "Число",
|
||||
"boolean": "Логическое",
|
||||
"json": "JSON"
|
||||
"boolean": "Логическое"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
|
||||
@@ -185,12 +185,7 @@
|
||||
"open": "打开",
|
||||
"size": "大小",
|
||||
"text": "文本",
|
||||
"title": "文件",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"delete.title": "删除文件",
|
||||
"delete.content": "删除文件会删除文件在所有消息中的引用,确定要删除此文件吗?",
|
||||
"delete.paintings.warning": "绘图中包含该图片,暂时无法删除"
|
||||
"title": "文件"
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "继续聊天",
|
||||
@@ -285,9 +280,7 @@
|
||||
"regenerate.confirm": "这将覆盖已生成的图片,是否继续?",
|
||||
"seed": "随机种子",
|
||||
"seed_tip": "相同的种子和提示词可以生成相似的图片",
|
||||
"title": "图片",
|
||||
"prompt_enhancement": "提示词增强",
|
||||
"prompt_enhancement_tip": "开启后将提示重写为详细的、适合模型的版本"
|
||||
"title": "图片"
|
||||
},
|
||||
"provider": {
|
||||
"aihubmix": "AiHubMix",
|
||||
@@ -370,12 +363,7 @@
|
||||
"webdav.minutes": "分钟",
|
||||
"webdav.restore.button": "从 WebDAV 恢复",
|
||||
"webdav.title": "WebDAV",
|
||||
"webdav.user": "WebDAV 用户名",
|
||||
"webdav.syncStatus": "同步状态",
|
||||
"webdav.autoSync.off": "关闭",
|
||||
"webdav.noSync": "等待下次同步",
|
||||
"webdav.syncError": "同步错误",
|
||||
"webdav.lastSync": "上次同步时间"
|
||||
"webdav.user": "WebDAV 用户名"
|
||||
},
|
||||
"display.title": "显示设置",
|
||||
"font_size.title": "消息字体大小",
|
||||
@@ -569,7 +557,6 @@
|
||||
"directory_placeholder": "请输入目录路径",
|
||||
"model_info": "模型信息",
|
||||
"not_support": "知识库数据库引擎已更新,该知识库将不再支持,请重新创建知识库",
|
||||
"no_provider": "知识库模型服务商丢失,该知识库将不再支持,请重新创建知识库",
|
||||
"source": "来源"
|
||||
},
|
||||
"models": {
|
||||
@@ -596,8 +583,7 @@
|
||||
"parameter_type": {
|
||||
"string": "文本",
|
||||
"number": "数字",
|
||||
"boolean": "布尔值",
|
||||
"json": "JSON"
|
||||
"boolean": "布尔值"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
|
||||
@@ -184,12 +184,7 @@
|
||||
"open": "打開",
|
||||
"size": "大小",
|
||||
"text": "文本",
|
||||
"title": "檔案",
|
||||
"edit": "編輯",
|
||||
"delete": "刪除",
|
||||
"delete.title": "刪除檔案",
|
||||
"delete.content": "刪除檔案會刪除檔案在所有消息中的引用,確定要刪除此檔案嗎?",
|
||||
"delete.paintings.warning": "繪圖中包含該圖片,暫時無法刪除"
|
||||
"title": "檔案"
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "繼續聊天",
|
||||
@@ -284,9 +279,7 @@
|
||||
"regenerate.confirm": "這將覆蓋已生成的圖片,是否繼續?",
|
||||
"seed": "隨機種子",
|
||||
"seed_tip": "相同的種子和提示詞可以生成相似的圖片",
|
||||
"title": "繪圖",
|
||||
"prompt_enhancement": "提示詞增強",
|
||||
"prompt_enhancement_tip": "開啟後將提示重寫為詳細的、適合模型的版本"
|
||||
"title": "繪圖"
|
||||
},
|
||||
"provider": {
|
||||
"aihubmix": "AiHubMix",
|
||||
@@ -369,12 +362,7 @@
|
||||
"webdav.minutes": "分鐘",
|
||||
"webdav.restore.button": "從 WebDAV 恢復",
|
||||
"webdav.title": "WebDAV",
|
||||
"webdav.user": "WebDAV 使用者名稱",
|
||||
"webdav.syncStatus": "同步狀態",
|
||||
"webdav.autoSync.off": "關閉",
|
||||
"webdav.noSync": "等待下次同步",
|
||||
"webdav.syncError": "同步錯誤",
|
||||
"webdav.lastSync": "上次同步時間"
|
||||
"webdav.user": "WebDAV 使用者名稱"
|
||||
},
|
||||
"display.title": "顯示設定",
|
||||
"font_size.title": "訊息字體大小",
|
||||
@@ -568,7 +556,6 @@
|
||||
"directory_placeholder": "請輸入目錄路徑",
|
||||
"model_info": "模型信息",
|
||||
"not_support": "知識庫數據庫引擎已更新,該知識庫將不再支持,請重新創建知識庫",
|
||||
"no_provider": "知識庫模型提供商遺失,該知識庫將不再支持,請重新創建知識庫",
|
||||
"source": "來源"
|
||||
},
|
||||
"models": {
|
||||
@@ -595,8 +582,7 @@
|
||||
"parameter_type": {
|
||||
"string": "文字",
|
||||
"number": "數字",
|
||||
"boolean": "布林值",
|
||||
"json": "JSON"
|
||||
"boolean": "布林值"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
|
||||
@@ -76,8 +76,7 @@ const AppsContainer = styled.div`
|
||||
display: flex;
|
||||
min-width: 930px;
|
||||
max-width: 930px;
|
||||
max-height: 520px;
|
||||
min-height: 520px;
|
||||
max-height: 500px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, minmax(90px, 1fr));
|
||||
gap: 25px 25px;
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
import {
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
EllipsisOutlined,
|
||||
FileImageOutlined,
|
||||
FilePdfOutlined,
|
||||
FileTextOutlined
|
||||
} from '@ant-design/icons'
|
||||
import { FileImageOutlined, FilePdfOutlined, FileTextOutlined } from '@ant-design/icons'
|
||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||
import TextEditPopup from '@renderer/components/Popups/TextEditPopup'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import db from '@renderer/databases'
|
||||
import FileManager from '@renderer/services/FileManager'
|
||||
import store from '@renderer/store'
|
||||
import { FileType, FileTypes } from '@renderer/types'
|
||||
import { formatFileSize } from '@renderer/utils'
|
||||
import type { MenuProps } from 'antd'
|
||||
import { Button, Col, Dropdown, Image, Menu, Row, Spin, Table } from 'antd'
|
||||
import { Col, Image, Menu, Row, Spin, Table } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import { useLiveQuery } from 'dexie-react-hooks'
|
||||
import { FC, useState } from 'react'
|
||||
@@ -33,88 +23,14 @@ const FilesPage: FC = () => {
|
||||
return db.files.where('type').equals(fileType).sortBy('count')
|
||||
}, [fileType])
|
||||
|
||||
const handleDelete = async (fileId: string) => {
|
||||
const file = await FileManager.getFile(fileId)
|
||||
|
||||
const paintings = await store.getState().paintings.paintings
|
||||
const paintingsFiles = paintings.flatMap((p) => p.files)
|
||||
|
||||
if (paintingsFiles.some((p) => p.id === fileId)) {
|
||||
window.modal.warning({ content: t('files.delete.paintings.warning'), centered: true })
|
||||
return
|
||||
}
|
||||
|
||||
if (file) {
|
||||
await FileManager.deleteFile(fileId, true)
|
||||
}
|
||||
|
||||
const topics = await db.topics
|
||||
.filter((topic) => topic.messages.some((message) => message.files?.some((f) => f.id === fileId)))
|
||||
.toArray()
|
||||
|
||||
if (topics.length > 0) {
|
||||
for (const topic of topics) {
|
||||
const updatedMessages = topic.messages.map((message) => ({
|
||||
...message,
|
||||
files: message.files?.filter((f) => f.id !== fileId)
|
||||
}))
|
||||
await db.topics.update(topic.id, { messages: updatedMessages })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleRename = async (fileId: string) => {
|
||||
const file = await FileManager.getFile(fileId)
|
||||
if (file) {
|
||||
const newName = await TextEditPopup.show({ text: file.origin_name })
|
||||
if (newName) {
|
||||
FileManager.updateFile({ ...file, origin_name: newName })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getActionMenu = (fileId: string): MenuProps['items'] => [
|
||||
{
|
||||
key: 'rename',
|
||||
icon: <EditOutlined />,
|
||||
label: t('files.edit'),
|
||||
onClick: () => handleRename(fileId)
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
icon: <DeleteOutlined />,
|
||||
label: t('files.delete'),
|
||||
danger: true,
|
||||
onClick: () => {
|
||||
window.modal.confirm({
|
||||
title: t('files.delete.title'),
|
||||
content: t('files.delete.content'),
|
||||
centered: true,
|
||||
okButtonProps: { danger: true },
|
||||
onOk: () => handleDelete(fileId)
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const dataSource = files?.map((file) => {
|
||||
return {
|
||||
key: file.id,
|
||||
file: (
|
||||
<FileNameText className="text-nowrap" onClick={() => window.api.file.openPath(file.path)}>
|
||||
{file.origin_name}
|
||||
</FileNameText>
|
||||
),
|
||||
file: <FileNameText className="text-nowrap">{file.origin_name}</FileNameText>,
|
||||
size: formatFileSize(file),
|
||||
size_bytes: file.size,
|
||||
count: file.count,
|
||||
created_at: dayjs(file.created_at).format('MM-DD HH:mm'),
|
||||
created_at_unix: dayjs(file.created_at).unix(),
|
||||
actions: (
|
||||
<Dropdown menu={{ items: getActionMenu(file.id) }} trigger={['click']}>
|
||||
<Button type="text" size="small" icon={<EllipsisOutlined />} />
|
||||
</Dropdown>
|
||||
)
|
||||
actions: <a href={'file://' + FileManager.getSafePath(file)}>{t('files.open')}</a>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -129,25 +45,19 @@ const FilesPage: FC = () => {
|
||||
title: t('files.size'),
|
||||
dataIndex: 'size',
|
||||
key: 'size',
|
||||
width: '80px',
|
||||
sorter: (a: { size_bytes: number }, b: { size_bytes: number }) => b.size_bytes - a.size_bytes,
|
||||
align: 'center'
|
||||
width: '80px'
|
||||
},
|
||||
{
|
||||
title: t('files.count'),
|
||||
dataIndex: 'count',
|
||||
key: 'count',
|
||||
width: '60px',
|
||||
sorter: (a: { count: number }, b: { count: number }) => b.count - a.count,
|
||||
align: 'center'
|
||||
width: '60px'
|
||||
},
|
||||
{
|
||||
title: t('files.created_at'),
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
width: '120px',
|
||||
align: 'center',
|
||||
sorter: (a: { created_at_unix: number }, b: { created_at_unix: number }) => b.created_at_unix - a.created_at_unix
|
||||
width: '120px'
|
||||
},
|
||||
{
|
||||
title: t('files.actions'),
|
||||
@@ -203,7 +113,7 @@ const FilesPage: FC = () => {
|
||||
) : (
|
||||
<Table
|
||||
dataSource={dataSource}
|
||||
columns={columns as any}
|
||||
columns={columns}
|
||||
style={{ width: '100%' }}
|
||||
size="small"
|
||||
pagination={{ pageSize: 100 }}
|
||||
@@ -239,7 +149,6 @@ const TableContainer = styled(Scrollbar)`
|
||||
const FileNameText = styled.div`
|
||||
font-size: 14px;
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const ImageWrapper = styled.div`
|
||||
|
||||
@@ -36,7 +36,7 @@ const HomePage: FC = () => {
|
||||
}, [state])
|
||||
|
||||
return (
|
||||
<Container id="home-page">
|
||||
<Container>
|
||||
<Navbar activeAssistant={activeAssistant} activeTopic={activeTopic} setActiveTopic={setActiveTopic} />
|
||||
<ContentContainer id="content-container">
|
||||
{showAssistants && (
|
||||
|
||||
@@ -13,7 +13,7 @@ import TranslateButton from '@renderer/components/TranslateButton'
|
||||
import { isVisionModel, isWebSearchModel } from '@renderer/config/models'
|
||||
import db from '@renderer/databases'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime'
|
||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||
import { useMessageStyle, useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useShortcut, useShortcutDisplay } from '@renderer/hooks/useShortcuts'
|
||||
import { useShowTopics } from '@renderer/hooks/useStore'
|
||||
@@ -25,7 +25,7 @@ import { translateText } from '@renderer/services/TranslateService'
|
||||
import store, { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { setGenerating, setSearching } from '@renderer/store/runtime'
|
||||
import { Assistant, FileType, KnowledgeBase, Message, Topic } from '@renderer/types'
|
||||
import { classNames, delay, getFileExtension, uuid } from '@renderer/utils'
|
||||
import { delay, getFileExtension, uuid } from '@renderer/utils'
|
||||
import { documentExts, imageExts, textExts } from '@shared/config/constant'
|
||||
import { Button, Popconfirm, Tooltip } from 'antd'
|
||||
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
||||
@@ -97,7 +97,9 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
|
||||
_base = selectedKnowledgeBase
|
||||
|
||||
const sendMessage = useCallback(async () => {
|
||||
await modelGenerating()
|
||||
if (generating) {
|
||||
return
|
||||
}
|
||||
|
||||
if (inputEmpty) {
|
||||
return
|
||||
@@ -205,7 +207,10 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
|
||||
}
|
||||
|
||||
const addNewTopic = useCallback(async () => {
|
||||
await modelGenerating()
|
||||
if (generating) {
|
||||
window.message.warning({ content: t('message.switch.disabled'), key: 'generating' })
|
||||
return
|
||||
}
|
||||
|
||||
const topic = getDefaultTopic(assistant.id)
|
||||
|
||||
@@ -221,7 +226,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
|
||||
setActiveTopic(topic)
|
||||
|
||||
clickAssistantToShowTopic && setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 0)
|
||||
}, [addTopic, assistant, clickAssistantToShowTopic, setActiveTopic, setModel])
|
||||
}, [addTopic, assistant, clickAssistantToShowTopic, generating, setActiveTopic, setModel, t])
|
||||
|
||||
const clearTopic = async () => {
|
||||
if (generating) {
|
||||
@@ -383,12 +388,9 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Container onDragOver={handleDragOver} onDrop={handleDrop} className="inputbar">
|
||||
<Container onDragOver={handleDragOver} onDrop={handleDrop}>
|
||||
<AttachmentPreview files={files} setFiles={setFiles} />
|
||||
<InputBarContainer
|
||||
id="inputbar"
|
||||
className={classNames('inputbar-container', inputFocus && 'focus')}
|
||||
ref={containerRef}>
|
||||
<InputBarContainer id="inputbar" className={inputFocus ? 'focus' : ''} ref={containerRef}>
|
||||
<Textarea
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
|
||||
@@ -62,7 +62,6 @@ const KnowledgeBaseButton: FC<Props> = ({ selectedBase, onSelect, disabled, Tool
|
||||
<Popover
|
||||
placement="top"
|
||||
content={<KnowledgeBaseSelector selectedBase={selectedBase} onSelect={onSelect} />}
|
||||
overlayStyle={{ maxWidth: 400 }}
|
||||
trigger="click">
|
||||
<ToolbarButton type="text" onClick={() => selectedBase && onSelect(undefined)} disabled={disabled}>
|
||||
<FileSearchOutlined style={{ color: selectedBase ? 'var(--color-link)' : 'var(--color-icon)' }} />
|
||||
|
||||
@@ -3,7 +3,6 @@ import CopyIcon from '@renderer/components/Icons/CopyIcon'
|
||||
import { useSyntaxHighlighter } from '@renderer/context/SyntaxHighlighterProvider'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import React, { memo, useEffect, useRef, useState } from 'react'
|
||||
import DOMPurify from 'dompurify'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@@ -38,7 +37,6 @@ const ExpandButton: React.FC<{
|
||||
</ExpandButtonWrapper>
|
||||
)
|
||||
}
|
||||
const ALLOWED_TAGS = ['sub'] // 允许的HTML标签
|
||||
|
||||
const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
||||
const match = /language-(\w+)/.exec(className || '')
|
||||
@@ -135,15 +133,7 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
||||
{language === 'html' && children?.includes('</html>') && <Artifacts html={children} />}
|
||||
</CodeBlockWrapper>
|
||||
) : (
|
||||
<code
|
||||
className={className}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(children, {
|
||||
ALLOWED_TAGS,
|
||||
ALLOWED_ATTR: [] // 不允许任何属性
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<code className={className}>{children}</code>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ const MessageHeader: FC<Props> = memo(({ assistant, model, message }) => {
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<Container className="message-header">
|
||||
<Container>
|
||||
<AvatarWrapper style={avatarStyle}>
|
||||
{isAssistantMessage ? (
|
||||
<Avatar
|
||||
|
||||
@@ -11,11 +11,10 @@ import {
|
||||
} from '@ant-design/icons'
|
||||
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
|
||||
import TextEditPopup from '@renderer/components/Popups/TextEditPopup'
|
||||
import { modelGenerating } from '@renderer/hooks/useRuntime'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { translateText } from '@renderer/services/TranslateService'
|
||||
import { Message, Model } from '@renderer/types'
|
||||
import { removeTrailingDoubleSpaces, uuid } from '@renderer/utils'
|
||||
import { removeTrailingDoubleSpaces } from '@renderer/utils'
|
||||
import { Button, Dropdown, Popconfirm, Tooltip } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import { FC, useCallback, useMemo, useState } from 'react'
|
||||
@@ -70,8 +69,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
[setModel]
|
||||
)
|
||||
|
||||
const onNewBranch = useCallback(async () => {
|
||||
await modelGenerating()
|
||||
const onNewBranch = useCallback(() => {
|
||||
EventEmitter.emit(EVENT_NAMES.NEW_BRANCH, index)
|
||||
window.message.success({
|
||||
content: t('chat.message.new.branch.created'),
|
||||
@@ -79,8 +77,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
})
|
||||
}, [index, t])
|
||||
|
||||
const onResend = useCallback(async () => {
|
||||
await modelGenerating()
|
||||
const onResend = useCallback(() => {
|
||||
const _messages = onGetMessages?.() || []
|
||||
const index = _messages.findIndex((m) => m.id === message.id)
|
||||
const nextIndex = index + 1
|
||||
@@ -95,12 +92,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
translatedContent: undefined
|
||||
})
|
||||
}
|
||||
|
||||
if (!nextMessage) {
|
||||
onDeleteMessage?.(message)
|
||||
EventEmitter.emit(EVENT_NAMES.SEND_MESSAGE, { ...message, id: uuid() })
|
||||
}
|
||||
}, [assistantModel?.id, message, model?.id, onDeleteMessage, onGetMessages])
|
||||
}, [assistantModel?.id, message.id, model?.id, onGetMessages])
|
||||
|
||||
const onEdit = useCallback(async () => {
|
||||
let resendMessage = false
|
||||
@@ -166,23 +158,57 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
onClick: onEdit
|
||||
},
|
||||
{
|
||||
label: t('chat.message.new.branch'),
|
||||
key: 'new-branch',
|
||||
icon: <ForkOutlined />,
|
||||
onClick: onNewBranch
|
||||
label: t('chat.translate'),
|
||||
key: 'translate',
|
||||
icon: isTranslating ? <SyncOutlined spin /> : <TranslationOutlined />,
|
||||
children: [
|
||||
{
|
||||
label: '🇨🇳 ' + t('languages.chinese'),
|
||||
key: 'translate-chinese',
|
||||
onClick: () => handleTranslate('chinese')
|
||||
},
|
||||
{
|
||||
label: '🇭🇰 ' + t('languages.chinese-traditional'),
|
||||
key: 'translate-chinese-traditional',
|
||||
onClick: () => handleTranslate('chinese-traditional')
|
||||
},
|
||||
{
|
||||
label: '🇬🇧 ' + t('languages.english'),
|
||||
key: 'translate-english',
|
||||
onClick: () => handleTranslate('english')
|
||||
},
|
||||
{
|
||||
label: '🇯🇵 ' + t('languages.japanese'),
|
||||
key: 'translate-japanese',
|
||||
onClick: () => handleTranslate('japanese')
|
||||
},
|
||||
{
|
||||
label: '🇰🇷 ' + t('languages.korean'),
|
||||
key: 'translate-korean',
|
||||
onClick: () => handleTranslate('korean')
|
||||
},
|
||||
{
|
||||
label: '🇷🇺 ' + t('languages.russian'),
|
||||
key: 'translate-russian',
|
||||
onClick: () => handleTranslate('russian')
|
||||
},
|
||||
{
|
||||
label: '✖ ' + t('translate.close'),
|
||||
key: 'translate-close',
|
||||
onClick: () => onEditMessage?.({ ...message, translatedContent: undefined })
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
[message, onEdit, onNewBranch, t]
|
||||
[handleTranslate, isTranslating, message, onEdit, onEditMessage, t]
|
||||
)
|
||||
|
||||
const onAtModelRegenerate = async () => {
|
||||
await modelGenerating()
|
||||
const selectedModel = await SelectModelPopup.show({ model })
|
||||
selectedModel && onRegenerate(selectedModel)
|
||||
}
|
||||
|
||||
const onDeleteAndRegenerate = async () => {
|
||||
await modelGenerating()
|
||||
const onDeleteAndRegenerate = () => {
|
||||
onEditMessage?.({
|
||||
...message,
|
||||
content: '',
|
||||
@@ -228,56 +254,12 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!isUserMessage && (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
label: '🇨🇳 ' + t('languages.chinese'),
|
||||
key: 'translate-chinese',
|
||||
onClick: () => handleTranslate('chinese')
|
||||
},
|
||||
{
|
||||
label: '🇭🇰 ' + t('languages.chinese-traditional'),
|
||||
key: 'translate-chinese-traditional',
|
||||
onClick: () => handleTranslate('chinese-traditional')
|
||||
},
|
||||
{
|
||||
label: '🇬🇧 ' + t('languages.english'),
|
||||
key: 'translate-english',
|
||||
onClick: () => handleTranslate('english')
|
||||
},
|
||||
{
|
||||
label: '🇯🇵 ' + t('languages.japanese'),
|
||||
key: 'translate-japanese',
|
||||
onClick: () => handleTranslate('japanese')
|
||||
},
|
||||
{
|
||||
label: '🇰🇷 ' + t('languages.korean'),
|
||||
key: 'translate-korean',
|
||||
onClick: () => handleTranslate('korean')
|
||||
},
|
||||
{
|
||||
label: '🇷🇺 ' + t('languages.russian'),
|
||||
key: 'translate-russian',
|
||||
onClick: () => handleTranslate('russian')
|
||||
},
|
||||
{
|
||||
label: '✖ ' + t('translate.close'),
|
||||
key: 'translate-close',
|
||||
onClick: () => onEditMessage?.({ ...message, translatedContent: undefined })
|
||||
}
|
||||
]
|
||||
}}
|
||||
trigger={['click']}
|
||||
placement="topRight"
|
||||
arrow>
|
||||
<Tooltip title={t('chat.translate')} mouseEnterDelay={1.2}>
|
||||
<ActionButton className="message-action-button">
|
||||
<TranslationOutlined />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
</Dropdown>
|
||||
{isAssistantMessage && (
|
||||
<Tooltip title={t('chat.message.new.branch')} mouseEnterDelay={0.8}>
|
||||
<ActionButton className="message-action-button" onClick={onNewBranch}>
|
||||
<ForkOutlined />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Popconfirm
|
||||
title={t('message.message.delete.content')}
|
||||
|
||||
@@ -136,7 +136,6 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
(message: Message) => {
|
||||
const _messages = messages.filter((m) => m.id !== message.id)
|
||||
setMessages(_messages)
|
||||
setDisplayMessages(_messages)
|
||||
db.topics.update(topic.id, { messages: _messages })
|
||||
deleteMessageFiles(message)
|
||||
},
|
||||
|
||||
@@ -41,7 +41,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
|
||||
})
|
||||
|
||||
return (
|
||||
<Navbar className="home-navbar">
|
||||
<Navbar>
|
||||
{showAssistants && (
|
||||
<NavbarLeft style={{ justifyContent: 'space-between', borderRight: 'none', padding: '0 8px' }}>
|
||||
<NavbarIcon onClick={toggleShowAssistants} style={{ marginLeft: isMac ? 8 : 0 }}>
|
||||
@@ -52,9 +52,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
|
||||
</NavbarIcon>
|
||||
</NavbarLeft>
|
||||
)}
|
||||
<NavbarRight
|
||||
style={{ justifyContent: 'space-between', paddingRight: isWindows ? 140 : 12, flex: 1 }}
|
||||
className="home-navbar-right">
|
||||
<NavbarRight style={{ justifyContent: 'space-between', paddingRight: isWindows ? 140 : 12, flex: 1 }}>
|
||||
<HStack alignItems="center">
|
||||
{!showAssistants && (
|
||||
<NavbarIcon
|
||||
|
||||
@@ -4,11 +4,11 @@ import CopyIcon from '@renderer/components/Icons/CopyIcon'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { useAgents } from '@renderer/hooks/useAgents'
|
||||
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { modelGenerating } from '@renderer/hooks/useRuntime'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
|
||||
import { getDefaultTopic } from '@renderer/services/AssistantService'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { uuid } from '@renderer/utils'
|
||||
import { Dropdown } from 'antd'
|
||||
@@ -32,6 +32,7 @@ const Assistants: FC<Props> = ({
|
||||
onCreateDefaultAssistant
|
||||
}) => {
|
||||
const { assistants, removeAssistant, addAssistant, updateAssistants } = useAssistants()
|
||||
const generating = useAppSelector((state) => state.runtime.generating)
|
||||
const [dragging, setDragging] = useState(false)
|
||||
const { removeAllTopics } = useAssistant(activeAssistant.id)
|
||||
const { clickAssistantToShowTopic, topicPosition } = useSettings()
|
||||
@@ -40,7 +41,7 @@ const Assistants: FC<Props> = ({
|
||||
|
||||
const onDelete = useCallback(
|
||||
(assistant: Assistant) => {
|
||||
const _assistant: Assistant | undefined = last(assistants.filter((a) => a.id !== assistant.id))
|
||||
const _assistant = last(assistants.filter((a) => a.id !== assistant.id))
|
||||
_assistant ? setActiveAssistant(_assistant) : onCreateDefaultAssistant()
|
||||
removeAssistant(assistant.id)
|
||||
},
|
||||
@@ -116,8 +117,13 @@ const Assistants: FC<Props> = ({
|
||||
)
|
||||
|
||||
const onSwitchAssistant = useCallback(
|
||||
async (assistant: Assistant) => {
|
||||
await modelGenerating()
|
||||
(assistant: Assistant): any => {
|
||||
if (generating) {
|
||||
return window.message.warning({
|
||||
content: t('message.switch.disabled'),
|
||||
key: 'switch-assistant'
|
||||
})
|
||||
}
|
||||
|
||||
if (topicPosition === 'left' && clickAssistantToShowTopic) {
|
||||
EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)
|
||||
@@ -125,11 +131,11 @@ const Assistants: FC<Props> = ({
|
||||
|
||||
setActiveAssistant(assistant)
|
||||
},
|
||||
[clickAssistantToShowTopic, setActiveAssistant, topicPosition]
|
||||
[clickAssistantToShowTopic, generating, setActiveAssistant, t, topicPosition]
|
||||
)
|
||||
|
||||
return (
|
||||
<Container className="assistants-tab">
|
||||
<Container>
|
||||
<DragableList
|
||||
list={assistants}
|
||||
onUpdate={updateAssistants}
|
||||
|
||||
@@ -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 Scrollbar from '@renderer/components/Scrollbar'
|
||||
import {
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
setShowMessageDivider
|
||||
} from '@renderer/store/settings'
|
||||
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 { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
@@ -117,7 +117,7 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
}, [assistant])
|
||||
|
||||
return (
|
||||
<Container className="settings-tab">
|
||||
<Container>
|
||||
<SettingGroup style={{ marginTop: 10 }}>
|
||||
<SettingSubtitle style={{ marginTop: 0 }}>
|
||||
{t('settings.messages.model.title')}{' '}
|
||||
@@ -203,6 +203,106 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
/>
|
||||
</Col>
|
||||
</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>
|
||||
<SettingSubtitle style={{ marginTop: 0 }}>{t('settings.messages.title')}</SettingSubtitle>
|
||||
@@ -391,7 +491,6 @@ const Container = styled(Scrollbar)`
|
||||
padding: 0 10px;
|
||||
padding-right: 5px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 10px;
|
||||
`
|
||||
|
||||
const Label = styled.p`
|
||||
@@ -411,11 +510,24 @@ const SettingRowTitleSmall = styled(SettingRowTitle)`
|
||||
`
|
||||
|
||||
export const SettingGroup = styled.div<{ theme?: ThemeMode }>`
|
||||
padding: 0 5px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
border: 0.5px solid var(--color-border);
|
||||
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
|
||||
|
||||
@@ -10,12 +10,11 @@ import DragableList from '@renderer/components/DragableList'
|
||||
import PromptPopup from '@renderer/components/Popups/PromptPopup'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { modelGenerating } from '@renderer/hooks/useRuntime'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { TopicManager } from '@renderer/hooks/useTopic'
|
||||
import { fetchMessagesSummary } from '@renderer/services/ApiService'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import store from '@renderer/store'
|
||||
import store, { useAppSelector } from '@renderer/store'
|
||||
import { setGenerating } from '@renderer/store/runtime'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { exportTopicAsMarkdown, topicToMarkdown } from '@renderer/utils/export'
|
||||
@@ -36,36 +35,46 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
const { assistants } = useAssistants()
|
||||
const { assistant, removeTopic, moveTopic, updateTopic, updateTopics } = useAssistant(_assistant.id)
|
||||
const { t } = useTranslation()
|
||||
const generating = useAppSelector((state) => state.runtime.generating)
|
||||
const { showTopicTime, topicPosition } = useSettings()
|
||||
|
||||
const borderRadius = showTopicTime ? 12 : 17
|
||||
|
||||
const onDeleteTopic = useCallback(
|
||||
async (topic: Topic) => {
|
||||
await modelGenerating()
|
||||
(topic: Topic) => {
|
||||
if (generating) {
|
||||
window.message.warning({ content: t('message.switch.disabled'), key: 'generating' })
|
||||
return
|
||||
}
|
||||
const index = findIndex(assistant.topics, (t) => t.id === topic.id)
|
||||
setActiveTopic(assistant.topics[index + 1 === assistant.topics.length ? 0 : index + 1])
|
||||
removeTopic(topic)
|
||||
},
|
||||
[assistant.topics, removeTopic, setActiveTopic]
|
||||
[assistant.topics, generating, removeTopic, setActiveTopic, t]
|
||||
)
|
||||
|
||||
const onMoveTopic = useCallback(
|
||||
async (topic: Topic, toAssistant: Assistant) => {
|
||||
await modelGenerating()
|
||||
(topic: Topic, toAssistant: Assistant) => {
|
||||
if (generating) {
|
||||
window.message.warning({ content: t('message.switch.disabled'), key: 'generating' })
|
||||
return
|
||||
}
|
||||
const index = findIndex(assistant.topics, (t) => t.id === topic.id)
|
||||
setActiveTopic(assistant.topics[index + 1 === assistant.topics.length ? 0 : index + 1])
|
||||
moveTopic(topic, toAssistant)
|
||||
},
|
||||
[assistant.topics, moveTopic, setActiveTopic]
|
||||
[assistant.topics, generating, moveTopic, setActiveTopic, t]
|
||||
)
|
||||
|
||||
const onSwitchTopic = useCallback(
|
||||
async (topic: Topic) => {
|
||||
await modelGenerating()
|
||||
(topic: Topic) => {
|
||||
if (generating) {
|
||||
window.message.warning({ content: t('message.switch.disabled'), key: 'generating' })
|
||||
return
|
||||
}
|
||||
setActiveTopic(topic)
|
||||
},
|
||||
[setActiveTopic]
|
||||
[generating, setActiveTopic, t]
|
||||
)
|
||||
|
||||
const onClearMessages = useCallback(() => {
|
||||
@@ -177,7 +186,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
)
|
||||
|
||||
return (
|
||||
<Container right={topicPosition === 'right'} className="topics-tab">
|
||||
<Container right={topicPosition === 'right'}>
|
||||
<DragableList list={assistant.topics} onUpdate={updateTopics}>
|
||||
{(topic) => {
|
||||
const isActive = topic.id === activeTopic?.id
|
||||
|
||||
@@ -94,7 +94,7 @@ const HomeTabs: FC<Props> = ({ activeAssistant, activeTopic, setActiveAssistant,
|
||||
}, [position, tab, topicPosition])
|
||||
|
||||
return (
|
||||
<Container style={border} className="home-tabs">
|
||||
<Container style={border}>
|
||||
{showTab && (
|
||||
<Segmented
|
||||
value={tab}
|
||||
@@ -125,7 +125,7 @@ const HomeTabs: FC<Props> = ({ activeAssistant, activeTopic, setActiveAssistant,
|
||||
block
|
||||
/>
|
||||
)}
|
||||
<TabContent className="home-tabs-content">
|
||||
<TabContent>
|
||||
{tab === 'assistants' && (
|
||||
<Assistants
|
||||
activeAssistant={activeAssistant}
|
||||
|
||||
@@ -4,7 +4,7 @@ import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
|
||||
import { isLocalAi } from '@renderer/config/env'
|
||||
import { isVisionModel } from '@renderer/config/models'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { getProviderName } from '@renderer/services/ProviderService'
|
||||
import { getProviderByModel } from '@renderer/services/AssistantService'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { Button } from 'antd'
|
||||
import { FC } from 'react'
|
||||
@@ -31,14 +31,13 @@ const SelectModelButton: FC<Props> = ({ assistant }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const providerName = getProviderName(model?.provider)
|
||||
|
||||
return (
|
||||
<DropdownButton size="small" type="default" onClick={onSelectModel}>
|
||||
<ButtonContent>
|
||||
<ModelAvatar model={model} size={20} />
|
||||
<ModelName>
|
||||
{model ? model.name : t('button.select_model')} {providerName ? '| ' + providerName : ''}
|
||||
{model ? model.name : t('button.select_model')} |{' '}
|
||||
{t(`provider.${model?.provider}`, { defaultValue: getProviderByModel(model)?.name })}
|
||||
</ModelName>
|
||||
{isVisionModel(model) && <VisionIcon style={{ marginLeft: 0 }} />}
|
||||
</ButtonContent>
|
||||
|
||||
@@ -13,7 +13,6 @@ import TextEditPopup from '@renderer/components/Popups/TextEditPopup'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { useKnowledge } from '@renderer/hooks/useKnowledge'
|
||||
import FileManager from '@renderer/services/FileManager'
|
||||
import { getProviderName } from '@renderer/services/ProviderService'
|
||||
import { FileType, FileTypes, KnowledgeBase } from '@renderer/types'
|
||||
import { Alert, Button, Card, Divider, message, Tag, Typography, Upload } from 'antd'
|
||||
import { FC } from 'react'
|
||||
@@ -30,7 +29,7 @@ interface KnowledgeContentProps {
|
||||
selectedBase: KnowledgeBase
|
||||
}
|
||||
|
||||
const fileTypes = ['.pdf', '.docx', '.pptx', '.xlsx', '.txt', '.md']
|
||||
const fileTypes = ['.pdf', '.docx', '.pptx', '.xlsx', '.txt', '.md', '.mdx']
|
||||
|
||||
const FlexColumn = styled.div`
|
||||
display: flex;
|
||||
@@ -75,17 +74,11 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
addDirectory
|
||||
} = useKnowledge(selectedBase.id || '')
|
||||
|
||||
const providerName = getProviderName(base?.model.provider || '')
|
||||
const disabled = !base?.version || !providerName
|
||||
|
||||
if (!base) {
|
||||
return null
|
||||
}
|
||||
|
||||
const handleAddFile = () => {
|
||||
if (disabled) {
|
||||
return
|
||||
}
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.multiple = true
|
||||
@@ -98,10 +91,6 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
}
|
||||
|
||||
const handleDrop = async (files: File[]) => {
|
||||
if (disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (files) {
|
||||
const _files: FileType[] = files.map((file) => ({
|
||||
id: file.name,
|
||||
@@ -121,10 +110,6 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
}
|
||||
|
||||
const handleAddUrl = async () => {
|
||||
if (disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const url = await PromptPopup.show({
|
||||
title: t('knowledge_base.add_url'),
|
||||
message: '',
|
||||
@@ -150,10 +135,6 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
}
|
||||
|
||||
const handleAddSitemap = async () => {
|
||||
if (disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const url = await PromptPopup.show({
|
||||
title: t('knowledge_base.add_sitemap'),
|
||||
message: '',
|
||||
@@ -179,28 +160,16 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
}
|
||||
|
||||
const handleAddNote = async () => {
|
||||
if (disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const note = await TextEditPopup.show({ text: '', textareaProps: { rows: 20 } })
|
||||
note && addNote(note)
|
||||
}
|
||||
|
||||
const handleEditNote = async (note: any) => {
|
||||
if (disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const editedText = await TextEditPopup.show({ text: note.content as string, textareaProps: { rows: 20 } })
|
||||
editedText && updateNoteContent(note.id, editedText)
|
||||
}
|
||||
|
||||
const handleAddDirectory = async () => {
|
||||
if (disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const path = await window.api.file.selectFolder()
|
||||
console.log('[KnowledgeContent] Selected directory:', path)
|
||||
path && addDirectory(path)
|
||||
@@ -211,13 +180,10 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
{!base?.version && (
|
||||
<Alert message={t('knowledge_base.not_support')} type="error" style={{ marginBottom: 20 }} showIcon />
|
||||
)}
|
||||
{!providerName && (
|
||||
<Alert message={t('knowledge_base.no_provider')} type="error" style={{ marginBottom: 20 }} showIcon />
|
||||
)}
|
||||
<FileSection>
|
||||
<TitleWrapper>
|
||||
<Title level={5}>{t('files.title')}</Title>
|
||||
<Button icon={<PlusOutlined />} onClick={handleAddFile} disabled={disabled}>
|
||||
<Button icon={<PlusOutlined />} onClick={handleAddFile}>
|
||||
{t('knowledge_base.add_file')}
|
||||
</Button>
|
||||
</TitleWrapper>
|
||||
@@ -257,7 +223,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
<ContentSection>
|
||||
<TitleWrapper>
|
||||
<Title level={5}>{t('knowledge_base.directories')}</Title>
|
||||
<Button icon={<PlusOutlined />} onClick={handleAddDirectory} disabled={disabled}>
|
||||
<Button icon={<PlusOutlined />} onClick={handleAddDirectory}>
|
||||
{t('knowledge_base.add_directory')}
|
||||
</Button>
|
||||
</TitleWrapper>
|
||||
@@ -284,7 +250,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
<ContentSection>
|
||||
<TitleWrapper>
|
||||
<Title level={5}>{t('knowledge_base.urls')}</Title>
|
||||
<Button icon={<PlusOutlined />} onClick={handleAddUrl} disabled={disabled}>
|
||||
<Button icon={<PlusOutlined />} onClick={handleAddUrl}>
|
||||
{t('knowledge_base.add_url')}
|
||||
</Button>
|
||||
</TitleWrapper>
|
||||
@@ -311,7 +277,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
<ContentSection>
|
||||
<TitleWrapper>
|
||||
<Title level={5}>{t('knowledge_base.sitemaps')}</Title>
|
||||
<Button icon={<PlusOutlined />} onClick={handleAddSitemap} disabled={disabled}>
|
||||
<Button icon={<PlusOutlined />} onClick={handleAddSitemap}>
|
||||
{t('knowledge_base.add_sitemap')}
|
||||
</Button>
|
||||
</TitleWrapper>
|
||||
@@ -338,7 +304,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
<ContentSection>
|
||||
<TitleWrapper>
|
||||
<Title level={5}>{t('knowledge_base.notes')}</Title>
|
||||
<Button icon={<PlusOutlined />} onClick={handleAddNote} disabled={disabled}>
|
||||
<Button icon={<PlusOutlined />} onClick={handleAddNote}>
|
||||
{t('knowledge_base.add_note')}
|
||||
</Button>
|
||||
</TitleWrapper>
|
||||
@@ -366,15 +332,11 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
<label htmlFor="model-info">{t('knowledge_base.model_info')}</label>
|
||||
<Tag color="blue">{base.model.name}</Tag>
|
||||
<Tag color="cyan">{t('models.dimensions', { dimensions: base.dimensions || 0 })}</Tag>
|
||||
{providerName && <Tag color="purple">{providerName}</Tag>}
|
||||
<Tag color="purple">{base.model.provider}</Tag>
|
||||
</ModelInfo>
|
||||
|
||||
<IndexSection>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => KnowledgeSearchPopup.show({ base })}
|
||||
icon={<SearchOutlined />}
|
||||
disabled={disabled}>
|
||||
<Button type="primary" onClick={() => KnowledgeSearchPopup.show({ base })} icon={<SearchOutlined />}>
|
||||
{t('knowledge_base.search')}
|
||||
</Button>
|
||||
</IndexSection>
|
||||
|
||||
@@ -6,7 +6,7 @@ import ImageSize3_4 from '@renderer/assets/images/paintings/image-size-3-4.svg'
|
||||
import ImageSize9_16 from '@renderer/assets/images/paintings/image-size-9-16.svg'
|
||||
import ImageSize16_9 from '@renderer/assets/images/paintings/image-size-16-9.svg'
|
||||
import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar'
|
||||
import { HStack, VStack } from '@renderer/components/Layout'
|
||||
import { VStack } from '@renderer/components/Layout'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import TranslateButton from '@renderer/components/TranslateButton'
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
@@ -25,7 +25,7 @@ import { DEFAULT_PAINTING } from '@renderer/store/paintings'
|
||||
import { setGenerating } from '@renderer/store/runtime'
|
||||
import { FileType, Painting } from '@renderer/types'
|
||||
import { getErrorMessage } from '@renderer/utils'
|
||||
import { Button, Input, InputNumber, Radio, Select, Slider, Switch, Tooltip } from 'antd'
|
||||
import { Button, Input, InputNumber, Radio, Select, Slider, Tooltip } from 'antd'
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
import { FC, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -149,13 +149,8 @@ const PaintingsPage: FC = () => {
|
||||
dispatch(setGenerating(true))
|
||||
const AI = new AiProvider(provider)
|
||||
|
||||
if (!painting.model) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const urls = await AI.generateImage({
|
||||
model: painting.model,
|
||||
prompt,
|
||||
negativePrompt: painting.negativePrompt || '',
|
||||
imageSize: painting.imageSize || '1024x1024',
|
||||
@@ -163,8 +158,7 @@ const PaintingsPage: FC = () => {
|
||||
seed: painting.seed || undefined,
|
||||
numInferenceSteps: painting.steps || 25,
|
||||
guidanceScale: painting.guidanceScale || 4.5,
|
||||
signal: controller.signal,
|
||||
promptEnhancement: painting.promptEnhancement || false
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
if (urls.length > 0) {
|
||||
@@ -366,15 +360,13 @@ const PaintingsPage: FC = () => {
|
||||
<InfoIcon />
|
||||
</Tooltip>
|
||||
</SettingTitle>
|
||||
<SliderContainer>
|
||||
<Slider min={1} max={50} value={painting.steps} onChange={(v) => updatePaintingState({ steps: v })} />
|
||||
<StyledInputNumber
|
||||
min={1}
|
||||
max={50}
|
||||
value={painting.steps}
|
||||
onChange={(v) => updatePaintingState({ steps: (v as number) || 25 })}
|
||||
/>
|
||||
</SliderContainer>
|
||||
<Slider min={1} max={50} value={painting.steps} onChange={(v) => updatePaintingState({ steps: v })} />
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={50}
|
||||
value={painting.steps}
|
||||
onChange={(v) => updatePaintingState({ steps: v || 25 })}
|
||||
/>
|
||||
|
||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>
|
||||
{t('paintings.guidance_scale')}
|
||||
@@ -382,22 +374,21 @@ const PaintingsPage: FC = () => {
|
||||
<InfoIcon />
|
||||
</Tooltip>
|
||||
</SettingTitle>
|
||||
<SliderContainer>
|
||||
<Slider
|
||||
min={1}
|
||||
max={20}
|
||||
step={0.1}
|
||||
value={painting.guidanceScale}
|
||||
onChange={(v) => updatePaintingState({ guidanceScale: v })}
|
||||
/>
|
||||
<StyledInputNumber
|
||||
min={1}
|
||||
max={20}
|
||||
step={0.1}
|
||||
value={painting.guidanceScale}
|
||||
onChange={(v) => updatePaintingState({ guidanceScale: (v as number) || 4.5 })}
|
||||
/>
|
||||
</SliderContainer>
|
||||
<Slider
|
||||
min={1}
|
||||
max={20}
|
||||
step={0.1}
|
||||
value={painting.guidanceScale}
|
||||
onChange={(v) => updatePaintingState({ guidanceScale: v })}
|
||||
/>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={20}
|
||||
step={0.1}
|
||||
value={painting.guidanceScale}
|
||||
onChange={(v) => updatePaintingState({ guidanceScale: v || 4.5 })}
|
||||
/>
|
||||
|
||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>
|
||||
{t('paintings.negative_prompt')}
|
||||
<Tooltip title={t('paintings.negative_prompt_tip')}>
|
||||
@@ -409,18 +400,6 @@ const PaintingsPage: FC = () => {
|
||||
onChange={(e) => updatePaintingState({ negativePrompt: e.target.value })}
|
||||
rows={4}
|
||||
/>
|
||||
<SettingTitle style={{ marginBottom: 5, marginTop: 15 }}>
|
||||
{t('paintings.prompt_enhancement')}
|
||||
<Tooltip title={t('paintings.prompt_enhancement_tip')}>
|
||||
<InfoIcon />
|
||||
</Tooltip>
|
||||
</SettingTitle>
|
||||
<HStack>
|
||||
<Switch
|
||||
checked={painting.promptEnhancement}
|
||||
onChange={(checked) => updatePaintingState({ promptEnhancement: checked })}
|
||||
/>
|
||||
</HStack>
|
||||
</LeftContainer>
|
||||
<MainContainer>
|
||||
<Artboard
|
||||
@@ -568,18 +547,4 @@ const InfoIcon = styled(QuestionCircleOutlined)`
|
||||
}
|
||||
`
|
||||
|
||||
const SliderContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.ant-slider {
|
||||
flex: 1;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledInputNumber = styled(InputNumber)`
|
||||
width: 70px;
|
||||
`
|
||||
|
||||
export default PaintingsPage
|
||||
|
||||
@@ -4,9 +4,9 @@ import { HStack } from '@renderer/components/Layout'
|
||||
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
|
||||
import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||
import { SettingRow } from '@renderer/pages/settings'
|
||||
import { Assistant, AssistantSettingCustomParameters, AssistantSettings } from '@renderer/types'
|
||||
import { Assistant, AssistantSettings } from '@renderer/types'
|
||||
import { Button, Col, Divider, Input, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
|
||||
import { FC, useEffect, useRef, useState } from 'react'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@@ -25,14 +25,13 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
||||
const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true)
|
||||
const [defaultModel, setDefaultModel] = useState(assistant?.defaultModel)
|
||||
const [topP, setTopP] = useState(assistant?.settings?.topP ?? 1)
|
||||
const [customParameters, setCustomParameters] = useState<AssistantSettingCustomParameters[]>(
|
||||
assistant?.settings?.customParameters ?? []
|
||||
)
|
||||
|
||||
const customParametersRef = useRef(customParameters)
|
||||
|
||||
customParametersRef.current = customParameters
|
||||
|
||||
const [customParameters, setCustomParameters] = useState<
|
||||
Array<{
|
||||
name: string
|
||||
value: string | number | boolean
|
||||
type: 'string' | 'number' | 'boolean'
|
||||
}>
|
||||
>(assistant?.settings?.customParameters ?? [])
|
||||
const { t } = useTranslation()
|
||||
|
||||
const onTemperatureChange = (value) => {
|
||||
@@ -69,7 +68,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
||||
const onUpdateCustomParameter = (
|
||||
index: number,
|
||||
field: 'name' | 'value' | 'type',
|
||||
value: string | number | boolean | object
|
||||
value: string | number | boolean
|
||||
) => {
|
||||
const newParams = [...customParameters]
|
||||
if (field === 'type') {
|
||||
@@ -81,9 +80,6 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
||||
case 'boolean':
|
||||
defaultValue = false
|
||||
break
|
||||
case 'json':
|
||||
defaultValue = ''
|
||||
break
|
||||
default:
|
||||
defaultValue = ''
|
||||
}
|
||||
@@ -96,6 +92,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
||||
newParams[index] = { ...newParams[index], [field]: value }
|
||||
}
|
||||
setCustomParameters(newParams)
|
||||
updateAssistantSettings({ customParameters: newParams })
|
||||
}
|
||||
|
||||
const renderParameterValueInput = (param: (typeof customParameters)[0], index: number) => {
|
||||
@@ -116,20 +113,6 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
||||
onChange={(checked) => onUpdateCustomParameter(index, 'value', checked)}
|
||||
/>
|
||||
)
|
||||
case 'json':
|
||||
return (
|
||||
<Input
|
||||
value={typeof param.value === 'string' ? param.value : JSON.stringify(param.value, null, 2)}
|
||||
onChange={(e) => {
|
||||
try {
|
||||
const jsonValue = JSON.parse(e.target.value)
|
||||
onUpdateCustomParameter(index, 'value', jsonValue)
|
||||
} catch {
|
||||
onUpdateCustomParameter(index, 'value', e.target.value)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<Input
|
||||
@@ -176,10 +159,6 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return () => updateAssistantSettings({ customParameters: customParametersRef.current })
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Row align="middle" style={{ marginBottom: 10 }}>
|
||||
@@ -358,7 +337,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
||||
</Button>
|
||||
</SettingRow>
|
||||
{customParameters.map((param, index) => (
|
||||
<Row key={index} align="stretch" gutter={10} style={{ marginTop: 10 }}>
|
||||
<Row key={index} align="middle" gutter={10} style={{ marginTop: 10 }}>
|
||||
<Col span={6}>
|
||||
<Input
|
||||
placeholder={t('models.parameter_name')}
|
||||
@@ -374,11 +353,10 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
||||
<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.Option value="json">{t('models.parameter_type.json')}</Select.Option>
|
||||
</Select>
|
||||
</Col>
|
||||
<Col span={12}>{renderParameterValueInput(param, index)}</Col>
|
||||
<Col span={2} style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Col span={11}>{renderParameterValueInput(param, index)}</Col>
|
||||
<Col span={3}>
|
||||
<Button icon={<DeleteOutlined />} onClick={() => onDeleteCustomParameter(index)} danger />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FolderOpenOutlined, SaveOutlined, SyncOutlined } from '@ant-design/icons'
|
||||
import { FolderOpenOutlined, SaveOutlined } from '@ant-design/icons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { backupToWebdav, restoreFromWebdav, startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
@@ -12,8 +11,7 @@ import {
|
||||
setWebdavSyncInterval as _setWebdavSyncInterval,
|
||||
setWebdavUser as _setWebdavUser
|
||||
} from '@renderer/store/settings'
|
||||
import { Button, Input, Select } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import { Button, Input, Select, Switch } from 'antd'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
@@ -25,6 +23,7 @@ const WebDavSettings: FC = () => {
|
||||
webdavUser: webDAVUser,
|
||||
webdavPass: webDAVPass,
|
||||
webdavPath: webDAVPath,
|
||||
webdavAutoSync: webDAVAutoSync,
|
||||
webdavSyncInterval: webDAVSyncInterval
|
||||
} = useSettings()
|
||||
|
||||
@@ -33,6 +32,7 @@ const WebDavSettings: FC = () => {
|
||||
const [webdavPass, setWebdavPass] = useState<string | undefined>(webDAVPass)
|
||||
const [webdavPath, setWebdavPath] = useState<string | undefined>(webDAVPath)
|
||||
|
||||
const [autoSync, setAutoSync] = useState<boolean>(webDAVAutoSync)
|
||||
const [syncInterval, setSyncInterval] = useState<number>(webDAVSyncInterval)
|
||||
|
||||
const [backuping, setBackuping] = useState(false)
|
||||
@@ -42,8 +42,6 @@ const WebDavSettings: FC = () => {
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { webdavSync } = useRuntime()
|
||||
|
||||
// 把之前备份的文件定时上传到 webdav,首先先配置 webdav 的 host, port, user, pass, path
|
||||
|
||||
const onBackup = async () => {
|
||||
@@ -66,40 +64,18 @@ const WebDavSettings: FC = () => {
|
||||
setRestoring(false)
|
||||
}
|
||||
|
||||
const onSyncIntervalChange = (value: number) => {
|
||||
setSyncInterval(value)
|
||||
dispatch(_setWebdavSyncInterval(value))
|
||||
if (value === 0) {
|
||||
dispatch(setWebdavAutoSync(false))
|
||||
stopAutoSync()
|
||||
} else {
|
||||
dispatch(setWebdavAutoSync(true))
|
||||
const onToggleAutoSync = (checked: boolean) => {
|
||||
dispatch(setWebdavAutoSync(checked))
|
||||
if (checked) {
|
||||
startAutoSync()
|
||||
} else {
|
||||
stopAutoSync()
|
||||
}
|
||||
}
|
||||
|
||||
const renderSyncStatus = () => {
|
||||
if (!webdavHost) return null
|
||||
|
||||
if (!webdavSync.lastSyncTime && !webdavSync.syncing && !webdavSync.lastSyncError) {
|
||||
return <span style={{ color: 'var(--text-secondary)' }}>{t('settings.data.webdav.noSync')}</span>
|
||||
}
|
||||
|
||||
return (
|
||||
<HStack gap="5px" alignItems="center">
|
||||
{webdavSync.syncing && <SyncOutlined spin />}
|
||||
{webdavSync.lastSyncTime && (
|
||||
<span style={{ color: 'var(--text-secondary)' }}>
|
||||
{t('settings.data.webdav.lastSync')}: {dayjs(webdavSync.lastSyncTime).format('HH:mm:ss')}
|
||||
</span>
|
||||
)}
|
||||
{webdavSync.lastSyncError && (
|
||||
<span style={{ color: 'var(--error-color)' }}>
|
||||
{t('settings.data.webdav.syncError')}: {webdavSync.lastSyncError}
|
||||
</span>
|
||||
)}
|
||||
</HStack>
|
||||
)
|
||||
const onSyncIntervalChange = (value: number) => {
|
||||
setSyncInterval(value)
|
||||
dispatch(_setWebdavSyncInterval(value))
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -151,6 +127,32 @@ const WebDavSettings: FC = () => {
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.data.webdav.autoSync')}</SettingRowTitle>
|
||||
<HStack gap="10px" alignItems="center">
|
||||
<Switch
|
||||
checked={autoSync}
|
||||
onChange={(checked) => {
|
||||
setAutoSync(checked)
|
||||
onToggleAutoSync(checked)
|
||||
}}
|
||||
disabled={!webdavHost}
|
||||
/>
|
||||
<Select
|
||||
value={syncInterval || 5}
|
||||
onChange={onSyncIntervalChange}
|
||||
disabled={!webdavHost || !autoSync}
|
||||
style={{ width: 120 }}>
|
||||
<Select.Option value={1}>1 {t('settings.data.webdav.minutes')}</Select.Option>
|
||||
<Select.Option value={5}>5 {t('settings.data.webdav.minutes')}</Select.Option>
|
||||
<Select.Option value={15}>15 {t('settings.data.webdav.minutes')}</Select.Option>
|
||||
<Select.Option value={30}>30 {t('settings.data.webdav.minutes')}</Select.Option>
|
||||
<Select.Option value={60}>60 {t('settings.data.webdav.minutes')}</Select.Option>
|
||||
<Select.Option value={120}>120 {t('settings.data.webdav.minutes')}</Select.Option>
|
||||
</Select>
|
||||
</HStack>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle>
|
||||
<HStack gap="5px" justifyContent="space-between">
|
||||
@@ -163,28 +165,6 @@ const WebDavSettings: FC = () => {
|
||||
</Button>
|
||||
</HStack>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.data.webdav.autoSync')}</SettingRowTitle>
|
||||
<Select value={syncInterval} onChange={onSyncIntervalChange} disabled={!webdavHost} style={{ width: 120 }}>
|
||||
<Select.Option value={0}>{t('settings.data.webdav.autoSync.off')}</Select.Option>
|
||||
<Select.Option value={1}>1 {t('settings.data.webdav.minutes')}</Select.Option>
|
||||
<Select.Option value={5}>5 {t('settings.data.webdav.minutes')}</Select.Option>
|
||||
<Select.Option value={15}>15 {t('settings.data.webdav.minutes')}</Select.Option>
|
||||
<Select.Option value={30}>30 {t('settings.data.webdav.minutes')}</Select.Option>
|
||||
<Select.Option value={60}>60 {t('settings.data.webdav.minutes')}</Select.Option>
|
||||
<Select.Option value={120}>120 {t('settings.data.webdav.minutes')}</Select.Option>
|
||||
</Select>
|
||||
</SettingRow>
|
||||
{webdavSync && syncInterval > 0 && (
|
||||
<>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.data.webdav.syncStatus')}</SettingRowTitle>
|
||||
{renderSyncStatus()}
|
||||
</SettingRow>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -41,39 +41,22 @@ const PopupContainer: React.FC<Props> = ({ title, provider, resolve }) => {
|
||||
resolve({})
|
||||
}
|
||||
|
||||
const onAddModel = (values: FieldType) => {
|
||||
const id = values.id.trim()
|
||||
|
||||
if (find(models, { id })) {
|
||||
window.message.error('Model ID already exists')
|
||||
const onFinish: FormProps<FieldType>['onFinish'] = (values) => {
|
||||
if (find(models, { id: values.id })) {
|
||||
Modal.error({ title: 'Error', content: 'Model ID already exists' })
|
||||
return
|
||||
}
|
||||
|
||||
const model: Model = {
|
||||
id,
|
||||
provider: provider.id,
|
||||
name: values.name ? values.name : id.toUpperCase(),
|
||||
group: getDefaultGroupName(values.group || id)
|
||||
id: values.id,
|
||||
name: values.name ? values.name : values.id.toUpperCase(),
|
||||
group: getDefaultGroupName(values.group || values.id)
|
||||
}
|
||||
|
||||
addModel(model)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const onFinish: FormProps<FieldType>['onFinish'] = (values) => {
|
||||
const id = values.id.trim().replaceAll(',', ',')
|
||||
|
||||
if (id.includes(',')) {
|
||||
const ids = id.split(',')
|
||||
ids.forEach((id) => onAddModel({ id, name: id } as FieldType))
|
||||
resolve({})
|
||||
return
|
||||
}
|
||||
|
||||
if (onAddModel(values)) {
|
||||
resolve({})
|
||||
}
|
||||
resolve(model)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import BaseProvider from '@renderer/providers/BaseProvider'
|
||||
import ProviderFactory from '@renderer/providers/ProviderFactory'
|
||||
import { Assistant, GenerateImageParams, Message, Model, Provider, Suggestion } from '@renderer/types'
|
||||
import { Assistant, Message, Model, Provider, Suggestion } from '@renderer/types'
|
||||
import OpenAI from 'openai'
|
||||
|
||||
import { CompletionsParams } from '.'
|
||||
@@ -48,7 +48,16 @@ export default class AiProvider {
|
||||
return this.sdk.getApiKey()
|
||||
}
|
||||
|
||||
public async generateImage(params: GenerateImageParams): Promise<string[]> {
|
||||
public async generateImage(params: {
|
||||
prompt: string
|
||||
negativePrompt: string
|
||||
imageSize: string
|
||||
batchSize: number
|
||||
seed?: string
|
||||
numInferenceSteps: number
|
||||
guidanceScale: number
|
||||
signal?: AbortSignal
|
||||
}): Promise<string[]> {
|
||||
return this.sdk.generateImage(params)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { REFERENCE_PROMPT } from '@renderer/config/prompts'
|
||||
import { getOllamaKeepAliveTime } from '@renderer/hooks/useOllama'
|
||||
import { getKnowledgeReferences } from '@renderer/services/KnowledgeService'
|
||||
import store from '@renderer/store'
|
||||
import { Assistant, GenerateImageParams, Message, Model, Provider, Suggestion } from '@renderer/types'
|
||||
import { delay, isJSON } from '@renderer/utils'
|
||||
import { Assistant, Message, Model, Provider, Suggestion } from '@renderer/types'
|
||||
import { delay } from '@renderer/utils'
|
||||
import OpenAI from 'openai'
|
||||
|
||||
import { CompletionsParams } from '.'
|
||||
@@ -26,7 +26,16 @@ export default abstract class BaseProvider {
|
||||
abstract generateText({ prompt, content }: { prompt: string; content: string }): Promise<string>
|
||||
abstract check(): Promise<{ valid: boolean; error: Error | null }>
|
||||
abstract models(): Promise<OpenAI.Models.Model[]>
|
||||
abstract generateImage(params: GenerateImageParams): Promise<string[]>
|
||||
abstract generateImage(_params: {
|
||||
prompt: string
|
||||
negativePrompt: string
|
||||
imageSize: string
|
||||
batchSize: number
|
||||
seed?: string
|
||||
numInferenceSteps: number
|
||||
guidanceScale: number
|
||||
signal?: AbortSignal
|
||||
}): Promise<string[]>
|
||||
abstract getEmbeddingDimensions(model: Model): Promise<number>
|
||||
|
||||
public getBaseURL(): string {
|
||||
@@ -92,16 +101,13 @@ export default abstract class BaseProvider {
|
||||
|
||||
protected getCustomParameters(assistant: Assistant) {
|
||||
return (
|
||||
assistant?.settings?.customParameters?.reduce((acc, param) => {
|
||||
if (!param.name?.trim()) {
|
||||
return acc
|
||||
}
|
||||
if (param.type === 'json') {
|
||||
const value = param.value as string
|
||||
return { ...acc, [param.name]: isJSON(value) ? JSON.parse(value) : value }
|
||||
}
|
||||
return { ...acc, [param.name]: param.value }
|
||||
}, {}) || {}
|
||||
assistant?.settings?.customParameters?.reduce(
|
||||
(acc, param) => ({
|
||||
...acc,
|
||||
[param.name]: param.value
|
||||
}),
|
||||
{}
|
||||
) || {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { isEmbeddingModel, isSupportedModel, isVisionModel, isWebSearchModel } from '@renderer/config/models'
|
||||
import { isEmbeddingModel, isSupportedModel, isVisionModel } from '@renderer/config/models'
|
||||
import { getStoreSetting } from '@renderer/hooks/useSettings'
|
||||
import i18n from '@renderer/i18n'
|
||||
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService'
|
||||
import { EVENT_NAMES } from '@renderer/services/EventService'
|
||||
import { filterContextMessages } from '@renderer/services/MessagesService'
|
||||
import { Assistant, FileTypes, GenerateImageParams, Message, Model, Provider, Suggestion } from '@renderer/types'
|
||||
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 { removeQuotes } from '@renderer/utils'
|
||||
import { last, takeRight } from 'lodash'
|
||||
import OpenAI, { AzureOpenAI } from 'openai'
|
||||
import {
|
||||
ChatCompletionContentPart,
|
||||
ChatCompletionCreateParamsNonStreaming,
|
||||
ChatCompletionMessageParam
|
||||
ChatCompletionMessageParam,
|
||||
ChatCompletionTool
|
||||
} from 'openai/resources'
|
||||
|
||||
import { CompletionsParams } from '.'
|
||||
@@ -133,7 +136,6 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
}
|
||||
|
||||
const isOpenAIo1 = model.id.includes('o1-')
|
||||
const isSupportStreamOutput = streamOutput
|
||||
|
||||
let time_first_token_millsec = 0
|
||||
const start_time_millsec = new Date().getTime()
|
||||
@@ -148,13 +150,28 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
top_p: assistant?.settings?.topP,
|
||||
max_tokens: maxTokens,
|
||||
keep_alive: this.keepAliveTime,
|
||||
stream: isSupportStreamOutput,
|
||||
...(isWebSearchModel(model) ? { enable_enhancement: true } : {}),
|
||||
stream: streamOutput,
|
||||
tools: [DuckDuckGoLiteSearch as ChatCompletionTool],
|
||||
...this.getCustomParameters(assistant)
|
||||
})
|
||||
|
||||
if (!isSupportStreamOutput) {
|
||||
if (!streamOutput) {
|
||||
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({
|
||||
text: stream.choices[0].message?.content || '',
|
||||
usage: stream.usage,
|
||||
@@ -199,8 +216,7 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
model: model.id,
|
||||
messages: messages as ChatCompletionMessageParam[],
|
||||
stream: false,
|
||||
keep_alive: this.keepAliveTime,
|
||||
temperature: assistant?.settings?.temperature
|
||||
keep_alive: this.keepAliveTime
|
||||
})
|
||||
|
||||
return response.choices[0].message?.content || ''
|
||||
@@ -346,7 +362,6 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
}
|
||||
|
||||
public async generateImage({
|
||||
model,
|
||||
prompt,
|
||||
negativePrompt,
|
||||
imageSize,
|
||||
@@ -354,23 +369,30 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
seed,
|
||||
numInferenceSteps,
|
||||
guidanceScale,
|
||||
signal,
|
||||
promptEnhancement
|
||||
}: GenerateImageParams): Promise<string[]> {
|
||||
signal
|
||||
}: {
|
||||
prompt: string
|
||||
negativePrompt?: string
|
||||
imageSize: string
|
||||
batchSize: number
|
||||
seed?: string
|
||||
numInferenceSteps: number
|
||||
guidanceScale: number
|
||||
signal?: AbortSignal
|
||||
}): Promise<string[]> {
|
||||
const response = (await this.sdk.request({
|
||||
method: 'post',
|
||||
path: '/images/generations',
|
||||
signal,
|
||||
body: {
|
||||
model,
|
||||
model: 'stabilityai/stable-diffusion-3-5-large',
|
||||
prompt,
|
||||
negative_prompt: negativePrompt,
|
||||
image_size: imageSize,
|
||||
batch_size: batchSize,
|
||||
seed: seed ? parseInt(seed) : undefined,
|
||||
num_inference_steps: numInferenceSteps,
|
||||
guidance_scale: guidanceScale,
|
||||
prompt_enhancement: promptEnhancement
|
||||
guidance_scale: guidanceScale
|
||||
}
|
||||
})) as { data: Array<{ url: string }> }
|
||||
|
||||
|
||||
@@ -23,11 +23,6 @@ export function getDefaultTranslateAssistant(targetLanguage: string, text: strin
|
||||
const translateModel = getTranslateModel()
|
||||
const assistant: Assistant = getDefaultAssistant()
|
||||
assistant.model = translateModel
|
||||
|
||||
assistant.settings = {
|
||||
temperature: 0.7
|
||||
}
|
||||
|
||||
assistant.prompt = store
|
||||
.getState()
|
||||
.settings.translateModelPrompt.replace('{{target_language}}', targetLanguage)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import db from '@renderer/databases'
|
||||
import i18n from '@renderer/i18n'
|
||||
import store from '@renderer/store'
|
||||
import { setWebDAVSyncState } from '@renderer/store/runtime'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export async function backup() {
|
||||
@@ -64,9 +63,6 @@ export async function backupToWebdav({ showMessage = true }: { showMessage?: boo
|
||||
console.log('[Backup] Manual backup already in progress')
|
||||
return
|
||||
}
|
||||
|
||||
store.dispatch(setWebDAVSyncState({ syncing: true, lastSyncError: null }))
|
||||
|
||||
const { webdavHost, webdavUser, webdavPass, webdavPath } = store.getState().settings
|
||||
|
||||
const backupData = await getBackupData()
|
||||
@@ -80,19 +76,11 @@ export async function backupToWebdav({ showMessage = true }: { showMessage?: boo
|
||||
webdavPath
|
||||
})
|
||||
if (success) {
|
||||
store.dispatch(
|
||||
setWebDAVSyncState({
|
||||
lastSyncTime: Date.now(),
|
||||
lastSyncError: null
|
||||
})
|
||||
)
|
||||
showMessage && window.message.success({ content: i18n.t('message.backup.success'), key: 'backup' })
|
||||
} else {
|
||||
store.dispatch(setWebDAVSyncState({ lastSyncError: 'Backup failed' }))
|
||||
showMessage && window.message.error({ content: i18n.t('message.backup.failed'), key: 'backup' })
|
||||
}
|
||||
} catch (error: any) {
|
||||
store.dispatch(setWebDAVSyncState({ lastSyncError: error.message }))
|
||||
console.error('[backup] backupToWebdav: Error uploading file to WebDAV:', error)
|
||||
showMessage &&
|
||||
window.modal.error({
|
||||
@@ -100,7 +88,6 @@ export async function backupToWebdav({ showMessage = true }: { showMessage?: boo
|
||||
content: error.message
|
||||
})
|
||||
} finally {
|
||||
store.dispatch(setWebDAVSyncState({ syncing: false }))
|
||||
isManualBackupRunning = false
|
||||
}
|
||||
}
|
||||
@@ -138,9 +125,9 @@ export function startAutoSync() {
|
||||
return
|
||||
}
|
||||
|
||||
const { webdavAutoSync, webdavHost } = store.getState().settings
|
||||
const { webdavAutoSync, webdavHost, webdavSyncInterval } = store.getState().settings
|
||||
|
||||
if (!webdavAutoSync || !webdavHost) {
|
||||
if (!webdavAutoSync || !webdavHost || webdavSyncInterval <= 0) {
|
||||
console.log('[AutoSync] Invalid sync settings, auto sync disabled')
|
||||
return
|
||||
}
|
||||
@@ -157,16 +144,7 @@ export function startAutoSync() {
|
||||
syncTimeout = null
|
||||
}
|
||||
|
||||
const { webdavSyncInterval } = store.getState().settings
|
||||
|
||||
if (webdavSyncInterval <= 0) {
|
||||
console.log('[AutoSync] Invalid sync interval, auto sync disabled')
|
||||
stopAutoSync()
|
||||
return
|
||||
}
|
||||
|
||||
syncTimeout = setTimeout(performAutoBackup, webdavSyncInterval * 60 * 1000)
|
||||
|
||||
console.log(`[AutoSync] Next sync scheduled in ${webdavSyncInterval} minutes`)
|
||||
}
|
||||
|
||||
|
||||
@@ -57,29 +57,20 @@ class FileManager {
|
||||
return file
|
||||
}
|
||||
|
||||
static async deleteFile(id: string, force: boolean = false): Promise<void> {
|
||||
static async deleteFile(id: string): Promise<void> {
|
||||
const file = await this.getFile(id)
|
||||
|
||||
console.debug('[FileManager] Deleting file:', file)
|
||||
|
||||
if (!file) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!force) {
|
||||
if (file.count > 1) {
|
||||
await db.files.update(id, { ...file, count: file.count - 1 })
|
||||
return
|
||||
}
|
||||
if (file.count > 1) {
|
||||
await db.files.update(id, { ...file, count: file.count - 1 })
|
||||
return
|
||||
}
|
||||
|
||||
await db.files.delete(id)
|
||||
|
||||
try {
|
||||
await window.api.file.delete(id + file.ext)
|
||||
} catch (error) {
|
||||
console.error('[FileManager] Failed to delete file:', error)
|
||||
}
|
||||
await window.api.file.delete(id + file.ext)
|
||||
}
|
||||
|
||||
static async deleteFiles(files: FileType[]): Promise<void> {
|
||||
@@ -102,14 +93,6 @@ class FileManager {
|
||||
const filesPath = store.getState().runtime.filesPath
|
||||
return 'file://' + filesPath + '/' + file.name
|
||||
}
|
||||
|
||||
static async updateFile(file: FileType) {
|
||||
if (!file.origin_name.includes(file.ext)) {
|
||||
file.origin_name = file.origin_name + file.ext
|
||||
}
|
||||
|
||||
await db.files.update(file.id, file)
|
||||
}
|
||||
}
|
||||
|
||||
export default FileManager
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import i18n from '@renderer/i18n'
|
||||
import store from '@renderer/store'
|
||||
|
||||
export function getProviderName(id: string) {
|
||||
const provider = store.getState().llm.providers.find((p) => p.id === id)
|
||||
if (!provider) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (provider.isSystem) {
|
||||
return i18n.t(`provider.${provider.id}`, { defaultValue: provider.name })
|
||||
}
|
||||
|
||||
return provider?.name
|
||||
}
|
||||
@@ -10,12 +10,6 @@ export interface UpdateState {
|
||||
available: boolean
|
||||
}
|
||||
|
||||
export interface WebDAVSyncState {
|
||||
lastSyncTime: number | null
|
||||
syncing: boolean
|
||||
lastSyncError: string | null
|
||||
}
|
||||
|
||||
export interface RuntimeState {
|
||||
avatar: string
|
||||
generating: boolean
|
||||
@@ -23,7 +17,6 @@ export interface RuntimeState {
|
||||
searching: boolean
|
||||
filesPath: string
|
||||
update: UpdateState
|
||||
webdavSync: WebDAVSyncState
|
||||
}
|
||||
|
||||
const initialState: RuntimeState = {
|
||||
@@ -38,11 +31,6 @@ const initialState: RuntimeState = {
|
||||
downloading: false,
|
||||
downloadProgress: 0,
|
||||
available: false
|
||||
},
|
||||
webdavSync: {
|
||||
lastSyncTime: null,
|
||||
syncing: false,
|
||||
lastSyncError: null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,21 +55,11 @@ const runtimeSlice = createSlice({
|
||||
},
|
||||
setUpdateState: (state, action: PayloadAction<Partial<UpdateState>>) => {
|
||||
state.update = { ...state.update, ...action.payload }
|
||||
},
|
||||
setWebDAVSyncState: (state, action: PayloadAction<Partial<WebDAVSyncState>>) => {
|
||||
state.webdavSync = { ...state.webdavSync, ...action.payload }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const {
|
||||
setAvatar,
|
||||
setGenerating,
|
||||
setMinappShow,
|
||||
setSearching,
|
||||
setFilesPath,
|
||||
setUpdateState,
|
||||
setWebDAVSyncState
|
||||
} = runtimeSlice.actions
|
||||
export const { setAvatar, setGenerating, setMinappShow, setSearching, setFilesPath, setUpdateState } =
|
||||
runtimeSlice.actions
|
||||
|
||||
export default runtimeSlice.reducer
|
||||
|
||||
22
src/renderer/src/tools/DuckDuckGoLiteSearch/function.json
Normal file
22
src/renderer/src/tools/DuckDuckGoLiteSearch/function.json
Normal file
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/renderer/src/tools/DuckDuckGoLiteSearch/index.js
Normal file
23
src/renderer/src/tools/DuckDuckGoLiteSearch/index.js
Normal file
@@ -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()
|
||||
})
|
||||
@@ -21,12 +21,6 @@ export type AssistantMessage = {
|
||||
content: string
|
||||
}
|
||||
|
||||
export type AssistantSettingCustomParameters = {
|
||||
name: string
|
||||
value: string | number | boolean | object
|
||||
type: 'string' | 'number' | 'boolean' | 'json'
|
||||
}
|
||||
|
||||
export type AssistantSettings = {
|
||||
contextCount: number
|
||||
temperature: number
|
||||
@@ -36,7 +30,11 @@ export type AssistantSettings = {
|
||||
streamOutput: boolean
|
||||
hideMessages: boolean
|
||||
autoResetModel: boolean
|
||||
customParameters?: AssistantSettingCustomParameters[]
|
||||
customParameters?: {
|
||||
name: string
|
||||
value: string | number | boolean
|
||||
type: 'string' | 'number' | 'boolean'
|
||||
}[]
|
||||
}
|
||||
|
||||
export type Agent = Omit<Assistant, 'model'>
|
||||
@@ -114,7 +112,6 @@ export type Suggestion = {
|
||||
|
||||
export interface Painting {
|
||||
id: string
|
||||
model?: string
|
||||
urls: string[]
|
||||
files: FileType[]
|
||||
prompt?: string
|
||||
@@ -124,7 +121,7 @@ export interface Painting {
|
||||
seed?: string
|
||||
steps?: number
|
||||
guidanceScale?: number
|
||||
promptEnhancement?: boolean
|
||||
model?: string
|
||||
}
|
||||
|
||||
export type MinAppType = {
|
||||
@@ -227,16 +224,3 @@ export type KnowledgeBaseParams = {
|
||||
apiVersion?: string
|
||||
baseURL: string
|
||||
}
|
||||
|
||||
export type GenerateImageParams = {
|
||||
model: string
|
||||
prompt: string
|
||||
negativePrompt?: string
|
||||
imageSize: string
|
||||
batchSize: number
|
||||
seed?: string
|
||||
numInferenceSteps: number
|
||||
guidanceScale: number
|
||||
signal?: AbortSignal
|
||||
promptEnhancement?: boolean
|
||||
}
|
||||
|
||||
49
yarn.lock
49
yarn.lock
@@ -1577,7 +1577,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@llm-tools/embedjs-loader-markdown@npm:0.1.25":
|
||||
"@llm-tools/embedjs-loader-markdown@npm:^0.1.25":
|
||||
version: 0.1.25
|
||||
resolution: "@llm-tools/embedjs-loader-markdown@npm:0.1.25"
|
||||
dependencies:
|
||||
@@ -1592,21 +1592,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@llm-tools/embedjs-loader-markdown@patch:@llm-tools/embedjs-loader-markdown@npm%3A0.1.25#~/.yarn/patches/@llm-tools-embedjs-loader-markdown-npm-0.1.25-d1d536d640.patch":
|
||||
version: 0.1.25
|
||||
resolution: "@llm-tools/embedjs-loader-markdown@patch:@llm-tools/embedjs-loader-markdown@npm%3A0.1.25#~/.yarn/patches/@llm-tools-embedjs-loader-markdown-npm-0.1.25-d1d536d640.patch::version=0.1.25&hash=3a7d12"
|
||||
dependencies:
|
||||
"@llm-tools/embedjs-interfaces": "npm:0.1.25"
|
||||
"@llm-tools/embedjs-loader-web": "npm:0.1.25"
|
||||
debug: "npm:^4.4.0"
|
||||
md5: "npm:^2.3.0"
|
||||
micromark: "npm:^4.0.1"
|
||||
micromark-extension-gfm: "npm:^3.0.0"
|
||||
micromark-extension-mdx-jsx: "npm:^3.0.1"
|
||||
checksum: 10c0/8e91a1260f8c94ec516be13a5105055bf140b5c63a85fa3c7641cc8f6799a0410ddb6bce61db858e91712f7dbc2b333269eb7c3ce813c1d95416f49f4f4f31a5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@llm-tools/embedjs-loader-msoffice@npm:^0.1.25":
|
||||
version: 0.1.25
|
||||
resolution: "@llm-tools/embedjs-loader-msoffice@npm:0.1.25"
|
||||
@@ -2639,13 +2624,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/trusted-types@npm:^2.0.7":
|
||||
version: 2.0.7
|
||||
resolution: "@types/trusted-types@npm:2.0.7"
|
||||
checksum: 10c0/4c4855f10de7c6c135e0d32ce462419d8abbbc33713b31d294596c0cc34ae1fa6112a2f9da729c8f7a20707782b0d69da3b1f8df6645b0366d08825ca1522e0c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/unist@npm:*, @types/unist@npm:^3.0.0":
|
||||
version: 3.0.3
|
||||
resolution: "@types/unist@npm:3.0.3"
|
||||
@@ -2868,7 +2846,7 @@ __metadata:
|
||||
"@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-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": "npm:^0.1.25"
|
||||
"@llm-tools/embedjs-loader-markdown": "patch:@llm-tools/embedjs-loader-markdown@npm%3A0.1.25#~/.yarn/patches/@llm-tools-embedjs-loader-markdown-npm-0.1.25-d1d536d640.patch"
|
||||
"@llm-tools/embedjs-loader-markdown": "npm:^0.1.25"
|
||||
"@llm-tools/embedjs-loader-msoffice": "npm:^0.1.25"
|
||||
"@llm-tools/embedjs-loader-pdf": "npm:^0.1.25"
|
||||
"@llm-tools/embedjs-loader-sitemap": "npm:^0.1.25"
|
||||
@@ -2889,13 +2867,12 @@ __metadata:
|
||||
adm-zip: "npm:^0.5.16"
|
||||
antd: "npm:^5.22.5"
|
||||
apache-arrow: "npm:^18.1.0"
|
||||
axios: "npm:^1.7.3"
|
||||
axios: "npm:^1.7.9"
|
||||
browser-image-compression: "npm:^2.0.2"
|
||||
dayjs: "npm:^1.11.11"
|
||||
dexie: "npm:^4.0.8"
|
||||
dexie-react-hooks: "npm:^1.1.7"
|
||||
docx: "npm:^9.0.2"
|
||||
dompurify: "npm:^3.2.3"
|
||||
dotenv-cli: "npm:^7.4.2"
|
||||
electron: "npm:31.7.6"
|
||||
electron-builder: "npm:^24.13.3"
|
||||
@@ -3529,14 +3506,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axios@npm:^1.7.3":
|
||||
version: 1.7.7
|
||||
resolution: "axios@npm:1.7.7"
|
||||
"axios@npm:^1.7.9":
|
||||
version: 1.7.9
|
||||
resolution: "axios@npm:1.7.9"
|
||||
dependencies:
|
||||
follow-redirects: "npm:^1.15.6"
|
||||
form-data: "npm:^4.0.0"
|
||||
proxy-from-env: "npm:^1.1.0"
|
||||
checksum: 10c0/4499efc89e86b0b49ffddc018798de05fab26e3bf57913818266be73279a6418c3ce8f9e934c7d2d707ab8c095e837fc6c90608fb7715b94d357720b5f568af7
|
||||
checksum: 10c0/b7a41e24b59fee5f0f26c1fc844b45b17442832eb3a0fb42dd4f1430eb4abc571fe168e67913e8a1d91c993232bd1d1ab03e20e4d1fee8c6147649b576fc1b0b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4990,18 +4967,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dompurify@npm:^3.2.3":
|
||||
version: 3.2.3
|
||||
resolution: "dompurify@npm:3.2.3"
|
||||
dependencies:
|
||||
"@types/trusted-types": "npm:^2.0.7"
|
||||
dependenciesMeta:
|
||||
"@types/trusted-types":
|
||||
optional: true
|
||||
checksum: 10c0/0ce5cb89b76f396d800751bcb48e0d137792891d350ccc049f1bc9a5eca7332cc69030c25007ff4962e0824a5696904d4d74264df9277b5ad955642dfb6f313f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"domutils@npm:^3.0.1":
|
||||
version: 3.1.0
|
||||
resolution: "domutils@npm:3.1.0"
|
||||
|
||||
Reference in New Issue
Block a user