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
@@ -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}