@@ -8,18 +8,17 @@ import {
88 useState ,
99} from 'react' ;
1010
11- import { prewarmCodeBlocks } from '@/components/CodeBlock' ;
1211import { Messages } from '@/components/Messages' ;
1312import { TURN_ABORTED_MESSAGE } from '@/components/Messages/constants' ;
1413import { PlanReview } from '@/components/PlanReview' ;
1514import { ToolApproval } from '@/components/ToolApproval' ;
16- import { DECISION , MODE , PROMPT , ROLE , THEME , UI } from '@/constants' ;
15+ import { DECISION , MODE , ROLE , THEME , UI } from '@/constants' ;
1716import type { Decision , Mode , ThemeDefinition } from '@/types' ;
18- import { ollama , screen , tools } from '@/utils' ;
17+ import { ollama , tools } from '@/utils' ;
1918
2019import { ChatInput , type SubmittedInput } from './ChatInput' ;
2120import { ChatActionType , InterruptReason } from './constants' ;
22- import { useRunTurn } from './hooks' ;
21+ import { useCompact , useRunTurn } from './hooks' ;
2322import { chatReducer , createInitialChatState } from './reducer' ;
2423
2524interface Props {
@@ -34,32 +33,6 @@ interface Props {
3433 theme ?: ThemeDefinition ;
3534}
3635
37- /**
38- * Gets the latest user interaction plus the assistant response.
39- */
40- function getLatestTurn ( messages : ollama . Message [ ] ) : ollama . Message [ ] {
41- const latestAssistantIndex = messages . findLastIndex (
42- ( { role } ) => role === ROLE . ASSISTANT ,
43- ) ;
44-
45- if ( latestAssistantIndex >= 0 ) {
46- const latestUserIndex = messages
47- . slice ( 0 , latestAssistantIndex )
48- . findLastIndex ( ( { role } ) => role === ROLE . USER ) ;
49-
50- return [
51- // v8 ignore start
52- ...( latestUserIndex >= 0 ? [ messages [ latestUserIndex ] ] : [ ] ) ,
53- // v8 ignore stop
54- messages [ latestAssistantIndex ] ,
55- ] ;
56- }
57-
58- const latestUser = messages . findLast ( ( { role } ) => role === ROLE . USER ) ;
59- // v8 ignore next
60- return latestUser ? [ latestUser ] : [ ] ;
61- }
62-
6336export function Chat ( {
6437 initialMessages,
6538 model,
@@ -95,6 +68,16 @@ export function Chat({
9568 const abortControllerRef = useRef < AbortController | null > ( null ) ;
9669 const persistedSnapshotRef = useRef ( '' ) ;
9770 const [ compactError , setCompactError ] = useState < string | null > ( null ) ;
71+ const compact = useCompact ( {
72+ abortControllerRef,
73+ dispatch,
74+ model,
75+ onMessagesReplace,
76+ persistedSnapshotRef,
77+ sessionId,
78+ state : { isLoading, messages, pendingPlan, pendingToolCall } ,
79+ theme,
80+ } ) ;
9881
9982 useEffect ( ( ) => {
10083 dispatch ( {
@@ -131,117 +114,6 @@ export function Chat({
131114 } ) ;
132115 } , [ ] ) ;
133116
134- const handleCompact = useCallback ( async ( ) => {
135- // v8 ignore start
136- if ( isLoading || pendingPlan || pendingToolCall ) {
137- setCompactError ( 'Cannot compact while another action is pending.' ) ;
138- return ;
139- }
140- // v8 ignore stop
141-
142- if ( ! messages . length ) {
143- setCompactError ( 'Nothing to compact yet.' ) ;
144- return ;
145- }
146-
147- const modelName = model ;
148- // v8 ignore next
149- if ( ! modelName ) {
150- setCompactError ( 'Model is required.' ) ;
151- return ;
152- }
153-
154- const controller = new AbortController ( ) ;
155- abortControllerRef . current = controller ;
156- let summary = '' ;
157-
158- setCompactError ( null ) ;
159- dispatch ( {
160- type : ChatActionType . SetLoading ,
161- isLoading : true ,
162- } ) ;
163- dispatch ( {
164- type : ChatActionType . SetStreamingMessage ,
165- message : { role : ROLE . ASSISTANT , content : '' } ,
166- } ) ;
167-
168- try {
169- const compactionMessages : ollama . Message [ ] = [
170- ...messages ,
171- {
172- role : ROLE . USER ,
173- content : PROMPT . COMPACT_MESSAGES_INSTRUCTION ,
174- } ,
175- ] ;
176-
177- for await ( const chunk of ollama . streamChat (
178- compactionMessages ,
179- modelName ,
180- [ ] ,
181- controller . signal ,
182- ) ) {
183- // v8 ignore next 3
184- if ( chunk . type === 'content' ) {
185- summary = ollama . sanitizeAssistantContent ( summary + chunk . content ) ;
186- }
187- }
188-
189- summary = summary . trim ( ) ;
190- if ( ! summary ) {
191- throw new Error ( 'Compaction summary was empty' ) ;
192- }
193-
194- await prewarmCodeBlocks ( summary , theme ) ;
195-
196- const compactedMessages : ollama . Message [ ] = [
197- {
198- role : ROLE . SYSTEM ,
199- content : `Compacted conversation context:\n\n${ summary } ` ,
200- } ,
201- ...getLatestTurn ( messages ) ,
202- ] ;
203-
204- onMessagesReplace ?.( compactedMessages ) ;
205- persistedSnapshotRef . current = JSON . stringify ( compactedMessages ) ;
206- dispatch ( {
207- type : ChatActionType . CommitMessages ,
208- messages : compactedMessages ,
209- } ) ;
210- screen . clear ( sessionId ) ;
211- } catch ( error ) {
212- // v8 ignore start
213- if ( ! controller . signal . aborted ) {
214- setCompactError (
215- `Compaction failed: ${ error instanceof Error ? error . message : String ( error ) } ` ,
216- ) ;
217- }
218- } finally {
219- if ( abortControllerRef . current === controller ) {
220- abortControllerRef . current = null ;
221- }
222- // v8 ignore stop
223-
224- dispatch ( {
225- type : ChatActionType . SetLoading ,
226- isLoading : false ,
227- } ) ;
228-
229- dispatch ( {
230- type : ChatActionType . SetStreamingMessage ,
231- message : null ,
232- } ) ;
233- }
234- } , [
235- isLoading ,
236- messages ,
237- model ,
238- onMessagesReplace ,
239- pendingPlan ,
240- pendingToolCall ,
241- sessionId ,
242- theme ,
243- ] ) ;
244-
245117 const handlePlanReview = useCallback (
246118 async ( mode : Mode ) => {
247119 // v8 ignore next
@@ -375,7 +247,10 @@ export function Chat({
375247 }
376248
377249 if ( userContent === '/compact' && ! images ?. length ) {
378- await handleCompact ( ) ;
250+ const error = await compact ( ) ;
251+ if ( error ) {
252+ setCompactError ( error ) ;
253+ }
379254 return ;
380255 }
381256
@@ -403,7 +278,7 @@ export function Chat({
403278 await runTurn ( updatedMessages ) ;
404279 }
405280 } ,
406- [ handleCompact , messages , mode , onCommand , runTurn , runTurnReadOnly ] ,
281+ [ compact , messages , mode , onCommand , runTurn , runTurnReadOnly ] ,
407282 ) ;
408283
409284 return (
0 commit comments