feat/ 添加app-expo三种环境切换,待测试 调整tts

This commit is contained in:
Kevin
2026-03-19 09:58:02 +08:00
parent faf7607bf9
commit 15512834d2
12 changed files with 187 additions and 18 deletions

2
app-expo/.gitignore vendored
View File

@@ -32,6 +32,8 @@ yarn-error.*
# local env files
.env*.local
# generated .env (from use-env script)
.env
# typescript
*.tsbuildinfo

View File

@@ -3,11 +3,15 @@
"main": "expo-router/entry",
"version": "1.0.0",
"scripts": {
"use-env": "node scripts/use-env.js",
"prestart": "npm run use-env -- development",
"start": "expo start",
"start:staging": "npm run use-env -- staging && expo start",
"start:prod": "npm run use-env -- production && expo start",
"reset-project": "node ./scripts/reset-project.js",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"android": "npm run use-env -- development && expo run:android",
"ios": "npm run use-env -- development && expo run:ios",
"web": "npm run use-env -- development && expo start --web",
"lint": "expo lint",
"test": "jest --watch",
"test:changed": "jest --onlyChanged --coverage=false",

View File

@@ -1,6 +1,14 @@
import { Image } from 'expo-image';
import { useLocalSearchParams } from 'expo-router';
import { Mic, Pause, Play, PlusCircle, Type, X } from 'lucide-react-native';
import {
Mic,
Pause,
Play,
PlusCircle,
Type,
Volume2,
X,
} from 'lucide-react-native';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
Alert,
@@ -23,6 +31,7 @@ import { useThemeColors } from '@/hooks/use-theme-colors';
import { useMessages, useRealtimeSession } from '@/features/conversation/hooks';
import type { MessageItem } from '@/features/conversation/types';
import { audioFocus } from '@/core/audio/audio-focus';
import { usePlayer } from '@/features/voice/hooks/use-player';
import { useRecorder } from '@/features/voice/hooks/use-recorder';
import { useAudioPlayer, useAudioPlayerStatus } from 'expo-audio';
@@ -543,10 +552,12 @@ export default function ConversationScreen() {
const { t } = useTranslation('conversation');
const { t: tApp } = useTranslation('app');
const { data: messages } = useMessages(id);
const { enqueueTtsAudio, status: playerStatus } = usePlayer();
const { connectionState, streamingMessage, sendText, sendVoiceMessage } =
useRealtimeSession({
conversationId: id,
conversationId: id ?? '',
enabled: !!id,
onTtsAudio: enqueueTtsAudio,
});
const handleRecordingComplete = useCallback(
@@ -606,6 +617,14 @@ export default function ConversationScreen() {
title={
<View style={styles.headerTitleBlock}>
<Text style={styles.headerTitle}>{tApp('name')}</Text>
{playerStatus === 'playing' && (
<Icon
as={Volume2}
size={18}
color={CHAT_COLORS.primary}
style={{ marginRight: 6 }}
/>
)}
<View
style={[
styles.statusBadge,

View File

@@ -1,14 +1,6 @@
const DEV_API_URL = 'http://127.0.0.1:8000';
const DEV_WS_URL = 'ws://127.0.0.1:8000';
const PROD_API_URL = 'https://lifecho.worldsplats.com';
const PROD_WS_URL = 'wss://lifecho.worldsplats.com';
const useProdServer = process.env.EXPO_PUBLIC_USE_PROD_SERVER === 'true';
export const config = {
apiBaseUrl: useProdServer ? PROD_API_URL : DEV_API_URL,
wsBaseUrl: useProdServer ? PROD_WS_URL : DEV_WS_URL,
apiBaseUrl: process.env.EXPO_PUBLIC_API_URL ?? 'http://192.168.10.178:8000',
wsBaseUrl: process.env.EXPO_PUBLIC_WS_URL ?? 'ws://192.168.10.178:8000',
isDebugMode: __DEV__,
api: {

View File

@@ -112,6 +112,7 @@ export function useEndConversation() {
interface UseRealtimeSessionOptions {
conversationId: string;
enabled?: boolean;
onTtsAudio?: (audioBase64: string) => void;
}
const MIN_RECORDING_DURATION_SEC = 1;
@@ -136,6 +137,7 @@ interface RealtimeSessionState {
export function useRealtimeSession({
conversationId,
enabled = true,
onTtsAudio,
}: UseRealtimeSessionOptions): RealtimeSessionState {
const queryClient = useQueryClient();
const sessionRef = useRef<RealtimeSession | null>(null);
@@ -168,6 +170,7 @@ export function useRealtimeSession({
conversationId,
queryClient,
onStreamingText: handleStreamingText,
onTtsAudio,
onError: handleError,
onStateChange: setConnectionState,
});
@@ -181,7 +184,7 @@ export function useRealtimeSession({
setConnectionState('disconnected');
setStreamingMessage(null);
};
}, [conversationId, enabled, queryClient, handleStreamingText, handleError]);
}, [conversationId, enabled, queryClient, handleStreamingText, handleError, onTtsAudio]);
const sendText = useCallback(
(text: string) => {

View File

@@ -18,6 +18,7 @@ interface RealtimeSessionOptions {
conversationId: string;
queryClient: QueryClient;
onStreamingText?: StreamingTextCallback;
onTtsAudio?: (audioBase64: string) => void;
onError?: ErrorCallback;
onStateChange?: WsStateListener;
}
@@ -38,6 +39,7 @@ export class RealtimeSession {
private conversationId: string;
private queryClient: QueryClient;
private onStreamingText?: StreamingTextCallback;
private onTtsAudio?: (audioBase64: string) => void;
private onError?: ErrorCallback;
private unsubEvent: (() => void) | null = null;
private unsubState: (() => void) | null = null;
@@ -49,6 +51,7 @@ export class RealtimeSession {
this.conversationId = options.conversationId;
this.queryClient = options.queryClient;
this.onStreamingText = options.onStreamingText;
this.onTtsAudio = options.onTtsAudio;
this.onError = options.onError;
this.unsubEvent = this.client.onEvent(this.handleEvent);
@@ -117,6 +120,11 @@ export class RealtimeSession {
return;
}
if (event.kind === 'tts_audio_received') {
this.onTtsAudio?.(event.audioBase64);
return;
}
handleWsEvent(this.queryClient, event);
if (event.kind === 'session_error') {