diff --git a/app/components/chat.tsx b/app/components/chat.tsx index bcd0e605df2..2594f9fd7e2 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -37,6 +37,7 @@ import AutoIcon from "../icons/auto.svg"; import BottomIcon from "../icons/bottom.svg"; import StopIcon from "../icons/pause.svg"; import RobotIcon from "../icons/robot.svg"; +import AddIcon from "../icons/add.svg"; import { ChatMessage, @@ -97,6 +98,9 @@ import { ExportMessageModal } from "./exporter"; import { getClientConfig } from "../config/client"; import { useAllModels } from "../utils/hooks"; import { MultimodalContent } from "../client/api"; +import { InputRange } from "./input-range"; +import { config } from "process"; +import { listen } from "@tauri-apps/api/event"; const Markdown = dynamic(async () => (await import("./markdown")).Markdown, { loading: () => , @@ -555,6 +559,15 @@ export function ChatActions(props: { icon={} /> + } + onClick={() => { + chatStore.newSession(chatStore.currentSession().mask); + navigate(Path.Chat); + }} + /> + {showModelSelector && ( { + const unlisten = listen("activate_input_field", () => { + inputRef.current?.focus(); + }); + + return () => { + unlisten.then((f) => f()); + }; + }, []); + // chat commands shortcuts const chatCommands = useChatCommand({ new: () => chatStore.newSession(), @@ -1100,11 +1123,13 @@ function _Chat() { }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - + const handlePaste = useCallback( async (event: React.ClipboardEvent) => { const currentModel = chatStore.currentSession().mask.modelConfig.model; - if(!isVisionModel(currentModel)){return;} + if (!isVisionModel(currentModel)) { + return; + } const items = (event.clipboardData || window.clipboardData).items; for (const item of items) { if (item.kind === "file" && item.type.startsWith("image/")) { diff --git a/app/components/home.tsx b/app/components/home.tsx index 8386ba144b9..cc2c6b442e4 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -29,6 +29,7 @@ import { AuthPage } from "./auth"; import { getClientConfig } from "../config/client"; import { ClientApi } from "../client/api"; import { useAccessStore } from "../store"; +import { invoke } from "@tauri-apps/api/tauri"; export function Loading(props: { noLogo?: boolean }) { return ( @@ -183,6 +184,12 @@ export function useLoadData() { })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + + useEffect(() => { + (async () => { + await invoke("update_shortcut", { shortcut: config.shortcutQuickChat }); + })(); + }, []); } export function Home() { diff --git a/app/components/settings.tsx b/app/components/settings.tsx index ba6d9345e24..6dc0ac466d7 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -71,6 +71,7 @@ import { useSyncStore } from "../store/sync"; import { nanoid } from "nanoid"; import { useMaskStore } from "../store/mask"; import { ProviderType } from "../utils/cloud"; +import { invoke } from "@tauri-apps/api/tauri"; function EditPromptModal(props: { id: string; onClose: () => void }) { const promptStore = usePromptStore(); @@ -557,6 +558,13 @@ function SyncItems() { ); } +async function useUpdateShortcut(newShortcut: string) { + useEffect(() => { + (async () => { + await invoke("update_shortcut", { shortcut: newShortcut }); + })(); + }, []); +} export function Settings() { const navigate = useNavigate(); @@ -650,6 +658,8 @@ export function Settings() { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + useUpdateShortcut(config.shortcutQuickChat); + const clientConfig = useMemo(() => getClientConfig(), []); const showAccessCode = enabledAccessControl && !clientConfig?.isApp; @@ -827,6 +837,22 @@ export function Settings() { } > + + + { + updateConfig( + (config) => + (config.shortcutQuickChat = e.currentTarget.value), + ); + invoke("update_shortcut", { + shortcut: config.shortcutQuickChat, + }); + }} + > + diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 5d0c284283e..3a6ae5315c4 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -62,6 +62,7 @@ const cn = { Prompt: "快捷指令", Masks: "所有面具", Clear: "清除聊天", + NewChat: "另起聊天", Settings: "对话设置", UploadImage: "上传图片", }, @@ -176,6 +177,7 @@ const cn = { Title: "预览气泡", SubTitle: "在预览气泡中预览 Markdown 内容", }, + ShortcutQuickChat: "快捷键-快速对话", AutoGenerateTitle: { Title: "自动生成标题", SubTitle: "根据对话内容生成合适的标题", diff --git a/app/locales/en.ts b/app/locales/en.ts index 79a91d7ccd5..4e320891437 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -64,6 +64,7 @@ const en: LocaleType = { Prompt: "Prompts", Masks: "Masks", Clear: "Clear Context", + NewChat: "New Chat", Settings: "Settings", UploadImage: "Upload Images", }, @@ -178,6 +179,7 @@ const en: LocaleType = { Title: "Send Preview Bubble", SubTitle: "Preview markdown in bubble", }, + ShortcutQuickChat: "Shortcut-QuickChat", AutoGenerateTitle: { Title: "Auto Generate Title", SubTitle: "Generate a suitable title based on the conversation content", diff --git a/app/store/config.ts b/app/store/config.ts index 6f2f558a042..ce565f50117 100644 --- a/app/store/config.ts +++ b/app/store/config.ts @@ -34,6 +34,7 @@ export const DEFAULT_CONFIG = { theme: Theme.Auto as Theme, tightBorder: !!getClientConfig()?.isApp, sendPreviewBubble: true, + shortcutQuickChat: "Alt+B", enableAutoGenerateTitle: true, sidebarWidth: DEFAULT_SIDEBAR_WIDTH, diff --git a/app/utils.ts b/app/utils.ts index 8b755afeac1..bd1da2febe7 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -295,6 +295,7 @@ export function isVisionModel(model: string) { return ( // model.startsWith("gpt-4-vision") || // model.startsWith("gemini-pro-vision") || + model.startsWith("claude-3-opus-20240229") || model.includes("vision") ); } diff --git a/package.json b/package.json index c92e0a08459..518d1091e74 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@hello-pangea/dnd": "^16.5.0", "@next/third-parties": "^14.1.0", "@svgr/webpack": "^6.5.1", + "@tauri-apps/api": "^1.5.3", "@vercel/analytics": "^0.1.11", "@vercel/speed-insights": "^1.0.2", "emoji-picker-react": "^4.5.15", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e0892590223..d059dc6dedf 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -17,7 +17,7 @@ tauri-build = { version = "1.5.1", features = [] } [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -tauri = { version = "1.5.4", features = [ +tauri = { version = "1.5.4", features = [ "system-tray", "global-shortcut-all", "notification-all", "fs-all", "clipboard-all", diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index ed3ec32f37b..20dff2179b0 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,9 +1,34 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +use tauri::{Manager,CustomMenuItem, SystemTray, SystemTrayEvent, SystemTrayMenu}; +mod shortcuts; fn main() { - tauri::Builder::default() - .plugin(tauri_plugin_window_state::Builder::default().build()) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + let quit = CustomMenuItem::new("quit".to_string(), "Quit"); + let tray_menu = SystemTrayMenu::new() + .add_item(quit); + + let system_tray = SystemTray::new() + .with_menu(tray_menu); + + tauri::Builder::default() + .plugin(tauri_plugin_window_state::Builder::default().build()) + .invoke_handler(tauri::generate_handler![shortcuts::update_shortcut]) + .system_tray(system_tray) + .on_system_tray_event(|app, event| match event { + SystemTrayEvent::LeftClick {..} => { + let window = app.get_window("main").unwrap(); + window.show().unwrap(); + window.set_focus().unwrap(); + } + SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() { + "quit" => { + std::process::exit(0); + } + _ => {} + }, + _ => {} + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); } diff --git a/src-tauri/src/shortcuts.rs b/src-tauri/src/shortcuts.rs new file mode 100644 index 00000000000..5497f79dfa1 --- /dev/null +++ b/src-tauri/src/shortcuts.rs @@ -0,0 +1,22 @@ +use tauri::{Manager,AppHandle, GlobalShortcutManager}; + +#[tauri::command] +pub fn update_shortcut(shortcut: String, handle: AppHandle) { + handle + .global_shortcut_manager() + .unregister_all() + .unwrap(); + + let window = handle.get_window("main").unwrap(); + match handle + .global_shortcut_manager() + .register(&shortcut, move || { + println!("Shortcut triggered successfully"); + window.unminimize().unwrap(); + window.set_focus().unwrap(); + window.emit("activate_input_field", {}).unwrap(); + }) { + Ok(_) => println!("Shortcut registered successfully"), + Err(err) => eprintln!("Failed to register shortcut: {}", err), + } +} \ No newline at end of file diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f03efb0fe49..648a949e959 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -14,6 +14,9 @@ "tauri": { "allowlist": { "all": false, + "globalShortcut": { + "all":true + }, "shell": { "all": false, "open": true @@ -52,6 +55,10 @@ "all": true } }, + "systemTray": { + "iconPath": "../public/favicon-16x16.png", + "iconAsTemplate": true + }, "bundle": { "active": true, "category": "DeveloperTool",