diff --git a/src/main/ipc.ts b/src/main/ipc.ts index fa53f082b0..559e05e71e 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -255,6 +255,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ipcMain.handle('miniwindow:hide', () => windowService.hideMiniWindow()) ipcMain.handle('miniwindow:close', () => windowService.closeMiniWindow()) ipcMain.handle('miniwindow:toggle', () => windowService.toggleMiniWindow()) + ipcMain.handle('miniwindow:set-pin', (_, isPinned) => windowService.setPinMiniWindow(isPinned)) // aes ipcMain.handle('aes:encrypt', (_, text: string, secretKey: string, iv: string) => encrypt(text, secretKey, iv)) diff --git a/src/main/services/NutstoreService.ts b/src/main/services/NutstoreService.ts index 3b04496aba..20e05beb76 100644 --- a/src/main/services/NutstoreService.ts +++ b/src/main/services/NutstoreService.ts @@ -112,10 +112,10 @@ function convertToFileStat(serverBase: string, item: WebDAVResponse['multistatus const props = item.propstat.prop const isDir = !isNil(props.resourcetype?.collection) const href = decodeURIComponent(item.href) - const filename = serverBase === '/' ? href : path.join('/', href.replace(serverBase, '')) + const filename = serverBase === '/' ? href : path.posix.join('/', href.replace(serverBase, '')) return { - filename, + filename: filename.endsWith('/') ? filename.slice(0, -1) : filename, basename: path.basename(filename), lastmod: props.getlastmodified || '', size: props.getcontentlength ? parseInt(props.getcontentlength, 10) : 0, diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index f72c5b1b1c..0ab3b0233b 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -15,6 +15,7 @@ export class WindowService { private static instance: WindowService | null = null private mainWindow: BrowserWindow | null = null private miniWindow: BrowserWindow | null = null + private isPinnedMiniWindow: boolean = false private wasFullScreen: boolean = false //hacky-fix: store the focused status of mainWindow before miniWindow shows //to restore the focus status when miniWindow hides @@ -378,8 +379,12 @@ export class WindowService { public createMiniWindow(isPreload: boolean = false): BrowserWindow { this.miniWindow = new BrowserWindow({ - width: 500, - height: 520, + width: 550, + height: 400, + minWidth: 350, + minHeight: 380, + maxWidth: 1024, + maxHeight: 768, show: false, autoHideMenuBar: true, transparent: isMac, @@ -388,7 +393,7 @@ export class WindowService { center: true, frame: false, alwaysOnTop: true, - resizable: false, + resizable: true, useContentSize: true, ...(isMac ? { type: 'panel' } : {}), skipTaskbar: true, @@ -419,7 +424,9 @@ export class WindowService { }) this.miniWindow.on('blur', () => { - this.hideMiniWindow() + if (!this.isPinnedMiniWindow) { + this.hideMiniWindow() + } }) this.miniWindow.on('closed', () => { @@ -503,6 +510,10 @@ export class WindowService { this.showMiniWindow() } + public setPinMiniWindow(isPinned) { + this.isPinnedMiniWindow = isPinned + } + public showSelectionMenu(bounds: { x: number; y: number }) { if (this.selectionMenuWindow && !this.selectionMenuWindow.isDestroyed()) { this.selectionMenuWindow.setPosition(bounds.x, bounds.y) diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 1e2fb51a25..aee54e55a9 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -137,6 +137,7 @@ declare global { hide: () => Promise close: () => Promise toggle: () => Promise + setPin: (isPinned: boolean) => Promise } aes: { encrypt: (text: string, secretKey: string, iv: string) => Promise<{ iv: string; encryptedData: string }> diff --git a/src/preload/index.ts b/src/preload/index.ts index 50aa5a2d22..fe223e9f1a 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -112,7 +112,8 @@ const api = { show: () => ipcRenderer.invoke('miniwindow:show'), hide: () => ipcRenderer.invoke('miniwindow:hide'), close: () => ipcRenderer.invoke('miniwindow:close'), - toggle: () => ipcRenderer.invoke('miniwindow:toggle') + toggle: () => ipcRenderer.invoke('miniwindow:toggle'), + setPin: (isPinned: boolean) => ipcRenderer.invoke('miniwindow:set-pin', isPinned) }, aes: { encrypt: (text: string, secretKey: string, iv: string) => ipcRenderer.invoke('aes:encrypt', text, secretKey, iv), diff --git a/src/renderer/src/config/models.ts b/src/renderer/src/config/models.ts index ac20258ddf..1d405cc7ea 100644 --- a/src/renderer/src/config/models.ts +++ b/src/renderer/src/config/models.ts @@ -1231,7 +1231,140 @@ export const SYSTEM_MODELS: Record = { group: 'Step 1' } ], - doubao: [], + doubao: [ + { + id: 'doubao-1-5-vision-pro-32k-250115', + provider: 'doubao', + name: 'doubao-1.5-vision-pro', + group: 'Doubao-1.5-vision-pro' + }, + { + id: 'doubao-1-5-pro-32k-250115', + provider: 'doubao', + name: 'doubao-1.5-pro-32k', + group: 'Doubao-1.5-pro' + }, + { + id: 'doubao-1-5-pro-32k-character-250228', + provider: 'doubao', + name: 'doubao-1.5-pro-32k-character', + group: 'Doubao-1.5-pro' + }, + { + id: 'doubao-1-5-pro-256k-250115', + provider: 'doubao', + name: 'Doubao-1.5-pro-256k', + group: 'Doubao-1.5-pro' + }, + { + id: 'deepseek-r1-250120', + provider: 'doubao', + name: 'DeepSeek-R1', + group: 'DeepSeek' + }, + { + id: 'deepseek-r1-distill-qwen-32b-250120', + provider: 'doubao', + name: 'DeepSeek-R1-Distill-Qwen-32B', + group: 'DeepSeek' + }, + { + id: 'deepseek-r1-distill-qwen-7b-250120', + provider: 'doubao', + name: 'DeepSeek-R1-Distill-Qwen-7B', + group: 'DeepSeek' + }, + { + id: 'deepseek-v3-250324', + provider: 'doubao', + name: 'DeepSeek-V3', + group: 'DeepSeek' + }, + { + id: 'deepseek-v3-250324', + provider: 'doubao', + name: 'DeepSeek-V3', + group: 'DeepSeek' + }, + { + id: 'doubao-pro-32k-241215', + provider: 'doubao', + name: 'Doubao-pro-32k', + group: 'Doubao-pro' + }, + { + id: 'doubao-pro-32k-functioncall-241028', + provider: 'doubao', + name: 'Doubao-pro-32k-functioncall-241028', + group: 'Doubao-pro' + }, + { + id: 'doubao-pro-32k-character-241215', + provider: 'doubao', + name: 'Doubao-pro-32k-character-241215', + group: 'Doubao-pro' + }, + { + id: 'doubao-pro-256k-241115', + provider: 'doubao', + name: 'Doubao-pro-256k', + group: 'Doubao-pro' + }, + { + id: 'doubao-lite-4k-character-240828', + provider: 'doubao', + name: 'Doubao-lite-4k-character-240828', + group: 'Doubao-lite' + }, + { + id: 'doubao-lite-32k-240828', + provider: 'doubao', + name: 'Doubao-lite-32k', + group: 'Doubao-lite' + }, + { + id: 'doubao-lite-32k-character-241015', + provider: 'doubao', + name: 'Doubao-lite-32k-character-241015', + group: 'Doubao-lite' + }, + { + id: 'doubao-lite-128k-240828', + provider: 'doubao', + name: 'Doubao-lite-128k', + group: 'Doubao-lite' + }, + { + id: 'doubao-1-5-lite-32k-250115', + provider: 'doubao', + name: 'Doubao-1.5-lite-32k', + group: 'Doubao-lite' + }, + { + id: 'doubao-embedding-large-text-240915', + provider: 'doubao', + name: 'Doubao-embedding-large', + group: 'Doubao-embedding' + }, + { + id: 'doubao-embedding-text-240715', + provider: 'doubao', + name: 'Doubao-embedding', + group: 'Doubao-embedding' + }, + { + id: 'doubao-embedding-vision-241215', + provider: 'doubao', + name: 'Doubao-embedding-vision', + group: 'Doubao-embedding' + }, + { + id: 'doubao-vision-lite-32k-241015', + provider: 'doubao', + name: 'Doubao-vision-lite-32k', + group: 'Doubao-vision-lite-32k' + } + ], minimax: [ { id: 'abab6.5s-chat', diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index a9f9f90681..7060a8c31d 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -586,15 +586,19 @@ }, "footer": { "copy_last_message": "Press C to copy", - "esc": "Press ESC {{action}}", - "esc_back": "back", - "esc_close": "close the window" + "backspace_clear": "Backspace to clear", + "esc": "ESC to {{action}}", + "esc_back": "return", + "esc_close": "close" }, "input": { "placeholder": { "empty": "Ask {{model}} for help...", "title": "What do you want to do with this text?" } + }, + "tooltip": { + "pin": "Keep Window on Top" } }, "models": { diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index c07b448d5c..4c38e6e91b 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -588,13 +588,17 @@ "copy_last_message": "C キーを押してコピー", "esc": "ESC キーを押して{{action}}", "esc_back": "戻る", - "esc_close": "ウィンドウを閉じる" + "esc_close": "ウィンドウを閉じる", + "backspace_clear": "バックスペースを押してクリアします" }, "input": { "placeholder": { "empty": "{{model}} に質問してください...", "title": "下のテキストに対して何をしますか?" } + }, + "tooltip": { + "pin": "上部ウィンドウ" } }, "models": { diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 81716e0e67..1a75879789 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -588,13 +588,17 @@ "copy_last_message": "Нажмите C для копирования", "esc": "Нажмите ESC {{action}}", "esc_back": "возвращения", - "esc_close": "закрытия окна" + "esc_close": "закрытия окна", + "backspace_clear": "Нажмите Backspace, чтобы очистить" }, "input": { "placeholder": { "empty": "Задайте вопрос {{model}}...", "title": "Что вы хотите сделать с этим текстом?" } + }, + "tooltip": { + "pin": "Верхнее окно" } }, "models": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 9a5c432a3d..e0a3f4b0da 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -586,15 +586,19 @@ }, "footer": { "copy_last_message": "按 C 键复制", + "backspace_clear": "按 Backspace 清空", "esc": "按 ESC {{action}}", "esc_back": "返回", - "esc_close": "关闭窗口" + "esc_close": "关闭" }, "input": { "placeholder": { "empty": "询问 {{model}} 获取帮助...", "title": "你想对下方文字做什么" } + }, + "tooltip": { + "pin": "窗口置顶" } }, "models": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 1ac2ac1b92..3673405ba7 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -588,13 +588,17 @@ "copy_last_message": "按 C 鍵複製", "esc": "按 ESC {{action}}", "esc_back": "返回", - "esc_close": "關閉視窗" + "esc_close": "關閉視窗", + "backspace_clear": "按 Backspace 清空" }, "input": { "placeholder": { "empty": "詢問 {{model}} 取得幫助...", "title": "你想對下方文字做什麼" } + }, + "tooltip": { + "pin": "窗口置頂" } }, "models": { diff --git a/src/renderer/src/pages/home/Messages/MessageGroup.tsx b/src/renderer/src/pages/home/Messages/MessageGroup.tsx index e43a7aabeb..9f39cb9bbb 100644 --- a/src/renderer/src/pages/home/Messages/MessageGroup.tsx +++ b/src/renderer/src/pages/home/Messages/MessageGroup.tsx @@ -262,7 +262,6 @@ const GridContainer = styled.div<{ $count: number; $layout: MultiModelMessageSty width: 100%; display: grid; gap: ${({ $layout }) => ($layout === 'horizontal' ? '16px' : '0')}; - overflow-y: auto; grid-template-columns: repeat( ${({ $layout, $count }) => (['fold', 'vertical'].includes($layout) ? 1 : $count)}, minmax(550px, 1fr) @@ -286,7 +285,13 @@ const GridContainer = styled.div<{ $count: number; $layout: MultiModelMessageSty grid-template-rows: auto; gap: 16px; `} - overflow-y: visible; + ${({ $layout }) => { + return $layout === 'horizontal' + ? css` + overflow-y: auto; + ` + : 'overflow-y: visible;' + }} ` interface MessageWrapperProps { @@ -325,17 +330,20 @@ const MessageWrapper = styled(Scrollbar)` }} ${({ $layout, $isInPopover, $isGrouped }) => { + // 如果布局是grid,并且是组消息,则设置最大高度和溢出行为(卡片不可滚动,点击展开后可滚动) + // 如果布局是horizontal,则设置溢出行为(卡片可滚动) + // 如果布局是fold、vertical,高度不限制,与正常消息流布局一致,则设置卡片不可滚动(visible) return $layout === 'grid' && $isGrouped ? css` max-height: ${$isInPopover ? '50vh' : '300px'}; - overflow-y: ${$isInPopover ? 'visible' : 'hidden'}; + overflow-y: ${$isInPopover ? 'auto' : 'hidden'}; border: 0.5px solid ${$isInPopover ? 'transparent' : 'var(--color-border)'}; padding: 10px; border-radius: 6px; background-color: var(--color-background); ` : css` - overflow-y: visible; + overflow-y: ${$layout === 'horizontal' ? 'auto' : 'visible'}; border-radius: 6px; ` }} diff --git a/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx b/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx index 4398919c6e..72e41c3fd1 100644 --- a/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx @@ -252,8 +252,10 @@ const NutstoreSettings: FC = () => { placeholder={t('settings.data.nutstore.path.placeholder')} style={{ width: 250 }} value={nutstorePath} - onChange={(e) => setStoragePath(e.target.value)} - onBlur={() => dispatch(setNutstorePath(storagePath || ''))} + onChange={(e) => { + setStoragePath(e.target.value) + dispatch(setNutstorePath(e.target.value)) + }} />