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:
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
@@ -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",
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user