fix: windows desktop workflow

This commit is contained in:
zhayujie
2026-07-16 17:00:21 +08:00
parent db49532211
commit c7060c147d
6 changed files with 107 additions and 35 deletions

View File

@@ -27,21 +27,21 @@
--danger-border: rgba(239, 68, 68, 0.3);
--info: #3b82f6;
/* Light theme — layered neutral surfaces */
--bg-base: #fafafa; /* app background */
/* Light theme — aligned with the web console (Tailwind gray/slate palette) */
--bg-base: #f9fafb; /* app background (gray-50) */
--bg-surface: #ffffff; /* panels, cards */
--bg-surface-2: #f4f4f5; /* nested surfaces, hover fills */
--bg-surface-2: #f1f5f9; /* nested surfaces, hover fills (slate-100) */
--bg-elevated: #ffffff; /* popovers, menus, modals */
--bg-inset: #f4f4f5; /* inputs, code blocks */
--bg-inset: #f1f5f9; /* inputs, code blocks (slate-100) */
--text-primary: #18181b; /* headings, primary text (contrast > 4.5:1) */
--text-secondary: #52525b; /* body, labels */
--text-tertiary: #71717a; /* hints, captions */
--text-disabled: #a1a1aa;
--text-primary: #1e293b; /* headings, primary text (slate-800) */
--text-secondary: #475569; /* body, labels (slate-600) */
--text-tertiary: #64748b; /* hints, captions (slate-500) */
--text-disabled: #94a3b8; /* slate-400 */
--border-default: #e4e4e7;
--border-strong: #d4d4d8;
--border-subtle: #f0f0f1;
--border-default: #e2e8f0; /* slate-200 */
--border-strong: #cbd5e1; /* slate-300 */
--border-subtle: #f1f5f9; /* slate-100 */
--overlay: rgba(0, 0, 0, 0.4);
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 3px rgba(0, 0, 0, 0.06);

View File

@@ -17,6 +17,7 @@ import apiClient from '../api/client'
import type { Attachment, ChatMessage } from '../types'
import { useChatStore } from '../store/chatStore'
import { useSessionStore } from '../store/sessionStore'
import { useUIStore } from '../store/uiStore'
interface ChatPageProps {
baseUrl: string
@@ -54,6 +55,8 @@ const ChatPage: React.FC<ChatPageProps> = ({ baseUrl }) => {
const deleteMessage = useChatStore((s) => s.deleteMessage)
const loadHistory = useChatStore((s) => s.loadHistory)
const ensureSession = useChatStore((s) => s.ensureSession)
const clearContext = useChatStore((s) => s.clearContext)
const setSessionsCollapsed = useUIStore((s) => s.setSessionsCollapsed)
const messages = session?.messages ?? []
const isStreaming = session?.isStreaming ?? false
@@ -170,15 +173,14 @@ const ChatPage: React.FC<ChatPageProps> = ({ baseUrl }) => {
const id = newSession()
ensureSession(id)
loadHistory(id, 1)
}, [newSession, ensureSession, loadHistory])
// Auto-expand the session list so the user sees the new/switched session.
setSessionsCollapsed(false)
}, [newSession, ensureSession, loadHistory, setSessionsCollapsed])
const handleClearContext = useCallback(async () => {
try {
await apiClient.clearContext(activeId)
} catch {
/* ignore */
}
}, [activeId])
await clearContext(activeId)
scrollToBottom(true)
}, [clearContext, activeId, scrollToBottom])
const handleStop = useCallback(() => cancel(activeId), [cancel, activeId])
@@ -242,7 +244,9 @@ const ChatPage: React.FC<ChatPageProps> = ({ baseUrl }) => {
<div className="flex flex-col items-center justify-center h-full px-6 py-12">
<img src="./logo.jpg" alt="CowAgent" className="w-16 h-16 rounded-2xl mb-5 shadow-md" />
<h1 className="text-xl font-semibold text-content mb-2">{t('chat_welcome')}</h1>
<p className="text-content-tertiary text-sm text-center max-w-md mb-8">{t('chat_empty_hint')}</p>
<p className="text-content-tertiary text-sm text-center max-w-md mb-8 leading-relaxed whitespace-pre-line">
{t('welcome_subtitle')}
</p>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3 w-full max-w-2xl">
{SUGGESTIONS.map(({ key, send, icon: Icon, iconClass, bgClass }) => (

View File

@@ -31,6 +31,7 @@ interface ChatState {
deleteMessage: (sid: string, userSeq: number, cascade: boolean) => Promise<void>
loadHistory: (sid: string, page?: number) => Promise<void>
clearContext: (sid: string) => Promise<boolean>
clearLocal: (sid: string) => void
}
@@ -397,6 +398,25 @@ export const useChatStore = create<ChatState>((set, get) => {
cancel: async (sid) => {
const s = get().sessions[sid]
if (!s?.requestId) return
// Optimistically stop the UI right away: mark the last assistant bubble
// cancelled, free the input, and tear down the local SSE stream so no
// further deltas render after the user hit stop. The backend still gets
// the cancel request to abort the running agent task.
patchMessages(sid, (msgs) => {
for (let i = msgs.length - 1; i >= 0; i--) {
if (msgs[i].role === 'assistant') {
msgs[i] = { ...msgs[i], isCancelled: true, isStreaming: false }
break
}
}
return [...msgs]
})
patchSession(sid, { isStreaming: false, requestId: null })
const es = streams[sid]
if (es) {
es.close()
delete streams[sid]
}
try {
await apiClient.cancel({ requestId: s.requestId, sessionId: sid })
} catch {
@@ -476,6 +496,28 @@ export const useChatStore = create<ChatState>((set, get) => {
}
},
clearContext: async (sid) => {
try {
const res = await apiClient.clearContext(sid)
if (res.status !== 'success') return false
// Append a visual divider so the user sees the context was cleared
// (mirrors the web console's context-divider).
patchMessages(sid, (msgs) => [
...msgs,
{
id: uid('divider'),
role: 'system',
kind: 'divider',
content: '',
timestamp: Date.now() / 1000,
},
])
return true
} catch {
return false
}
},
clearLocal: (sid) => {
const es = streams[sid]
if (es) {

View File

@@ -12,6 +12,7 @@ interface UIState {
/** Session list panel collapsed (hidden) vs expanded. */
sessionsCollapsed: boolean
toggleSessions: () => void
setSessionsCollapsed: (v: boolean) => void
/** Currently active session id (Chat page). */
activeSessionId: string | null
@@ -42,6 +43,10 @@ export const useUIStore = create<UIState>((set) => ({
localStorage.setItem(SESSIONS_KEY, next ? '1' : '0')
return { sessionsCollapsed: next }
}),
setSessionsCollapsed: (v) => {
localStorage.setItem(SESSIONS_KEY, v ? '1' : '0')
set({ sessionsCollapsed: v })
},
activeSessionId: null,
setActiveSessionId: (id) => set({ activeSessionId: id }),

View File

@@ -50,7 +50,7 @@ export interface BackendStatusEvent {
// Chat / messages / streaming
// ============================================================
export type Role = 'user' | 'assistant'
export type Role = 'user' | 'assistant' | 'system'
/** A single ordered step inside an assistant turn (matches backend history). */
export interface MessageStep {
@@ -83,8 +83,8 @@ export interface ChatMessage {
/** Sequence numbers from backend (for delete/regenerate). */
userSeq?: number
botSeq?: number
/** Self-evolution bubble flag. */
kind?: 'evolution'
/** Self-evolution bubble flag; 'divider' renders a context-cleared separator. */
kind?: 'evolution' | 'divider'
extras?: Record<string, unknown>
isStreaming?: boolean
isCancelled?: boolean