feat(memoir+conversation): 章节/故事最小可读字数;会话 hasUserMessage 与 UI 优化
- 后端:300 字门槛统一物化、hydrate、列表/PDF/详情;过短章节对读者隐藏 - 对话:首包前打字动画、大字模式排版、朗读/TTS 交互与布局稳定 - 首页:复用无用户消息会话;空列表「继续对话」与文案 i18n - 章节阅读:标题进正文、封面与去重标题;阅读 Markdown 字号上调
This commit is contained in:
@@ -65,6 +65,7 @@ export function useCreateConversation() {
|
||||
latestMessageTime: Date.now(),
|
||||
unreadCount: 0,
|
||||
isDefaultAssistant: true,
|
||||
hasUserMessage: false,
|
||||
};
|
||||
return [item, ...(old ?? [])];
|
||||
},
|
||||
@@ -134,6 +135,8 @@ function generateUUID(): string {
|
||||
interface RealtimeSessionState {
|
||||
connectionState: WsConnectionState;
|
||||
streamingMessage: StreamingAgentMessage | null;
|
||||
/** 已发出用户消息,尚未收到助手首段流式文本(用于「正在回复」气泡) */
|
||||
awaitingAssistantReply: boolean;
|
||||
error: string | null;
|
||||
sendText: (text: string) => void;
|
||||
sendVoiceMessage: (uri: string, durationMs: number) => Promise<boolean>;
|
||||
@@ -154,12 +157,17 @@ export function useRealtimeSession({
|
||||
useState<WsConnectionState>('disconnected');
|
||||
const [streamingMessage, setStreamingMessage] =
|
||||
useState<StreamingAgentMessage | null>(null);
|
||||
const [awaitingAssistantReply, setAwaitingAssistantReply] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const handleStreamingText: StreamingTextCallback = useCallback(
|
||||
(text, isComplete) => {
|
||||
if (text.trim().length > 0) {
|
||||
setAwaitingAssistantReply(false);
|
||||
}
|
||||
if (isComplete) {
|
||||
setStreamingMessage(null);
|
||||
setAwaitingAssistantReply(false);
|
||||
return;
|
||||
}
|
||||
setStreamingMessage({ text, isComplete });
|
||||
@@ -168,6 +176,7 @@ export function useRealtimeSession({
|
||||
);
|
||||
|
||||
const handleError: ErrorCallback = useCallback((message) => {
|
||||
setAwaitingAssistantReply(false);
|
||||
setError(message);
|
||||
}, []);
|
||||
|
||||
@@ -191,6 +200,7 @@ export function useRealtimeSession({
|
||||
sessionRef.current = null;
|
||||
setConnectionState('disconnected');
|
||||
setStreamingMessage(null);
|
||||
setAwaitingAssistantReply(false);
|
||||
};
|
||||
}, [
|
||||
conversationId,
|
||||
@@ -211,6 +221,7 @@ export function useRealtimeSession({
|
||||
return;
|
||||
}
|
||||
|
||||
setAwaitingAssistantReply(true);
|
||||
onTtsPlaybackResume?.();
|
||||
|
||||
const localId = `pending_${Date.now()}`;
|
||||
@@ -229,6 +240,18 @@ export function useRealtimeSession({
|
||||
return [...(old ?? []), msg];
|
||||
},
|
||||
);
|
||||
|
||||
queryClient.setQueryData<ConversationListItem[]>(
|
||||
conversationKeys.lists(),
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
return old.map((item) =>
|
||||
item.id === conversationId
|
||||
? { ...item, hasUserMessage: true }
|
||||
: item,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
[conversationId, queryClient, onTtsPlaybackResume],
|
||||
);
|
||||
@@ -258,6 +281,7 @@ export function useRealtimeSession({
|
||||
return false;
|
||||
}
|
||||
|
||||
setAwaitingAssistantReply(true);
|
||||
const localId = `pending_voice_${Date.now()}`;
|
||||
await voiceSegmentStore.recordSentSegment({
|
||||
voiceSessionId,
|
||||
@@ -282,6 +306,18 @@ export function useRealtimeSession({
|
||||
return [...(old ?? []), msg];
|
||||
},
|
||||
);
|
||||
|
||||
queryClient.setQueryData<ConversationListItem[]>(
|
||||
conversationKeys.lists(),
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
return old.map((item) =>
|
||||
item.id === conversationId
|
||||
? { ...item, hasUserMessage: true }
|
||||
: item,
|
||||
);
|
||||
},
|
||||
);
|
||||
onTtsPlaybackResume?.();
|
||||
return true;
|
||||
} catch {
|
||||
@@ -303,6 +339,7 @@ export function useRealtimeSession({
|
||||
return {
|
||||
connectionState,
|
||||
streamingMessage,
|
||||
awaitingAssistantReply,
|
||||
error,
|
||||
sendText,
|
||||
sendVoiceMessage,
|
||||
|
||||
Reference in New Issue
Block a user