66 Check ,
77 Server ,
88} from "lucide-react" ;
9- import { useState , useEffect , useCallback } from "react" ;
9+ import { useState , useEffect , useCallback , useRef } from "react" ;
1010import {
1111 Menu ,
1212 MenuButton ,
@@ -30,6 +30,10 @@ import type { Chat } from "./types";
3030import { useConnectStore } from "@/stores/connectStore" ;
3131import platformAdapter from "@/utils/platformAdapter" ;
3232import { list_coco_servers } from "@/commands" ;
33+ import VisibleKey from "../Common/VisibleKey" ;
34+ import { useShortcutsStore } from "@/stores/shortcutsStore" ;
35+ import { useBoolean } from "ahooks" ;
36+ import clsx from "clsx" ;
3337
3438interface ChatHeaderProps {
3539 onCreateNewChat : ( ) => void ;
@@ -68,6 +72,18 @@ export function ChatHeader({
6872 const setCurrentService = useConnectStore ( ( state ) => state . setCurrentService ) ;
6973
7074 const isTauri = useAppStore ( ( state ) => state . isTauri ) ;
75+ const historicalRecords = useShortcutsStore ( ( state ) => {
76+ return state . historicalRecords ;
77+ } ) ;
78+ const newSession = useShortcutsStore ( ( state ) => {
79+ return state . newSession ;
80+ } ) ;
81+ const fixedWindow = useShortcutsStore ( ( state ) => {
82+ return state . fixedWindow ;
83+ } ) ;
84+ const serviceList = useShortcutsStore ( ( state ) => state . serviceList ) ;
85+ const external = useShortcutsStore ( ( state ) => state . external ) ;
86+ const serverListButtonRef = useRef < HTMLButtonElement > ( null ) ;
7187
7288 const fetchServers = useCallback (
7389 async ( resetSelection : boolean ) => {
@@ -163,9 +179,14 @@ export function ChatHeader({
163179 e . stopPropagation ( ) ;
164180 setIsSidebarOpen ( ) ;
165181 } }
166- className = "p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
182+ className = "inline-flex size-[34px] p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
167183 >
168- < HistoryIcon />
184+ < VisibleKey
185+ shortcut = { historicalRecords }
186+ onKeypress = { setIsSidebarOpen }
187+ >
188+ < HistoryIcon />
189+ </ VisibleKey >
169190 </ button >
170191 ) }
171192
@@ -207,7 +228,9 @@ export function ChatHeader({
207228 onClick = { onCreateNewChat }
208229 className = "p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
209230 >
210- < MessageSquarePlus className = "h-4 w-4" />
231+ < VisibleKey shortcut = { newSession } onKeypress = { onCreateNewChat } >
232+ < MessageSquarePlus className = "h-4 w-4" />
233+ </ VisibleKey >
211234 </ button >
212235 ) : null }
213236 </ div >
@@ -220,117 +243,137 @@ export function ChatHeader({
220243 </ h2 >
221244 </ div >
222245
223- { isTauri ? < div className = "flex items-center gap-2" >
224- < button
225- onClick = { togglePin }
226- className = { `${ isPinned ? "text-blue-500" : "" } ` }
227- >
228- { isPinned ? < PinIcon /> : < PinOffIcon /> }
229- </ button >
246+ { isTauri ? (
247+ < div className = "flex items-center gap-2" >
248+ < button
249+ onClick = { togglePin }
250+ className = { clsx ( "inline-flex" , {
251+ "text-blue-500" : isPinned ,
252+ } ) }
253+ >
254+ < VisibleKey shortcut = { fixedWindow } onKeypress = { togglePin } >
255+ { isPinned ? < PinIcon /> : < PinOffIcon /> }
256+ </ VisibleKey >
257+ </ button >
230258
231- < Popover className = "relative" >
232- < PopoverButton className = "flex items-center" >
233- < ServerIcon />
234- </ PopoverButton >
259+ < Popover className = "relative" >
260+ < PopoverButton
261+ ref = { serverListButtonRef }
262+ className = "flex items-center"
263+ >
264+ < VisibleKey
265+ shortcut = { serviceList }
266+ onKeypress = { ( ) => {
267+ serverListButtonRef . current ?. click ( ) ;
268+ } }
269+ >
270+ < ServerIcon />
271+ </ VisibleKey >
272+ </ PopoverButton >
235273
236- < PopoverPanel className = "absolute right-0 z-10 mt-2 min-w-[240px] bg-white dark:bg-[#202126] rounded-lg shadow-lg border border-gray-200 dark:border-gray-700" >
237- < div className = "p-3" >
238- < div className = "flex items-center justify-between mb-3 whitespace-nowrap" >
239- < h3 className = "text-sm font-medium text-gray-900 dark:text-gray-100" >
240- Servers
241- </ h3 >
242- < div className = "flex items-center gap-2" >
243- < button
244- onClick = { openSettings }
245- className = "p-1 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-500 dark:text-gray-400"
246- >
247- < Settings className = "h-4 w-4 text-[#0287FF]" />
248- </ button >
249- < button
250- onClick = { async ( ) => {
251- setIsRefreshing ( true ) ;
252- await fetchServers ( false ) ;
253- setTimeout ( ( ) => setIsRefreshing ( false ) , 1000 ) ;
254- } }
255- className = "p-1 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-500 dark:text-gray-400"
256- disabled = { isRefreshing }
257- >
258- < RefreshCw
259- className = { `h-4 w-4 text-[#0287FF] transition-transform duration-1000 ${
260- isRefreshing ? "animate-spin" : ""
261- } `}
262- />
263- </ button >
264- </ div >
265- </ div >
266- < div className = "space-y-1" >
267- { serverList . length > 0 ? (
268- serverList . map ( ( server ) => (
269- < div
270- key = { server . id }
271- onClick = { ( ) => switchServer ( server ) }
272- className = { `w-full flex items-center justify-between gap-1 p-2 rounded-lg transition-colors whitespace-nowrap ${
273- currentService ?. id === server . id
274- ? "bg-gray-100 dark:bg-gray-800"
275- : "hover:bg-gray-50 dark:hover:bg-gray-800/50"
276- } `}
274+ < PopoverPanel className = "absolute right-0 z-10 mt-2 min-w-[240px] bg-white dark:bg-[#202126] rounded-lg shadow-lg border border-gray-200 dark:border-gray-700" >
275+ < div className = "p-3" >
276+ < div className = "flex items-center justify-between mb-3 whitespace-nowrap" >
277+ < h3 className = "text-sm font-medium text-gray-900 dark:text-gray-100" >
278+ Servers
279+ </ h3 >
280+ < div className = "flex items-center gap-2" >
281+ < button
282+ onClick = { openSettings }
283+ className = "p-1 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-500 dark:text-gray-400"
277284 >
278- < div className = "flex items-center gap-2 overflow-hidden min-w-0" >
279- < img
280- src = { server ?. provider ?. icon || logoImg }
281- alt = { server . name }
282- className = "w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800"
283- />
284- < div className = "text-left flex-1 min-w-0" >
285- < div className = "text-sm font-medium text-gray-900 dark:text-gray-100 truncate max-w-[200px]" >
286- { server . name }
287- </ div >
288- < div className = "text-xs text-gray-500 dark:text-gray-400 truncate max-w-[200px]" >
289- AI Assistant: { server . assistantCount || 1 }
285+ < Settings className = "h-4 w-4 text-[#0287FF]" />
286+ </ button >
287+ < button
288+ onClick = { async ( ) => {
289+ setIsRefreshing ( true ) ;
290+ await fetchServers ( false ) ;
291+ setTimeout ( ( ) => setIsRefreshing ( false ) , 1000 ) ;
292+ } }
293+ className = "p-1 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-500 dark:text-gray-400"
294+ disabled = { isRefreshing }
295+ >
296+ < RefreshCw
297+ className = { `h-4 w-4 text-[#0287FF] transition-transform duration-1000 ${
298+ isRefreshing ? "animate-spin" : ""
299+ } `}
300+ />
301+ </ button >
302+ </ div >
303+ </ div >
304+ < div className = "space-y-1" >
305+ { serverList . length > 0 ? (
306+ serverList . map ( ( server ) => (
307+ < div
308+ key = { server . id }
309+ onClick = { ( ) => switchServer ( server ) }
310+ className = { `w-full flex items-center justify-between gap-1 p-2 rounded-lg transition-colors whitespace-nowrap ${
311+ currentService ?. id === server . id
312+ ? "bg-gray-100 dark:bg-gray-800"
313+ : "hover:bg-gray-50 dark:hover:bg-gray-800/50"
314+ } `}
315+ >
316+ < div className = "flex items-center gap-2 overflow-hidden min-w-0" >
317+ < img
318+ src = { server ?. provider ?. icon || logoImg }
319+ alt = { server . name }
320+ className = "w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800"
321+ />
322+ < div className = "text-left flex-1 min-w-0" >
323+ < div className = "text-sm font-medium text-gray-900 dark:text-gray-100 truncate max-w-[200px]" >
324+ { server . name }
325+ </ div >
326+ < div className = "text-xs text-gray-500 dark:text-gray-400 truncate max-w-[200px]" >
327+ AI Assistant: { server . assistantCount || 1 }
328+ </ div >
290329 </ div >
291330 </ div >
292- </ div >
293- < div className = "flex items-center gap-2" >
294- < span
295- className = { `w-3 h-3 rounded-full ${
296- server . health ?. status
297- ? ` bg-[ ${ server . health ?. status } ]`
298- : "bg-gray-400 dark:bg-gray-600"
299- } ` }
300- / >
301- < div className = "w-4 h-4" >
302- { currentService ?. id === server . id && (
303- < Check className = "w-full h-full text-gray-500 dark:text-gray-400" />
304- ) }
331+ < div className = "flex items-center gap-2" >
332+ < span
333+ className = { `w-3 h-3 rounded-full ${
334+ server . health ?. status
335+ ? `bg-[ ${ server . health ?. status } ]`
336+ : " bg-gray-400 dark:bg-gray-600"
337+ } ` }
338+ />
339+ < div className = "w-4 h-4" >
340+ { currentService ?. id === server . id && (
341+ < Check className = "w-full h-full text-gray-500 dark:text-gray-400" />
342+ ) }
343+ </ div >
305344 </ div >
306345 </ div >
346+ ) )
347+ ) : (
348+ < div className = "flex flex-col items-center justify-center py-6 text-center" >
349+ < Server className = "w-8 h-8 text-gray-400 dark:text-gray-600 mb-2" />
350+ < p className = "text-sm text-gray-500 dark:text-gray-400" >
351+ { t ( "assistant.chat.noServers" ) }
352+ </ p >
353+ < button
354+ onClick = { openSettings }
355+ className = "mt-2 text-xs text-[#0287FF] hover:underline"
356+ >
357+ { t ( "assistant.chat.addServer" ) }
358+ </ button >
307359 </ div >
308- ) )
309- ) : (
310- < div className = "flex flex-col items-center justify-center py-6 text-center" >
311- < Server className = "w-8 h-8 text-gray-400 dark:text-gray-600 mb-2" />
312- < p className = "text-sm text-gray-500 dark:text-gray-400" >
313- { t ( "assistant.chat.noServers" ) }
314- </ p >
315- < button
316- onClick = { openSettings }
317- className = "mt-2 text-xs text-[#0287FF] hover:underline"
318- >
319- { t ( "assistant.chat.addServer" ) }
320- </ button >
321- </ div >
322- ) }
360+ ) }
361+ </ div >
323362 </ div >
324- </ div >
325- </ PopoverPanel >
326- </ Popover >
363+ </ PopoverPanel >
364+ </ Popover >
327365
328- { isChatPage ? null : (
329- < button onClick = { onOpenChatAI } >
330- < WindowsFullIcon className = "rotate-30 scale-x-[-1]" />
331- </ button >
332- ) }
333- </ div > : < div /> }
366+ { isChatPage ? null : (
367+ < button className = "inline-flex" onClick = { onOpenChatAI } >
368+ < VisibleKey shortcut = { external } onKeypress = { onOpenChatAI } >
369+ < WindowsFullIcon className = "rotate-30 scale-x-[-1]" />
370+ </ VisibleKey >
371+ </ button >
372+ ) }
373+ </ div >
374+ ) : (
375+ < div />
376+ ) }
334377 </ header >
335378 ) ;
336379}
0 commit comments