mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 11:37:47 +01:00
feat: tray internationalization and shortcut display (#259)
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
"@tauri-apps/plugin-global-shortcut": "~2.0.0",
|
||||
"@tauri-apps/plugin-http": "~2.0.2",
|
||||
"@tauri-apps/plugin-os": "^2.2.0",
|
||||
"@tauri-apps/plugin-process": "^2.2.0",
|
||||
"@tauri-apps/plugin-shell": "^2.2.0",
|
||||
"@tauri-apps/plugin-updater": "^2.5.1",
|
||||
"@tauri-apps/plugin-websocket": "~2.3.0",
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -32,6 +32,9 @@ importers:
|
||||
'@tauri-apps/plugin-os':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
'@tauri-apps/plugin-process':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
'@tauri-apps/plugin-shell':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
@@ -1098,6 +1101,9 @@ packages:
|
||||
'@tauri-apps/plugin-os@2.2.0':
|
||||
resolution: {integrity: sha512-HszbCdbisMlu5QhCNAN8YIWyz2v33abAWha6+uvV2CKX8P5VSct/y+kEe22JeyqrxCnWlQ3DRx7s49Byg7/0EA==}
|
||||
|
||||
'@tauri-apps/plugin-process@2.2.0':
|
||||
resolution: {integrity: sha512-uypN2Crmyop9z+KRJr3zl71OyVFgTuvHFjsJ0UxxQ/J5212jVa5w4nPEYjIewcn8bUEXacRebwE6F7owgrbhSw==}
|
||||
|
||||
'@tauri-apps/plugin-shell@2.2.0':
|
||||
resolution: {integrity: sha512-iC3Ic1hLmasoboG7BO+7p+AriSoqAwKrIk+Hpk+S/bjTQdXqbl2GbdclghI4gM32X0bls7xHzIFqhRdrlvJeaA==}
|
||||
|
||||
@@ -4138,6 +4144,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.3.0
|
||||
|
||||
'@tauri-apps/plugin-process@2.2.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.3.0
|
||||
|
||||
'@tauri-apps/plugin-shell@2.2.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.3.0
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"fs-pro:default",
|
||||
"macos-permissions:default",
|
||||
"screenshots:default",
|
||||
"core:window:allow-set-theme"
|
||||
"core:window:allow-set-theme",
|
||||
"process:default"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -82,7 +82,8 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs_pro::init())
|
||||
.plugin(tauri_plugin_macos_permissions::init())
|
||||
.plugin(tauri_plugin_screenshots::init());
|
||||
.plugin(tauri_plugin_screenshots::init())
|
||||
.plugin(tauri_plugin_process::init());
|
||||
|
||||
// Conditional compilation for macOS
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -97,7 +98,9 @@ pub fn run() {
|
||||
shortcut::unregister_shortcut,
|
||||
shortcut::get_current_shortcut,
|
||||
change_autostart,
|
||||
show_coco,
|
||||
hide_coco,
|
||||
show_settings,
|
||||
server::servers::get_server_token,
|
||||
server::servers::add_coco_server,
|
||||
server::servers::remove_coco_server,
|
||||
@@ -143,7 +146,7 @@ pub fn run() {
|
||||
});
|
||||
|
||||
shortcut::enable_shortcut(&app);
|
||||
enable_tray(app);
|
||||
// enable_tray(app);
|
||||
enable_autostart(app);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -239,6 +242,11 @@ async fn init_app_search_source<R: Runtime>(app_handle: &AppHandle<R>) -> Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn show_coco(app_handle: AppHandle) {
|
||||
handle_open_coco(&app_handle);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn hide_coco(app: tauri::AppHandle) {
|
||||
if let Some(window) = app.get_window(MAIN_WINDOW_LABEL) {
|
||||
@@ -475,3 +483,8 @@ async fn get_app_search_source<R: Runtime>(app_handle: AppHandle<R>) -> Result<(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn show_settings(app_handle: AppHandle) {
|
||||
open_settings(&app_handle);
|
||||
}
|
||||
|
||||
@@ -113,6 +113,14 @@ export default function GeneralSettings() {
|
||||
|
||||
const [shortcut, setShortcut] = useState<Shortcut>([]);
|
||||
|
||||
const setShowCocoShortcuts = useAppStore((state) => {
|
||||
return state.setShowCocoShortcuts;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setShowCocoShortcuts(shortcut);
|
||||
}, [shortcut]);
|
||||
|
||||
async function getCurrentShortcut() {
|
||||
try {
|
||||
const res: any = await invoke("get_current_shortcut");
|
||||
|
||||
88
src/hooks/useTray.ts
Normal file
88
src/hooks/useTray.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TrayIcon, type TrayIconOptions } from "@tauri-apps/api/tray";
|
||||
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
|
||||
import { isMac } from "@/utils/platform";
|
||||
import { resolveResource } from "@tauri-apps/api/path";
|
||||
import { useUpdateEffect } from "ahooks";
|
||||
import { exit } from "@tauri-apps/plugin-process";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useAppStore } from "@/stores/appStore";
|
||||
|
||||
const TRAY_ID = "COCO_TRAY";
|
||||
|
||||
export const useTray = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const showCocoShortcuts = useAppStore((state) => state.showCocoShortcuts);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
if (showCocoShortcuts.length === 0) return;
|
||||
|
||||
updateTrayMenu();
|
||||
}, [i18n.language, showCocoShortcuts]);
|
||||
|
||||
const getTrayById = () => {
|
||||
return TrayIcon.getById(TRAY_ID);
|
||||
};
|
||||
|
||||
const createTrayIcon = async () => {
|
||||
const tray = await getTrayById();
|
||||
|
||||
if (tray) return;
|
||||
|
||||
const menu = await getTrayMenu();
|
||||
|
||||
const iconPath = isMac ? "assets/tray-mac.ico" : "assets/tray.ico";
|
||||
const icon = await resolveResource(iconPath);
|
||||
|
||||
const options: TrayIconOptions = {
|
||||
menu,
|
||||
icon,
|
||||
id: TRAY_ID,
|
||||
iconAsTemplate: true,
|
||||
};
|
||||
|
||||
return TrayIcon.new(options);
|
||||
};
|
||||
|
||||
const getTrayMenu = async () => {
|
||||
const items = await Promise.all([
|
||||
MenuItem.new({
|
||||
text: t("tray.showCoco"),
|
||||
accelerator: showCocoShortcuts.join("+"),
|
||||
action: () => {
|
||||
invoke("show_coco");
|
||||
},
|
||||
}),
|
||||
PredefinedMenuItem.new({ item: "Separator" }),
|
||||
MenuItem.new({
|
||||
text: t("tray.settings"),
|
||||
// accelerator: "CommandOrControl+,",
|
||||
action: () => {
|
||||
invoke("show_settings");
|
||||
},
|
||||
}),
|
||||
PredefinedMenuItem.new({ item: "Separator" }),
|
||||
MenuItem.new({
|
||||
text: t("tray.quitCoco"),
|
||||
accelerator: "CommandOrControl+Q",
|
||||
action: () => {
|
||||
exit(0);
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
return Menu.new({ items });
|
||||
};
|
||||
|
||||
const updateTrayMenu = async () => {
|
||||
const tray = await getTrayById();
|
||||
|
||||
if (!tray) {
|
||||
return createTrayIcon();
|
||||
}
|
||||
|
||||
const menu = await getTrayMenu();
|
||||
|
||||
tray.setMenu(menu);
|
||||
};
|
||||
};
|
||||
@@ -201,5 +201,10 @@
|
||||
},
|
||||
"enable_server": "Enable Server",
|
||||
"disable_server": "Disable Server"
|
||||
},
|
||||
"tray": {
|
||||
"showCoco": "Show Coco",
|
||||
"settings": "Settings...",
|
||||
"quitCoco": "Quit Coco"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,5 +201,10 @@
|
||||
},
|
||||
"enable_server": "启用服务",
|
||||
"disable_server": "禁用服务"
|
||||
},
|
||||
"tray": {
|
||||
"showCoco": "显示 Coco",
|
||||
"settings": "偏好设置",
|
||||
"quitCoco": "退出 Coco"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import ChatAI, { ChatAIRef } from "@/components/Assistant/Chat";
|
||||
import { useAppStore } from "@/stores/appStore";
|
||||
import { useAuthStore } from "@/stores/authStore";
|
||||
import { isWin } from "@/utils/platform";
|
||||
import { useMount } from "ahooks";
|
||||
|
||||
export default function DesktopApp() {
|
||||
const initializeListeners = useAppStore((state) => state.initializeListeners);
|
||||
@@ -20,9 +19,9 @@ export default function DesktopApp() {
|
||||
|
||||
const isPinned = useAppStore((state) => state.isPinned);
|
||||
|
||||
useMount(() => {
|
||||
useEffect(() => {
|
||||
invoke("get_app_search_source");
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
initializeListeners();
|
||||
|
||||
@@ -2,25 +2,28 @@ import { useState, useEffect } from "react";
|
||||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react";
|
||||
import { Settings, Puzzle, Settings2, Info, Server } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
import SettingsPanel from "@/components/Settings/SettingsPanel";
|
||||
import GeneralSettings from "@/components/Settings/GeneralSettings";
|
||||
import AboutView from "@/components/Settings/AboutView";
|
||||
import Cloud from "@/components/Cloud/Cloud.tsx";
|
||||
import Footer from "@/components/Footer";
|
||||
import { useTray } from "@/hooks/useTray";
|
||||
|
||||
const tabIndexMap: { [key: string]: number } = {
|
||||
'general': 0,
|
||||
'extensions': 1,
|
||||
'connect': 2,
|
||||
'advanced': 3,
|
||||
'about': 4
|
||||
general: 0,
|
||||
extensions: 1,
|
||||
connect: 2,
|
||||
advanced: 3,
|
||||
about: 4,
|
||||
};
|
||||
|
||||
function SettingsPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useTray();
|
||||
|
||||
const tabs = [
|
||||
{ name: t("settings.tabs.general"), icon: Settings },
|
||||
{ name: t("settings.tabs.extensions"), icon: Puzzle },
|
||||
@@ -41,7 +44,7 @@ function SettingsPage() {
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten.then(fn => fn());
|
||||
unlisten.then((fn) => fn());
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { listen, emit } from '@tauri-apps/api/event';
|
||||
import { listen, emit } from "@tauri-apps/api/event";
|
||||
|
||||
import { AppEndpoint } from "@/utils/tauri"
|
||||
import { AppEndpoint } from "@/utils/tauri";
|
||||
|
||||
const ENDPOINT_CHANGE_EVENT = 'endpoint-changed';
|
||||
const ENDPOINT_CHANGE_EVENT = "endpoint-changed";
|
||||
|
||||
export interface IServer {
|
||||
id: string;
|
||||
@@ -19,7 +19,7 @@ export interface IServer {
|
||||
available?: boolean;
|
||||
health?: {
|
||||
status: string;
|
||||
},
|
||||
};
|
||||
assistantCount?: number;
|
||||
}
|
||||
|
||||
@@ -28,23 +28,26 @@ export type IAppStore = {
|
||||
setShowTooltip: (showTooltip: boolean) => void;
|
||||
|
||||
error: string;
|
||||
setError: (message: any) => void,
|
||||
setError: (message: any) => void;
|
||||
|
||||
ssoRequestID: string;
|
||||
setSSORequestID: (ssoRequestID: string) => void,
|
||||
setSSORequestID: (ssoRequestID: string) => void;
|
||||
|
||||
// ssoServerID: string;
|
||||
// setSSOServerID: (ssoServerID: string) => void,
|
||||
|
||||
endpoint: AppEndpoint,
|
||||
endpoint_http: string,
|
||||
endpoint_websocket: string,
|
||||
setEndpoint: (endpoint: AppEndpoint) => void,
|
||||
endpoint: AppEndpoint;
|
||||
endpoint_http: string;
|
||||
endpoint_websocket: string;
|
||||
setEndpoint: (endpoint: AppEndpoint) => void;
|
||||
language: string;
|
||||
setLanguage: (language: string) => void;
|
||||
isPinned: boolean,
|
||||
setIsPinned: (isPinned: boolean) => void,
|
||||
isPinned: boolean;
|
||||
setIsPinned: (isPinned: boolean) => void;
|
||||
initializeListeners: () => void;
|
||||
|
||||
showCocoShortcuts: string[];
|
||||
setShowCocoShortcuts: (showCocoShortcuts: string[]) => void;
|
||||
};
|
||||
|
||||
export const useAppStore = create<IAppStore>()(
|
||||
@@ -66,7 +69,7 @@ export const useAppStore = create<IAppStore>()(
|
||||
|
||||
const withoutProtocol = endpoint.split("//")[1];
|
||||
|
||||
const endpoint_websocket = endpoint?.includes('https')
|
||||
const endpoint_websocket = endpoint?.includes("https")
|
||||
? `wss://${withoutProtocol}/ws`
|
||||
: `ws://${withoutProtocol}/ws`;
|
||||
|
||||
@@ -79,7 +82,7 @@ export const useAppStore = create<IAppStore>()(
|
||||
await emit(ENDPOINT_CHANGE_EVENT, {
|
||||
endpoint,
|
||||
endpoint_http,
|
||||
endpoint_websocket
|
||||
endpoint_websocket,
|
||||
});
|
||||
},
|
||||
language: "en",
|
||||
@@ -92,6 +95,12 @@ export const useAppStore = create<IAppStore>()(
|
||||
set({ endpoint, endpoint_http, endpoint_websocket });
|
||||
});
|
||||
},
|
||||
showCocoShortcuts: [],
|
||||
setShowCocoShortcuts: (showCocoShortcuts: string[]) => {
|
||||
console.log("set showCocoShortcuts", showCocoShortcuts);
|
||||
|
||||
return set({ showCocoShortcuts });
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "app-store",
|
||||
|
||||
Reference in New Issue
Block a user