feat/调整tts音色,调整封面图prompt,修复对话页输入框显示逻辑,待验证封面图生成功能

This commit is contained in:
Kevin
2026-03-19 14:14:13 +08:00
parent 687f41df2e
commit 7237b53b9b
10 changed files with 168 additions and 18 deletions

View File

@@ -15,6 +15,7 @@ import {
Animated,
FlatList,
InteractionManager,
Keyboard,
KeyboardAvoidingView,
Platform,
Pressable,
@@ -578,8 +579,20 @@ export default function ConversationScreen() {
const [input, setInput] = useState('');
const [inputMode, setInputMode] = useState<InputMode>('text');
const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);
const listRef = useRef<FlatList>(null);
useEffect(() => {
const onShow = () => setIsKeyboardVisible(true);
const onHide = () => setIsKeyboardVisible(false);
const subShow = Keyboard.addListener('keyboardDidShow', onShow);
const subHide = Keyboard.addListener('keyboardDidHide', onHide);
return () => {
subShow.remove();
subHide.remove();
};
}, []);
const flattenedData = flattenMessagesForList(messages ?? []);
const isRecording = recorderStatus === 'recording';
@@ -607,11 +620,12 @@ export default function ConversationScreen() {
: t('connectionDisconnected');
const keyboardOffset = Platform.OS === 'ios' ? insets.top + 56 : 0;
const kavEnabled = inputMode === 'text' && isKeyboardVisible;
return (
<KeyboardAvoidingView
style={styles.container}
behavior="padding"
behavior={kavEnabled ? 'padding' : undefined}
keyboardVerticalOffset={keyboardOffset}
>
<View style={styles.column}>
@@ -694,9 +708,14 @@ export default function ConversationScreen() {
onChangeText={setInput}
onSend={handleSend}
inputMode={inputMode}
onInputModeToggle={() =>
setInputMode((m) => (m === 'text' ? 'voice' : 'text'))
}
onInputModeToggle={() => {
setInputMode((m) => {
if (m === 'text') {
Keyboard.dismiss();
}
return m === 'text' ? 'voice' : 'text';
});
}}
onAddPress={() => {}}
onStartRecording={handleStartRecording}
onStopRecording={() => void stopRecording()}

View File

@@ -1,9 +1,11 @@
import { useFocusEffect } from '@react-navigation/native';
import { Image } from 'expo-image';
import { router } from 'expo-router';
import React from 'react';
import React, { useCallback, useState } from 'react';
import {
Platform,
Pressable,
RefreshControl,
ScrollView,
View,
useWindowDimensions,
@@ -17,7 +19,7 @@ import { Skeleton } from '@/components/ui/skeleton';
import { Text } from '@/components/ui/text';
import { ScreenGutter } from '@/constants/layout';
import { useCreateConversation } from '@/features/conversation/hooks';
import { useChapters } from '@/features/memoir/hooks';
import { useChapters, useCheckCoverGeneration } from '@/features/memoir/hooks';
import type { ChapterViewModel } from '@/features/memoir/types';
type ChapterVariant = 'completed' | 'drafting' | 'locked-left' | 'locked-large';
@@ -393,8 +395,26 @@ function EmptyState({
export default function MemoirScreen() {
const { t } = useTranslation('memoir');
const { viewModels: chapters, isLoading } = useChapters();
const { viewModels: chapters, isLoading, refetch } = useChapters();
const createConversation = useCreateConversation();
const checkCover = useCheckCoverGeneration();
const [refreshing, setRefreshing] = useState(false);
useFocusEffect(
useCallback(() => {
checkCover.mutate(undefined);
}, [checkCover.mutate]),
);
const handleRefresh = useCallback(async () => {
setRefreshing(true);
try {
await checkCover.mutateAsync(undefined);
await refetch();
} finally {
setRefreshing(false);
}
}, [checkCover.mutateAsync, refetch]);
const handleStartChapter = () => {
createConversation.mutate(undefined, {
@@ -414,6 +434,9 @@ export default function MemoirScreen() {
<ScrollView
contentInsetAdjustmentBehavior="automatic"
className="flex-1"
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
}
contentContainerStyle={{
paddingHorizontal: ScreenGutter,
paddingTop: 24,

View File

@@ -48,6 +48,12 @@ export const memoirApi = {
);
},
checkCoverGeneration() {
return api.post<{ triggered: string[] }>(
'/api/chapters/check-cover-generation',
);
},
regenerateChapter(chapterId: string) {
return api.post<{ status: string; message: string }>(
`/api/chapters/${chapterId}/regenerate`,

View File

@@ -69,6 +69,19 @@ export function useDeleteChapter() {
});
}
export function useCheckCoverGeneration() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => memoirApi.checkCoverGeneration(),
onSuccess: (data) => {
if (data.triggered.length > 0) {
queryClient.invalidateQueries({ queryKey: memoirKeys.chapters() });
}
},
});
}
// ─── Memoir state ───
export function useMemoirState() {