@@ -3,13 +3,14 @@ import postgres from 'postgres';
33import {
44 chat ,
55 message ,
6+ type MessageDeprecated ,
67 messageDeprecated ,
78 vote ,
89 voteDeprecated ,
910} from '../schema' ;
1011import { drizzle } from 'drizzle-orm/postgres-js' ;
1112import { inArray } from 'drizzle-orm' ;
12- import { appendResponseMessages , UIMessage } from 'ai' ;
13+ import { appendResponseMessages , type UIMessage } from 'ai' ;
1314
1415config ( {
1516 path : '.env.local' ,
@@ -22,8 +23,8 @@ if (!process.env.POSTGRES_URL) {
2223const client = postgres ( process . env . POSTGRES_URL ) ;
2324const db = drizzle ( client ) ;
2425
25- const BATCH_SIZE = 50 ; // Process 10 chats at a time
26- const INSERT_BATCH_SIZE = 100 ; // Insert 100 messages at a time
26+ const BATCH_SIZE = 100 ; // Process 100 chats at a time
27+ const INSERT_BATCH_SIZE = 1000 ; // Insert 1000 messages at a time
2728
2829type NewMessageInsert = {
2930 id : string ;
@@ -40,16 +41,66 @@ type NewVoteInsert = {
4041 isUpvoted : boolean ;
4142} ;
4243
43- async function createNewTable ( ) {
44+ interface MessageDeprecatedContentPart {
45+ type : string ;
46+ content : unknown ;
47+ }
48+
49+ function getMessageRank ( message : MessageDeprecated ) : number {
50+ if (
51+ message . role === 'assistant' &&
52+ ( message . content as MessageDeprecatedContentPart [ ] ) . some (
53+ ( contentPart ) => contentPart . type === 'tool-call' ,
54+ )
55+ ) {
56+ return 0 ;
57+ }
58+
59+ if (
60+ message . role === 'tool' &&
61+ ( message . content as MessageDeprecatedContentPart [ ] ) . some (
62+ ( contentPart ) => contentPart . type === 'tool-result' ,
63+ )
64+ ) {
65+ return 1 ;
66+ }
67+
68+ if ( message . role === 'assistant' ) {
69+ return 2 ;
70+ }
71+
72+ return 3 ;
73+ }
74+
75+ function dedupeParts < T extends { type : string ; [ k : string ] : any } > (
76+ parts : T [ ] ,
77+ ) : T [ ] {
78+ const seen = new Set < string > ( ) ;
79+ return parts . filter ( ( p ) => {
80+ const key = `${ p . type } |${ JSON . stringify ( p . content ?? p ) } ` ;
81+ if ( seen . has ( key ) ) return false ;
82+ seen . add ( key ) ;
83+ return true ;
84+ } ) ;
85+ }
86+
87+ function sanitizeParts < T extends { type : string ; [ k : string ] : any } > (
88+ parts : T [ ] ,
89+ ) : T [ ] {
90+ return parts . filter (
91+ ( part ) => ! ( part . type === 'reasoning' && part . reasoning === 'undefined' ) ,
92+ ) ;
93+ }
94+
95+ async function migrateMessages ( ) {
4496 const chats = await db . select ( ) . from ( chat ) ;
97+
4598 let processedCount = 0 ;
4699
47- // Process chats in batches
48100 for ( let i = 0 ; i < chats . length ; i += BATCH_SIZE ) {
49101 const chatBatch = chats . slice ( i , i + BATCH_SIZE ) ;
50102 const chatIds = chatBatch . map ( ( chat ) => chat . id ) ;
51103
52- // Fetch all messages and votes for the current batch of chats in bulk
53104 const allMessages = await db
54105 . select ( )
55106 . from ( messageDeprecated )
@@ -60,20 +111,25 @@ async function createNewTable() {
60111 . from ( voteDeprecated )
61112 . where ( inArray ( voteDeprecated . chatId , chatIds ) ) ;
62113
63- // Prepare batches for insertion
64114 const newMessagesToInsert : NewMessageInsert [ ] = [ ] ;
65115 const newVotesToInsert : NewVoteInsert [ ] = [ ] ;
66116
67- // Process each chat in the batch
68117 for ( const chat of chatBatch ) {
69118 processedCount ++ ;
70119 console . info ( `Processed ${ processedCount } /${ chats . length } chats` ) ;
71120
72- // Filter messages and votes for this specific chat
73- const messages = allMessages . filter ( ( msg ) => msg . chatId === chat . id ) ;
121+ const messages = allMessages
122+ . filter ( ( message ) => message . chatId === chat . id )
123+ . sort ( ( a , b ) => {
124+ const differenceInTime =
125+ new Date ( a . createdAt ) . getTime ( ) - new Date ( b . createdAt ) . getTime ( ) ;
126+ if ( differenceInTime !== 0 ) return differenceInTime ;
127+
128+ return getMessageRank ( a ) - getMessageRank ( b ) ;
129+ } ) ;
130+
74131 const votes = allVotes . filter ( ( v ) => v . chatId === chat . id ) ;
75132
76- // Group messages into sections
77133 const messageSection : Array < UIMessage > = [ ] ;
78134 const messageSections : Array < Array < UIMessage > > = [ ] ;
79135
@@ -93,7 +149,6 @@ async function createNewTable() {
93149 messageSections . push ( [ ...messageSection ] ) ;
94150 }
95151
96- // Process each message section
97152 for ( const section of messageSections ) {
98153 const [ userMessage , ...assistantMessages ] = section ;
99154
@@ -121,10 +176,14 @@ async function createNewTable() {
121176 attachments : [ ] ,
122177 } as NewMessageInsert ;
123178 } else if ( message . role === 'assistant' ) {
179+ const cleanParts = sanitizeParts (
180+ dedupeParts ( message . parts || [ ] ) ,
181+ ) ;
182+
124183 return {
125184 id : message . id ,
126185 chatId : chat . id ,
127- parts : message . parts || [ ] ,
186+ parts : cleanParts ,
128187 role : message . role ,
129188 createdAt : message . createdAt ,
130189 attachments : [ ] ,
@@ -134,7 +193,6 @@ async function createNewTable() {
134193 } )
135194 . filter ( ( msg ) : msg is NewMessageInsert => msg !== null ) ;
136195
137- // Add messages to batch
138196 for ( const msg of projectedUISection ) {
139197 newMessagesToInsert . push ( msg ) ;
140198
@@ -155,11 +213,9 @@ async function createNewTable() {
155213 }
156214 }
157215
158- // Batch insert messages
159216 for ( let j = 0 ; j < newMessagesToInsert . length ; j += INSERT_BATCH_SIZE ) {
160217 const messageBatch = newMessagesToInsert . slice ( j , j + INSERT_BATCH_SIZE ) ;
161218 if ( messageBatch . length > 0 ) {
162- // Ensure all required fields are present
163219 const validMessageBatch = messageBatch . map ( ( msg ) => ( {
164220 id : msg . id ,
165221 chatId : msg . chatId ,
@@ -173,7 +229,6 @@ async function createNewTable() {
173229 }
174230 }
175231
176- // Batch insert votes
177232 for ( let j = 0 ; j < newVotesToInsert . length ; j += INSERT_BATCH_SIZE ) {
178233 const voteBatch = newVotesToInsert . slice ( j , j + INSERT_BATCH_SIZE ) ;
179234 if ( voteBatch . length > 0 ) {
@@ -185,7 +240,7 @@ async function createNewTable() {
185240 console . info ( `Migration completed: ${ processedCount } chats processed` ) ;
186241}
187242
188- createNewTable ( )
243+ migrateMessages ( )
189244 . then ( ( ) => {
190245 console . info ( 'Script completed successfully' ) ;
191246 process . exit ( 0 ) ;
0 commit comments