@@ -29,20 +29,83 @@ export default function Index() {
2929 const [ tokenizerName , setTokenizerName ] = useState ( '' ) ;
3030 const [ isInitialized , setIsInitialized ] = useState ( false ) ;
3131 const [ isInitializing , setIsInitializing ] = useState ( false ) ;
32- const [ history , setHistory ] = useState < Array < { input : boolean , text : string } > > ( [ ] ) ;
32+ const [ history , setHistory ] = useState < Array < { input : boolean , text : string } > > ( [ ] ) ;
3333 const scrollViewRef = useRef ( ) ;
3434
3535 useEffect ( ( ) => {
3636 const unsubscribe = LLaMABridge . onToken ( ( token ) => {
37- if ( ! isStopped ) { // Only process tokens if not stopped
38- setCurrentOutput ( prev => prev + token ) ;
39- if ( token === ' ' || / [ . , ! ? ] / . test ( token ) ) {
40- Haptics . impactAsync ( Haptics . ImpactFeedbackStyle . Light ) ;
37+ if ( ! isStopped ) {
38+ // Natural stop
39+ if ( token === "<|eot_id|>" ) {
40+ setIsGenerating ( false ) ;
41+ setCurrentOutput ( prev => {
42+ if ( prev . trim ( ) ) {
43+ setHistory ( prevHistory => [ ...prevHistory , { input : false , text : prev . trim ( ) } ] ) ;
44+ }
45+ return '' ;
46+ } ) ;
47+ return ;
4148 }
49+
50+ // Skip template tokens
51+ if ( token === formatPrompt ( '' ) ||
52+ token . includes ( "<|begin_of_text|>" ) ||
53+ token . includes ( "<|start_header_id|>" ) ||
54+ token . includes ( "<|end_header_id|>" ) ||
55+ token . includes ( "assistant" ) ) {
56+ return ;
57+ }
58+
59+ // Add token without leading newlines
60+ setCurrentOutput ( prev => prev + token . replace ( / ^ \n + / , '' ) ) ;
4261 }
4362 } ) ;
63+
4464 return ( ) => unsubscribe ( ) ;
45- } , [ isStopped ] ) ; // Add isStopped to dependencies
65+ } , [ isStopped , currentOutput ] ) ;
66+
67+
68+ const formatPrompt = ( text : string ) => {
69+ return `<|begin_of_text|><|start_header_id|>user<|end_header_id|>${ text . trim ( ) } <|eot_id|><|start_header_id|>assistant<|end_header_id|>` ;
70+ } ;
71+
72+ const handleGenerate = async ( ) => {
73+ if ( ! isInitialized || ! prompt . trim ( ) ) {
74+ return ;
75+ }
76+
77+ setIsStopped ( false ) ;
78+ const newPrompt = prompt . trim ( ) ;
79+ setPrompt ( '' ) ;
80+ setIsGenerating ( true ) ;
81+ setCurrentOutput ( '' ) ;
82+
83+ // Add the user message immediately
84+ const userMessage = { input : true , text : newPrompt } ;
85+ setHistory ( prev => [ ...prev , userMessage ] ) ;
86+
87+ try {
88+ const formattedPrompt = formatPrompt ( newPrompt ) ;
89+ await LLaMABridge . generate ( formattedPrompt , 768 ) ;
90+ } catch ( error ) {
91+ console . error ( error ) ;
92+ Alert . alert ( 'Error' , 'Generation failed' ) ;
93+ setIsGenerating ( false ) ;
94+ }
95+ } ;
96+
97+ const handleStop = ( ) => {
98+ if ( ! isGenerating ) return ;
99+
100+ setIsStopped ( true ) ;
101+ LLaMABridge . stop ( ) ;
102+
103+ if ( currentOutput ) {
104+ setHistory ( prev => [ ...prev , { input : false , text : currentOutput . trim ( ) } ] ) ;
105+ }
106+ setCurrentOutput ( '' ) ;
107+ setIsGenerating ( false ) ;
108+ } ;
46109
47110 const selectModel = async ( ) => {
48111 try {
@@ -75,11 +138,23 @@ export default function Index() {
75138 } ;
76139
77140 const initializeLLaMA = async ( ) => {
141+ // If already initialized, reset everything
142+ if ( isInitialized ) {
143+ setModelPath ( '' ) ;
144+ setModelName ( '' ) ;
145+ setTokenizerPath ( '' ) ;
146+ setTokenizerName ( '' ) ;
147+ setIsInitialized ( false ) ;
148+ setHistory ( [ ] ) ;
149+ setCurrentOutput ( '' ) ;
150+ return ;
151+ }
152+
78153 if ( ! modelPath || ! tokenizerPath ) {
79154 Alert . alert ( 'Error' , 'Please select both model and tokenizer files first' ) ;
80155 return ;
81156 }
82-
157+
83158 setIsInitializing ( true ) ;
84159 try {
85160 await LLaMABridge . initialize ( modelPath , tokenizerPath ) ;
@@ -88,50 +163,15 @@ export default function Index() {
88163 } catch ( error ) {
89164 console . error ( 'Failed to initialize LLaMA:' , error ) ;
90165 Alert . alert ( 'Error' , 'Failed to initialize LLaMA' ) ;
166+ setModelPath ( '' ) ;
167+ setModelName ( '' ) ;
168+ setTokenizerPath ( '' ) ;
169+ setTokenizerName ( '' ) ;
91170 } finally {
92171 setIsInitializing ( false ) ;
93172 }
94173 } ;
95174
96- const handleGenerate = async ( ) => {
97- if ( ! isInitialized ) {
98- Alert . alert ( 'Error' , 'Please initialize LLaMA first' ) ;
99- return ;
100- }
101- if ( ! prompt . trim ( ) ) {
102- return ;
103- }
104-
105- setIsStopped ( false ) ; // Add this line
106- const newPrompt = prompt . trim ( ) ;
107- setPrompt ( '' ) ;
108- setIsGenerating ( true ) ;
109- setCurrentOutput ( '' ) ;
110- setHistory ( prev => [ ...prev , { input : true , text : newPrompt } ] ) ;
111-
112- try {
113- await LLaMABridge . generate ( newPrompt , 768 ) ;
114- } catch ( error ) {
115- console . error ( error ) ;
116- Alert . alert ( 'Error' , 'Generation failed' ) ;
117- } finally {
118- setIsGenerating ( false ) ;
119- if ( currentOutput ) {
120- setHistory ( prev => [ ...prev , { input : false , text : currentOutput } ] ) ;
121- setCurrentOutput ( '' ) ;
122- }
123- }
124- } ;
125-
126- const handleStop = ( ) => {
127- setIsStopped ( true ) ;
128- LLaMABridge . stop ( ) ;
129- setIsGenerating ( false ) ;
130- if ( currentOutput ) {
131- setHistory ( prev => [ ...prev , { input : false , text : currentOutput } ] ) ;
132- setCurrentOutput ( '' ) ;
133- }
134- } ;
135175
136176 const handleClearHistory = ( ) => {
137177 Haptics . notificationAsync ( Haptics . NotificationFeedbackType . Success ) ;
@@ -147,17 +187,17 @@ export default function Index() {
147187
148188 < View style = { styles . setupBar } >
149189 < View style = { styles . setupControls } >
150- < TouchableOpacity
151- style = { [ styles . setupButton , modelPath ? styles . setupComplete : styles . setupIncomplete ] }
190+ < TouchableOpacity
191+ style = { [ styles . setupButton , modelPath ? styles . setupComplete : styles . setupIncomplete ] }
152192 onPress = { selectModel }
153193 >
154194 < Ionicons name = "cube-outline" size = { 20 } color = "#fff" />
155195 < Text style = { styles . setupText } >
156196 { modelName ? modelName . substring ( 0 , 15 ) + '...' : "Select Model" }
157197 </ Text >
158198 </ TouchableOpacity >
159- < TouchableOpacity
160- style = { [ styles . setupButton , tokenizerPath ? styles . setupComplete : styles . setupIncomplete ] }
199+ < TouchableOpacity
200+ style = { [ styles . setupButton , tokenizerPath ? styles . setupComplete : styles . setupIncomplete ] }
161201 onPress = { selectTokenizer }
162202 >
163203 < Ionicons name = "key-outline" size = { 20 } color = "#fff" />
@@ -166,28 +206,28 @@ export default function Index() {
166206 </ Text >
167207 </ TouchableOpacity >
168208 </ View >
169- < TouchableOpacity
209+ < TouchableOpacity
170210 style = { [
171- styles . initButton ,
211+ styles . initButton ,
172212 isInitialized ? styles . setupComplete : styles . setupIncomplete ,
173213 ( ! modelPath || ! tokenizerPath || isInitializing ) && styles . buttonDisabled
174- ] }
214+ ] }
175215 onPress = { initializeLLaMA }
176216 disabled = { ! modelPath || ! tokenizerPath || isInitializing }
177217 >
178218 { isInitializing ? (
179219 < ActivityIndicator size = "small" color = "#fff" />
180220 ) : (
181- < Ionicons
182- name = { isInitialized ? "checkmark-circle-outline" : "power-outline" }
183- size = { 24 }
184- color = "#fff"
221+ < Ionicons
222+ name = { isInitialized ? "checkmark-circle-outline" : "power-outline" }
223+ size = { 24 }
224+ color = "#fff"
185225 />
186226 ) }
187227 </ TouchableOpacity >
188228 </ View >
189229
190- < KeyboardAvoidingView
230+ < KeyboardAvoidingView
191231 behavior = { Platform . OS === "ios" ? "padding" : "height" }
192232 style = { styles . content }
193233 keyboardVerticalOffset = { Platform . OS === "ios" ? 90 : 0 }
@@ -205,11 +245,10 @@ export default function Index() {
205245 </ Text >
206246 </ View >
207247 ) : history . length === 0 ? (
208- < Pressable
248+ < Pressable
209249 style = { styles . emptyState }
210250 onLongPress = { handleClearHistory }
211251 >
212- < Ionicons name = "chatbubble-outline" size = { 48 } color = "#666" />
213252 < Text style = { styles . emptyStateText } > Start a conversation</ Text >
214253 < Text style = { styles . emptyStateHint } > Long press to clear history</ Text >
215254 </ Pressable >
@@ -247,14 +286,14 @@ export default function Index() {
247286 editable = { isInitialized }
248287 />
249288 < TouchableOpacity
250- style = { [ styles . sendButton , ! isInitialized && styles . buttonDisabled ] }
289+ style = { [ styles . sendButton , ( ! isInitialized || ( ! isGenerating && ! prompt . trim ( ) ) ) && styles . buttonDisabled ] }
251290 onPress = { isGenerating ? handleStop : handleGenerate }
252- disabled = { ! isInitialized || ( ! prompt . trim ( ) && ! isGenerating ) }
291+ disabled = { ! isInitialized || ( ! prompt . trim ( ) && ! isGenerating ) } // This was backwards
253292 >
254- < Ionicons
255- name = { isGenerating ? "stop-outline" : "send-outline" }
256- size = { 24 }
257- color = "#fff"
293+ < Ionicons
294+ name = { isGenerating ? "stop-outline" : "send-outline" }
295+ size = { 24 }
296+ color = "#fff"
258297 />
259298 </ TouchableOpacity >
260299 </ View >
@@ -270,9 +309,9 @@ const styles = StyleSheet.create({
270309 } ,
271310 header : {
272311 padding : 16 ,
273- alignItems : 'center' ,
274312 borderBottomWidth : 1 ,
275313 borderBottomColor : '#333' ,
314+ alignItems : "start" ,
276315 } ,
277316 headerTitle : {
278317 fontSize : 24 ,
@@ -349,7 +388,6 @@ const styles = StyleSheet.create({
349388 color : '#666' ,
350389 fontSize : 12 ,
351390 marginTop : 8 ,
352- fontStyle : 'italic' ,
353391 } ,
354392 initPrompt : {
355393 padding : 20 ,
0 commit comments