fix: 修复19个Bug (P0-P3) — 持续性调试第7轮发现的问题

P0 (5): crypto/rand session ID, TTS fallback可达性, goroutine defer recover, adminAuth前缀修正
P1 (5): 普通用户密码验证, context传递, priority clamp, 超时重试, 自主思考速率限制
P2 (4): Briefing AI降级, 前端消息类型渲染, Docker Compose补全, PWA 192图标
P3 (5): goroutine错误处理, .gitignore完善, reminder created_at, voice Dockerfile, Go版本更新
This commit is contained in:
2026-05-20 13:30:32 +08:00
parent baaf90fc47
commit 4b35736f73
37 changed files with 556 additions and 118 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

+6
View File
@@ -8,6 +8,12 @@
"theme_color": "#ec4899",
"orientation": "any",
"icons": [
{
"src": "/images/Cyrene_Avatar/2nd_Form/Cyrene-2F-N-Happy-1-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/Cyrene_Avatar/2nd_Form/Cyrene-2F-N-Happy-1.png",
"sizes": "512x512",
+1
View File
@@ -32,6 +32,7 @@ export interface Briefing {
news: NewsItem[];
reminders: BriefReminder[];
summary: string;
summary_source: 'ai' | 'fallback';
status: 'pending' | 'generated' | 'delivered';
generated_at?: string;
delivered_at?: string;
@@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useCallback } from 'react';
import { CyreneAvatar } from '@/components/persona/CyreneAvatar';
import { useAuthStore } from '@/store/authStore';
import { useSpeechSynthesis } from '@/hooks/useSpeechSynthesis';
import type { MessageAttachment } from '@/types/chat';
import type { MessageAttachment, MultiMessageItem, StreamSegment } from '@/types/chat';
import { ImageLightbox } from './ImageLightbox';
interface MessageBubbleProps {
@@ -11,6 +11,8 @@ interface MessageBubbleProps {
timestamp: number;
isStreaming?: boolean;
attachments?: MessageAttachment[];
multiMessages?: MultiMessageItem[];
streamSegments?: StreamSegment[];
}
/**
@@ -137,7 +139,7 @@ function AIMessageActions({ content }: { content: string }) {
);
}
export function MessageBubble({ role, content, timestamp, isStreaming, attachments }: MessageBubbleProps) {
export function MessageBubble({ role, content, timestamp, isStreaming, attachments, multiMessages, streamSegments }: MessageBubbleProps) {
const isUser = role === 'user';
const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);
const time = new Date(timestamp).toLocaleTimeString('zh-CN', {
@@ -172,13 +174,43 @@ export function MessageBubble({ role, content, timestamp, isStreaming, attachmen
${isStreaming ? 'message-streaming' : ''}
`}
>
<p className="whitespace-pre-wrap break-words">
{isStreaming ? displayedContent : content}
{/* 流式消息末尾闪烁光标 — 用独立 span 避免 ::after 在隐藏字符后错位 */}
{hasMoreChars && (
<span className="animate-streaming-cursor" />
)}
</p>
{/* 多段消息渲染 (multi_message 类型) */}
{multiMessages && multiMessages.length > 0 && !isStreaming && (
<div className="space-y-2">
{multiMessages
.sort((a, b) => a.index - b.index)
.map((item) => (
<p key={item.index} className="whitespace-pre-wrap break-words">
{item.content}
</p>
))}
</div>
)}
{/* 流式片段渲染 (stream_segments 类型) */}
{streamSegments && streamSegments.length > 0 && !isStreaming && (
<div className="space-y-1.5">
{streamSegments
.sort((a, b) => a.index - b.index)
.map((seg) => (
<p key={seg.index} className="whitespace-pre-wrap break-words text-gray-600 dark:text-gray-400">
{seg.text}
</p>
))}
</div>
)}
{/* 普通文本 / 流式文本 */}
{(!multiMessages || multiMessages.length === 0) && (!streamSegments || streamSegments.length === 0) && (
<p className="whitespace-pre-wrap break-words">
{isStreaming ? displayedContent : content}
{/* 流式消息末尾闪烁光标 — 用独立 span 避免 ::after 在隐藏字符后错位 */}
{hasMoreChars && (
<span className="animate-streaming-cursor" />
)}
</p>
)}
{/* 图片附件网格 */}
{!isStreaming && imageAttachments.length > 0 && (
@@ -36,6 +36,8 @@ export function MessageList({ messages, isTyping }: MessageListProps) {
timestamp={msg.timestamp}
isStreaming={msg.isStreaming}
attachments={msg.attachments}
multiMessages={(msg as any).multiMessages}
streamSegments={(msg as any).streamSegments}
/>
))}
{isTyping && !messages.some((m) => m.isStreaming) && <TypingIndicator />}
@@ -306,6 +306,11 @@ function BriefingCard({ briefing }: { briefing: Briefing }) {
<h4 className="text-xs font-semibold text-purple-600 dark:text-purple-400">
</h4>
{briefing.summary_source === 'fallback' && (
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-amber-100 text-amber-600 dark:bg-amber-900/30 dark:text-amber-400">
</span>
)}
</div>
<p className="text-xs text-gray-600 dark:text-gray-400 leading-relaxed whitespace-pre-wrap">
{briefing.summary}
+15 -1
View File
@@ -67,6 +67,18 @@ export interface IoTDeviceUpdate {
total: number;
}
/** 多段消息子项 (子会话架构 multi_message 类型) */
export interface MultiMessageItem {
index: number;
content: string;
}
/** 流式片段 (子会话架构 stream_segments 类型) */
export interface StreamSegment {
index: number;
text: string;
}
/** WebSocket 客户端消息 */
export interface WSClientMessage {
type: 'message' | 'voice_input' | 'ping' | 'history';
@@ -99,7 +111,7 @@ export interface AppNotification extends NotificationData {
/** WebSocket 服务端消息 */
export interface WSServerMessage {
type: 'response' | 'segment' | 'audio' | 'error' | 'device_update' | 'pong' | 'history_response' | 'stream_chunk' | 'stream_end' | 'background_thinking' | 'notification';
type: 'response' | 'segment' | 'audio' | 'error' | 'device_update' | 'pong' | 'history_response' | 'stream_chunk' | 'stream_end' | 'background_thinking' | 'notification' | 'multi_message' | 'stream_segments';
message_id?: string;
text?: string;
content?: string;
@@ -111,6 +123,8 @@ export interface WSServerMessage {
tool_calls?: ToolCall[];
error?: string;
messages?: Message[];
multi_messages?: MultiMessageItem[];
stream_segments?: StreamSegment[];
devices?: IoTDevice[];
thinking_status?: BackgroundThinkingStatus;
notification?: NotificationData;