Skip to content

Commit

Permalink
refactor: moved TTS to zustand state, renamed 'id' symbols to 'index'…
Browse files Browse the repository at this point in the history
… in many chat components
  • Loading branch information
Vali-98 committed Dec 30, 2024
1 parent d191a34 commit e8a9f9b
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 90 deletions.
14 changes: 7 additions & 7 deletions app/components/ChatMenu/ChatWindow/ChatBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import EditorModal from './EditorModal'
import Swipes from './Swipes'

type ChatTextProps = {
id: number
index: number
nowGenerating: boolean
isLastMessage: boolean
isGreeting: boolean
}

const ChatBody: React.FC<ChatTextProps> = ({ id, nowGenerating, isLastMessage, isGreeting }) => {
const message = Chats.useEntryData(id)
const ChatBody: React.FC<ChatTextProps> = ({ index, nowGenerating, isLastMessage, isGreeting }) => {
const message = Chats.useEntryData(index)
const [editMode, setEditMode] = useState(false)

const handleEnableEdit = () => {
Expand All @@ -29,7 +29,7 @@ const ChatBody: React.FC<ChatTextProps> = ({ id, nowGenerating, isLastMessage, i
<View>
{editMode && (
<EditorModal
id={id}
index={index}
isLastMessage={isLastMessage}
setEditMode={setEditMode}
editMode={editMode}
Expand All @@ -41,14 +41,14 @@ const ChatBody: React.FC<ChatTextProps> = ({ id, nowGenerating, isLastMessage, i
activeOpacity={0.7}
onLongPress={handleEnableEdit}>
{isLastMessage ? (
<ChatTextLast nowGenerating={nowGenerating} id={id} />
<ChatTextLast nowGenerating={nowGenerating} index={index} />
) : (
<ChatText nowGenerating={nowGenerating} id={id} />
<ChatText nowGenerating={nowGenerating} index={index} />
)}
</TouchableOpacity>

{showSwipe && (
<Swipes index={id} nowGenerating={nowGenerating} isGreeting={isGreeting} />
<Swipes index={index} nowGenerating={nowGenerating} isGreeting={isGreeting} />
)}
</View>
)
Expand Down
12 changes: 6 additions & 6 deletions app/components/ChatMenu/ChatWindow/ChatFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import TTSMenu from './TTS'

type ChatFrameProps = {
children?: ReactNode
id: number
index: number
nowGenerating: boolean
isLast?: boolean
}

const ChatFrame: React.FC<ChatFrameProps> = ({ children, id, nowGenerating, isLast }) => {
const message = Chats.useEntryData(id)
const ChatFrame: React.FC<ChatFrameProps> = ({ children, index, nowGenerating, isLast }) => {
const message = Chats.useEntryData(index)

const setShowViewer = useViewerState((state) => state.setShow)

Expand Down Expand Up @@ -49,11 +49,11 @@ const ChatFrame: React.FC<ChatFrameProps> = ({ children, id, nowGenerating, isLa
/>
</TouchableOpacity>

<Text style={styles.graytext}>#{id}</Text>
{deltaTime !== undefined && !message.is_user && id !== 0 && (
<Text style={styles.graytext}>#{index}</Text>
{deltaTime !== undefined && !message.is_user && index !== 0 && (
<Text style={styles.graytext}>{deltaTime}s</Text>
)}
{TTSenabled && <TTSMenu id={id} isLast={isLast} />}
{TTSenabled && <TTSMenu index={index} />}
</View>
<View style={{ flex: 1, flexDirection: 'column' }}>
<View style={{ flex: 1 }}>
Expand Down
8 changes: 4 additions & 4 deletions app/components/ChatMenu/ChatWindow/ChatItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ import ChatBody from './ChatBody'
import ChatFrame from './ChatFrame'

type ChatItemProps = {
id: number
index: number
isLastMessage: boolean
isGreeting: boolean
}

const ChatItem: React.FC<ChatItemProps> = ({ id, isLastMessage, isGreeting }) => {
const ChatItem: React.FC<ChatItemProps> = ({ index, isLastMessage, isGreeting }) => {
const nowGenerating = useInference((state) => state.nowGenerating)

return (
<FadeDownView>
<View style={styles.chatItem}>
<ChatFrame id={id} nowGenerating={nowGenerating} isLast={isLastMessage}>
<ChatFrame index={index} nowGenerating={nowGenerating} isLast={isLastMessage}>
<ChatBody
nowGenerating={nowGenerating}
id={id}
index={index}
isLastMessage={isLastMessage}
isGreeting={isGreeting}
/>
Expand Down
6 changes: 3 additions & 3 deletions app/components/ChatMenu/ChatWindow/ChatText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import Markdown from 'react-native-markdown-display'

type ChatTextProps = {
nowGenerating: boolean
id: number
index: number
}

const ChatText: React.FC<ChatTextProps> = ({ nowGenerating, id }) => {
const { swipeText } = Chats.useSwipeData(id)
const ChatText: React.FC<ChatTextProps> = ({ nowGenerating, index }) => {
const { swipeText } = Chats.useSwipeData(index)
const viewRef = useRef<View>(null)

const animHeight = useAnimatedValue(-1)
Expand Down
6 changes: 3 additions & 3 deletions app/components/ChatMenu/ChatWindow/ChatTextLast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import Markdown from 'react-native-markdown-display'

type ChatTextProps = {
nowGenerating: boolean
id: number
index: number
}

const ChatTextLast: React.FC<ChatTextProps> = ({ nowGenerating, id }) => {
const { swipeText, swipeId } = Chats.useSwipeData(id)
const ChatTextLast: React.FC<ChatTextProps> = ({ nowGenerating, index }) => {
const { swipeText, swipeId } = Chats.useSwipeData(index)
const { buffer } = Chats.useBuffer()

const viewRef = useRef<View>(null)
Expand Down
2 changes: 1 addition & 1 deletion app/components/ChatMenu/ChatWindow/ChatWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const ChatWindow = () => {
const renderItems = ({ item, index }: { item: ListItem; index: number }) => {
return (
<ChatItem
id={item.index}
index={item.index}
isLastMessage={item.isLastMessage}
isGreeting={item.isGreeting}
/>
Expand Down
12 changes: 6 additions & 6 deletions app/components/ChatMenu/ChatWindow/EditorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,26 @@ const EditorButton = ({ name, onPress, label, color }: EditorButtonProps) => (
)

type EditorProps = {
id: number
index: number
isLastMessage: boolean
setEditMode: React.Dispatch<React.SetStateAction<boolean>>
editMode: boolean
}

const EditorModal: React.FC<EditorProps> = ({ id, isLastMessage, setEditMode, editMode }) => {
const EditorModal: React.FC<EditorProps> = ({ index, isLastMessage, setEditMode, editMode }) => {
const { updateEntry, deleteEntry } = Chats.useEntry()
const { swipeText, swipe } = Chats.useSwipeData(id)
const entry = Chats.useEntryData(id)
const { swipeText, swipe } = Chats.useSwipeData(index)
const entry = Chats.useEntryData(index)

const [placeholderText, setPlaceholderText] = useState(swipeText)

const handleEditMessage = () => {
updateEntry(id, placeholderText, false)
updateEntry(index, placeholderText, false)
setEditMode(false)
}

const handleDeleteMessage = () => {
deleteEntry(id)
deleteEntry(index)
setEditMode(false)
}

Expand Down
67 changes: 10 additions & 57 deletions app/components/ChatMenu/ChatWindow/TTS.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,26 @@
import { useTTS } from '@constants/TTS'
import { FontAwesome } from '@expo/vector-icons'
import { Chats, useInference } from 'constants/Chat'
import { Global, Logger, Style } from 'constants/Global'
import * as Speech from 'expo-speech'
import { useEffect, useState } from 'react'
import { Chats } from 'constants/Chat'
import { Logger, Style } from 'constants/Global'
import { TouchableOpacity, View } from 'react-native'
import { useMMKVBoolean, useMMKVObject } from 'react-native-mmkv'

type TTSProps = {
id: number
isLast?: boolean
index: number
}

const TTS: React.FC<TTSProps> = ({ id, isLast }) => {
const [isSpeaking, setIsSpeaking] = useState<boolean>(false)
const [currentSpeaker, setCurrentSpeaker] = useMMKVObject<Speech.Voice>(Global.TTSSpeaker)
const [autoTTS, setAutoTTS] = useMMKVBoolean(Global.TTSAuto)
const [start, setStart] = useMMKVBoolean(Global.TTSAutoStart)
const nowGenerating = useInference((state) => state.nowGenerating)

const { swipeText } = Chats.useSwipeData(id)

useEffect(() => {
if (nowGenerating && isSpeaking) handleStopSpeaking()
}, [nowGenerating])

useEffect(() => {
if (autoTTS && isLast && start) handleSpeak()
}, [start])
const TTS: React.FC<TTSProps> = ({ index }) => {
const { startTTS, activeChatIndex, stopTTS } = useTTS()
const { swipeText } = Chats.useSwipeData(index)
const isSpeaking = index === activeChatIndex

const handleSpeak = async () => {
Logger.log('Starting TTS')
setStart(false)
if (currentSpeaker === undefined) {
Logger.log(`No Speaker Chosen`, true)
return
}
if (await Speech.isSpeakingAsync()) await Speech.stop()
setIsSpeaking(true)
const filter = /([!?.,*"])/
const filteredchunks: string[] = []
const chunks = swipeText.split(filter)
chunks.forEach((item, index) => {
if (!filter.test(item) && item) return filteredchunks.push(item)
if (index > 0)
filteredchunks[filteredchunks.length - 1] =
filteredchunks[filteredchunks.length - 1] + item
})
if (filteredchunks.length === 0) filteredchunks.push(swipeText)

const cleanedchunks = filteredchunks.map((item) => item.replaceAll(/[*"]/g, '').trim())
Logger.debug('TTS started with ' + cleanedchunks.length + ' chunks')

cleanedchunks.forEach((chunk, index) =>
Speech.speak(chunk, {
language: currentSpeaker?.language,
voice: currentSpeaker?.identifier,
onDone: () => {
index === cleanedchunks.length - 1 && setIsSpeaking(false)
},
onStopped: () => setIsSpeaking(false),
})
)
if (cleanedchunks.length === 0) setIsSpeaking(false)
await startTTS(swipeText, index)
}

const handleStopSpeaking = async () => {
Logger.log('TTS stopped')
await Speech.stop()
setIsSpeaking(false)
await stopTTS()
}

return (
Expand Down
9 changes: 7 additions & 2 deletions constants/Chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AppSettings, Global } from './GlobalValues'
import { Llama } from './LlamaLocal'
import { Logger } from './Logger'
import { mmkv } from './MMKV'
import { useTTSState } from './TTS'
import { convertToFormatInstruct } from './TextFormat'
import { Tokenizer } from './Tokenizer'
import { replaceMacros } from './Utils'
Expand Down Expand Up @@ -152,8 +153,12 @@ export namespace Chats {
get().setBuffer('')

if (mmkv.getBoolean(Global.TTSEnable) && mmkv.getBoolean(Global.TTSAuto)) {
Logger.log(`Automatically using TTS`)
mmkv.set(Global.TTSAutoStart, JSON.stringify(true))
const length = get().data?.messages?.length
if (!length) return
const message = get().data?.messages?.[length - 1]
if (!message) return
await useTTSState.getState().stopTTS()
useTTSState.getState().startTTS(message.swipes[message.swipe_id].swipe, length - 1)
}
},
load: async (chatId: number) => {
Expand Down
2 changes: 1 addition & 1 deletion constants/GlobalValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export const enum Global {
TTSSpeaker = 'ttsspeaker',
TTSEnable = 'ttsenable',
TTSAuto = `ttsauto`,
TTSAutoStart = 'ttsautostart',
// TTSAutoStart = 'ttsautostart', // moved autoTTS to zustand state
}

export enum GenerationSettings {
Expand Down
79 changes: 79 additions & 0 deletions constants/TTS.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as Speech from 'expo-speech'
import { create } from 'zustand'

import { Global } from './GlobalValues'
import { Logger } from './Logger'
import { mmkv } from './MMKV'

type TTSState = {
active: boolean
activeChatIndex?: number
startTTS: (text: string, index: number, callback?: () => void) => Promise<void>
stopTTS: () => Promise<void>
}

export const useTTS = () => {
const { startTTS, activeChatIndex, stopTTS } = useTTSState((state) => ({
startTTS: state.startTTS,
stopTTS: state.stopTTS,
activeChatIndex: state.activeChatIndex,
}))
return { startTTS, activeChatIndex, stopTTS }
}

export const useTTSState = create<TTSState>()((set, get) => ({
active: false,
activeChatIndex: undefined,
startTTS: async (text: string, index: number, exitCallback = () => {}) => {
const clearIndex = () => {
if (get().activeChatIndex === index)
set((state) => ({ ...state, activeChatIndex: undefined }))
}

const currentSpeakerString = mmkv.getString(Global.TTSSpeaker)
if (!currentSpeakerString) {
Logger.log('Invalid Speaker', true)
clearIndex()
return
}
const currentSpeaker: Speech.Voice = JSON.parse(currentSpeakerString)

Logger.log('Starting TTS')
if (currentSpeaker === undefined) {
Logger.log(`No Speaker Chosen`, true)
clearIndex()
return
}
if (await Speech.isSpeakingAsync()) await Speech.stop()
const filter = /([!?.,*"])/
const filteredchunks: string[] = []
const chunks = text.split(filter)
chunks.forEach((item, index) => {
if (!filter.test(item) && item) return filteredchunks.push(item)
if (index > 0)
filteredchunks[filteredchunks.length - 1] =
filteredchunks[filteredchunks.length - 1] + item
})
if (filteredchunks.length === 0) filteredchunks.push(text)

const cleanedchunks = filteredchunks.map((item) => item.replaceAll(/[*"]/g, '').trim())
Logger.debug('TTS started with ' + cleanedchunks.length + ' chunks')
set((state) => ({ ...state, activeChatIndex: index }))
cleanedchunks.forEach((chunk, index) =>
Speech.speak(chunk, {
language: currentSpeaker?.language,
voice: currentSpeaker?.identifier,
onDone: () => {
index === cleanedchunks.length - 1 && clearIndex()
},
onStopped: () => clearIndex(),
})
)
if (cleanedchunks.length === 0) clearIndex()
},
stopTTS: async () => {
Logger.log('TTS stopped')
set((state) => ({ ...state, activeChatIndex: undefined }))
await Speech.stop()
},
}))

0 comments on commit e8a9f9b

Please sign in to comment.