mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-24 07:19:23 +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-global-shortcut": "~2.0.0",
|
||||||
"@tauri-apps/plugin-http": "~2.0.2",
|
"@tauri-apps/plugin-http": "~2.0.2",
|
||||||
"@tauri-apps/plugin-os": "^2.2.0",
|
"@tauri-apps/plugin-os": "^2.2.0",
|
||||||
|
"@tauri-apps/plugin-process": "^2.2.0",
|
||||||
"@tauri-apps/plugin-shell": "^2.2.0",
|
"@tauri-apps/plugin-shell": "^2.2.0",
|
||||||
"@tauri-apps/plugin-updater": "^2.5.1",
|
"@tauri-apps/plugin-updater": "^2.5.1",
|
||||||
"@tauri-apps/plugin-websocket": "~2.3.0",
|
"@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':
|
'@tauri-apps/plugin-os':
|
||||||
specifier: ^2.2.0
|
specifier: ^2.2.0
|
||||||
version: 2.2.0
|
version: 2.2.0
|
||||||
|
'@tauri-apps/plugin-process':
|
||||||
|
specifier: ^2.2.0
|
||||||
|
version: 2.2.0
|
||||||
'@tauri-apps/plugin-shell':
|
'@tauri-apps/plugin-shell':
|
||||||
specifier: ^2.2.0
|
specifier: ^2.2.0
|
||||||
version: 2.2.0
|
version: 2.2.0
|
||||||
@@ -1098,6 +1101,9 @@ packages:
|
|||||||
'@tauri-apps/plugin-os@2.2.0':
|
'@tauri-apps/plugin-os@2.2.0':
|
||||||
resolution: {integrity: sha512-HszbCdbisMlu5QhCNAN8YIWyz2v33abAWha6+uvV2CKX8P5VSct/y+kEe22JeyqrxCnWlQ3DRx7s49Byg7/0EA==}
|
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':
|
'@tauri-apps/plugin-shell@2.2.0':
|
||||||
resolution: {integrity: sha512-iC3Ic1hLmasoboG7BO+7p+AriSoqAwKrIk+Hpk+S/bjTQdXqbl2GbdclghI4gM32X0bls7xHzIFqhRdrlvJeaA==}
|
resolution: {integrity: sha512-iC3Ic1hLmasoboG7BO+7p+AriSoqAwKrIk+Hpk+S/bjTQdXqbl2GbdclghI4gM32X0bls7xHzIFqhRdrlvJeaA==}
|
||||||
|
|
||||||
@@ -4138,6 +4144,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 2.3.0
|
'@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':
|
'@tauri-apps/plugin-shell@2.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 2.3.0
|
'@tauri-apps/api': 2.3.0
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
"fs-pro:default",
|
"fs-pro:default",
|
||||||
"macos-permissions:default",
|
"macos-permissions:default",
|
||||||
"screenshots: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_dialog::init())
|
||||||
.plugin(tauri_plugin_fs_pro::init())
|
.plugin(tauri_plugin_fs_pro::init())
|
||||||
.plugin(tauri_plugin_macos_permissions::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
|
// Conditional compilation for macOS
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -97,7 +98,9 @@ pub fn run() {
|
|||||||
shortcut::unregister_shortcut,
|
shortcut::unregister_shortcut,
|
||||||
shortcut::get_current_shortcut,
|
shortcut::get_current_shortcut,
|
||||||
change_autostart,
|
change_autostart,
|
||||||
|
show_coco,
|
||||||
hide_coco,
|
hide_coco,
|
||||||
|
show_settings,
|
||||||
server::servers::get_server_token,
|
server::servers::get_server_token,
|
||||||
server::servers::add_coco_server,
|
server::servers::add_coco_server,
|
||||||
server::servers::remove_coco_server,
|
server::servers::remove_coco_server,
|
||||||
@@ -143,7 +146,7 @@ pub fn run() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
shortcut::enable_shortcut(&app);
|
shortcut::enable_shortcut(&app);
|
||||||
enable_tray(app);
|
// enable_tray(app);
|
||||||
enable_autostart(app);
|
enable_autostart(app);
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -239,6 +242,11 @@ async fn init_app_search_source<R: Runtime>(app_handle: &AppHandle<R>) -> Result
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn show_coco(app_handle: AppHandle) {
|
||||||
|
handle_open_coco(&app_handle);
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn hide_coco(app: tauri::AppHandle) {
|
fn hide_coco(app: tauri::AppHandle) {
|
||||||
if let Some(window) = app.get_window(MAIN_WINDOW_LABEL) {
|
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(())
|
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 [shortcut, setShortcut] = useState<Shortcut>([]);
|
||||||
|
|
||||||
|
const setShowCocoShortcuts = useAppStore((state) => {
|
||||||
|
return state.setShowCocoShortcuts;
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setShowCocoShortcuts(shortcut);
|
||||||
|
}, [shortcut]);
|
||||||
|
|
||||||
async function getCurrentShortcut() {
|
async function getCurrentShortcut() {
|
||||||
try {
|
try {
|
||||||
const res: any = await invoke("get_current_shortcut");
|
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",
|
"enable_server": "Enable Server",
|
||||||
"disable_server": "Disable Server"
|
"disable_server": "Disable Server"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"showCoco": "Show Coco",
|
||||||
|
"settings": "Settings...",
|
||||||
|
"quitCoco": "Quit Coco"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,5 +201,10 @@
|
|||||||
},
|
},
|
||||||
"enable_server": "启用服务",
|
"enable_server": "启用服务",
|
||||||
"disable_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 { useAppStore } from "@/stores/appStore";
|
||||||
import { useAuthStore } from "@/stores/authStore";
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
import { isWin } from "@/utils/platform";
|
import { isWin } from "@/utils/platform";
|
||||||
import { useMount } from "ahooks";
|
|
||||||
|
|
||||||
export default function DesktopApp() {
|
export default function DesktopApp() {
|
||||||
const initializeListeners = useAppStore((state) => state.initializeListeners);
|
const initializeListeners = useAppStore((state) => state.initializeListeners);
|
||||||
@@ -20,9 +19,9 @@ export default function DesktopApp() {
|
|||||||
|
|
||||||
const isPinned = useAppStore((state) => state.isPinned);
|
const isPinned = useAppStore((state) => state.isPinned);
|
||||||
|
|
||||||
useMount(() => {
|
useEffect(() => {
|
||||||
invoke("get_app_search_source");
|
invoke("get_app_search_source");
|
||||||
});
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeListeners();
|
initializeListeners();
|
||||||
|
|||||||
@@ -2,25 +2,28 @@ import { useState, useEffect } from "react";
|
|||||||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react";
|
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react";
|
||||||
import { Settings, Puzzle, Settings2, Info, Server } from "lucide-react";
|
import { Settings, Puzzle, Settings2, Info, Server } from "lucide-react";
|
||||||
import { useTranslation } from "react-i18next";
|
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 SettingsPanel from "@/components/Settings/SettingsPanel";
|
||||||
import GeneralSettings from "@/components/Settings/GeneralSettings";
|
import GeneralSettings from "@/components/Settings/GeneralSettings";
|
||||||
import AboutView from "@/components/Settings/AboutView";
|
import AboutView from "@/components/Settings/AboutView";
|
||||||
import Cloud from "@/components/Cloud/Cloud.tsx";
|
import Cloud from "@/components/Cloud/Cloud.tsx";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
|
import { useTray } from "@/hooks/useTray";
|
||||||
|
|
||||||
const tabIndexMap: { [key: string]: number } = {
|
const tabIndexMap: { [key: string]: number } = {
|
||||||
'general': 0,
|
general: 0,
|
||||||
'extensions': 1,
|
extensions: 1,
|
||||||
'connect': 2,
|
connect: 2,
|
||||||
'advanced': 3,
|
advanced: 3,
|
||||||
'about': 4
|
about: 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
function SettingsPage() {
|
function SettingsPage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useTray();
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ name: t("settings.tabs.general"), icon: Settings },
|
{ name: t("settings.tabs.general"), icon: Settings },
|
||||||
{ name: t("settings.tabs.extensions"), icon: Puzzle },
|
{ name: t("settings.tabs.extensions"), icon: Puzzle },
|
||||||
@@ -41,7 +44,7 @@ function SettingsPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unlisten.then(fn => fn());
|
unlisten.then((fn) => fn());
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
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 {
|
export interface IServer {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -19,7 +19,7 @@ export interface IServer {
|
|||||||
available?: boolean;
|
available?: boolean;
|
||||||
health?: {
|
health?: {
|
||||||
status: string;
|
status: string;
|
||||||
},
|
};
|
||||||
assistantCount?: number;
|
assistantCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,23 +28,26 @@ export type IAppStore = {
|
|||||||
setShowTooltip: (showTooltip: boolean) => void;
|
setShowTooltip: (showTooltip: boolean) => void;
|
||||||
|
|
||||||
error: string;
|
error: string;
|
||||||
setError: (message: any) => void,
|
setError: (message: any) => void;
|
||||||
|
|
||||||
ssoRequestID: string;
|
ssoRequestID: string;
|
||||||
setSSORequestID: (ssoRequestID: string) => void,
|
setSSORequestID: (ssoRequestID: string) => void;
|
||||||
|
|
||||||
// ssoServerID: string;
|
// ssoServerID: string;
|
||||||
// setSSOServerID: (ssoServerID: string) => void,
|
// setSSOServerID: (ssoServerID: string) => void,
|
||||||
|
|
||||||
endpoint: AppEndpoint,
|
endpoint: AppEndpoint;
|
||||||
endpoint_http: string,
|
endpoint_http: string;
|
||||||
endpoint_websocket: string,
|
endpoint_websocket: string;
|
||||||
setEndpoint: (endpoint: AppEndpoint) => void,
|
setEndpoint: (endpoint: AppEndpoint) => void;
|
||||||
language: string;
|
language: string;
|
||||||
setLanguage: (language: string) => void;
|
setLanguage: (language: string) => void;
|
||||||
isPinned: boolean,
|
isPinned: boolean;
|
||||||
setIsPinned: (isPinned: boolean) => void,
|
setIsPinned: (isPinned: boolean) => void;
|
||||||
initializeListeners: () => void;
|
initializeListeners: () => void;
|
||||||
|
|
||||||
|
showCocoShortcuts: string[];
|
||||||
|
setShowCocoShortcuts: (showCocoShortcuts: string[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAppStore = create<IAppStore>()(
|
export const useAppStore = create<IAppStore>()(
|
||||||
@@ -66,7 +69,7 @@ export const useAppStore = create<IAppStore>()(
|
|||||||
|
|
||||||
const withoutProtocol = endpoint.split("//")[1];
|
const withoutProtocol = endpoint.split("//")[1];
|
||||||
|
|
||||||
const endpoint_websocket = endpoint?.includes('https')
|
const endpoint_websocket = endpoint?.includes("https")
|
||||||
? `wss://${withoutProtocol}/ws`
|
? `wss://${withoutProtocol}/ws`
|
||||||
: `ws://${withoutProtocol}/ws`;
|
: `ws://${withoutProtocol}/ws`;
|
||||||
|
|
||||||
@@ -79,7 +82,7 @@ export const useAppStore = create<IAppStore>()(
|
|||||||
await emit(ENDPOINT_CHANGE_EVENT, {
|
await emit(ENDPOINT_CHANGE_EVENT, {
|
||||||
endpoint,
|
endpoint,
|
||||||
endpoint_http,
|
endpoint_http,
|
||||||
endpoint_websocket
|
endpoint_websocket,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
language: "en",
|
language: "en",
|
||||||
@@ -92,6 +95,12 @@ export const useAppStore = create<IAppStore>()(
|
|||||||
set({ endpoint, endpoint_http, endpoint_websocket });
|
set({ endpoint, endpoint_http, endpoint_websocket });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
showCocoShortcuts: [],
|
||||||
|
setShowCocoShortcuts: (showCocoShortcuts: string[]) => {
|
||||||
|
console.log("set showCocoShortcuts", showCocoShortcuts);
|
||||||
|
|
||||||
|
return set({ showCocoShortcuts });
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "app-store",
|
name: "app-store",
|
||||||
|
|||||||
Reference in New Issue
Block a user