diff --git a/frontend/angular.json b/frontend/angular.json index 9b369998..a82b93b8 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -56,7 +56,18 @@ "src/styles/styles.scss", "src/styles/tailwind.scss" ], - "scripts": [] + "scripts": [ + "node_modules/prismjs/prism.js", + "node_modules/prismjs/components/prism-csharp.min.js", + "node_modules/prismjs/components/prism-bash.min.js", + "node_modules/prismjs/components/prism-css.min.js", + "node_modules/prismjs/components/prism-hcl.min.js", + "node_modules/prismjs/components/prism-javascript.min.js", + "node_modules/prismjs/components/prism-json.min.js", + "node_modules/prismjs/components/prism-python.min.js", + "node_modules/prismjs/components/prism-typescript.min.js", + "node_modules/prismjs/components/prism-yaml.min.js" + ] }, "configurations": { "production": { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6c62a45a..2da3ff05 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,11 +1,11 @@ { - "name": "fuse-angular", + "name": "@sophia/ui", "version": "20.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "fuse-angular", + "name": "@sophia/ui", "version": "20.0.0", "license": "https://themeforest.net/licenses/standard", "dependencies": { @@ -26,10 +26,10 @@ "highlight.js": "11.9.0", "lodash-es": "4.17.21", "luxon": "3.4.4", - "ng-apexcharts": "1.11.0", "ngx-markdown": "^18.1.0", "ngx-quill": "26.0.1", "perfect-scrollbar": "1.5.5", + "prismjs": "^1.29.0", "quill": "2.0.2", "rxjs": "7.8.1", "tslib": "2.6.2", @@ -39,6 +39,7 @@ "@angular-devkit/build-angular": "18.0.2", "@angular/cli": "18.0.2", "@angular/compiler-cli": "18.0.1", + "@ngx-rocket/scripts": "^5.2.3", "@tailwindcss/typography": "0.5.13", "@types/chroma-js": "2.4.4", "@types/crypto-js": "4.2.2", @@ -4602,6 +4603,121 @@ "webpack": "^5.54.0" } }, + "node_modules/@ngx-rocket/ascii-logo": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ngx-rocket/ascii-logo/-/ascii-logo-1.1.0.tgz", + "integrity": "sha512-iGTX2tIAQmo/z2xiOsTAMxvO4wwHJkmutTzfJ+T/0Ebig54e1L2kgvctFRVHSj86UQUn8duzpuzcJJ4K4NK4jg==", + "dev": true, + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ngx-rocket/scripts": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@ngx-rocket/scripts/-/scripts-5.2.3.tgz", + "integrity": "sha512-024H2U1H/07ISfkt/PMXOp3sl3+bWbncsjPVgxzU9eAAkNxkt56Z/Llzy46l6h2fsrGkjiGvUMFKGHWQawCgJg==", + "dev": true, + "dependencies": { + "@ngx-rocket/ascii-logo": "^1.1.0", + "chalk": "^4.1.0", + "fs-extra": "^10.0.0", + "lodash.get": "^4.4.2", + "minimist": "^1.2.5" + }, + "bin": { + "ngx-scripts": "bin/ngx-scripts" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@ngx-rocket/scripts/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@ngx-rocket/scripts/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@ngx-rocket/scripts/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@ngx-rocket/scripts/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@ngx-rocket/scripts/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@ngx-rocket/scripts/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@ngx-rocket/scripts/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -11484,6 +11600,12 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -12272,20 +12394,6 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/ng-apexcharts": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/ng-apexcharts/-/ng-apexcharts-1.11.0.tgz", - "integrity": "sha512-bzZ2qMYpJJ1V/Yfp9eC5y7BuxrlZx4fc9ev9pNHW8qEs5B0yE5yW25WrLhoGTYsfeFZfiADWeAmZkceEiihpFw==", - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "@angular/common": "^18.0.1", - "@angular/core": "^18.0.1", - "apexcharts": "^3.49.1", - "rxjs": "^6.5.5 || ^7.4.0" - } - }, "node_modules/ngx-markdown": { "version": "18.1.0", "resolved": "https://registry.npmjs.org/ngx-markdown/-/ngx-markdown-18.1.0.tgz", @@ -13702,7 +13810,6 @@ "version": "1.29.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", - "optional": true, "engines": { "node": ">=6" } diff --git a/frontend/package.json b/frontend/package.json index caa7619a..557c4987 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,10 +35,10 @@ "highlight.js": "11.9.0", "lodash-es": "4.17.21", "luxon": "3.4.4", - "ng-apexcharts": "1.11.0", "ngx-markdown": "^18.1.0", "ngx-quill": "26.0.1", "perfect-scrollbar": "1.5.5", + "prismjs": "^1.29.0", "quill": "2.0.2", "rxjs": "7.8.1", "tslib": "2.6.2", @@ -48,6 +48,7 @@ "@angular-devkit/build-angular": "18.0.2", "@angular/cli": "18.0.2", "@angular/compiler-cli": "18.0.1", + "@ngx-rocket/scripts": "^5.2.3", "@tailwindcss/typography": "0.5.13", "@types/chroma-js": "2.4.4", "@types/crypto-js": "4.2.2", @@ -75,4 +76,4 @@ "typescript": "5.4.5", "typescript-eslint": "8.2.0" } -} \ No newline at end of file +} diff --git a/frontend/public/images/splash.png b/frontend/public/images/splash.png new file mode 100644 index 00000000..c74d1f63 Binary files /dev/null and b/frontend/public/images/splash.png differ diff --git a/frontend/src/app/modules/actions/actions.component.html b/frontend/src/app/modules/actions/actions.component.html index cbed5be7..576c77e9 100644 --- a/frontend/src/app/modules/actions/actions.component.html +++ b/frontend/src/app/modules/actions/actions.component.html @@ -1,22 +1,13 @@
-
+
- -
-

- Actions -

-
+
Actions
- +
diff --git a/frontend/src/app/modules/admin/apps/chat/chat.component.ts b/frontend/src/app/modules/admin/apps/chat/chat.component.ts index 13e91170..3c5e2e43 100644 --- a/frontend/src/app/modules/admin/apps/chat/chat.component.ts +++ b/frontend/src/app/modules/admin/apps/chat/chat.component.ts @@ -13,9 +13,4 @@ import { RouterOutlet } from '@angular/router'; standalone: true, imports: [RouterOutlet], }) -export class ChatComponent { - /** - * Constructor - */ - constructor() {} -} +export class ChatComponent {} diff --git a/frontend/src/app/modules/admin/apps/chat/chat.service.ts b/frontend/src/app/modules/admin/apps/chat/chat.service.ts index 67086476..1f29799c 100644 --- a/frontend/src/app/modules/admin/apps/chat/chat.service.ts +++ b/frontend/src/app/modules/admin/apps/chat/chat.service.ts @@ -1,6 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Chat, LlmMessage } from 'app/modules/admin/apps/chat/chat.types'; +import {Chat, ChatMessage, LlmMessage} from 'app/modules/admin/apps/chat/chat.types'; import { BehaviorSubject, Observable, @@ -10,7 +10,7 @@ import { switchMap, take, tap, - throwError, + throwError, catchError, } from 'rxjs'; @Injectable({ providedIn: 'root' }) @@ -21,7 +21,10 @@ export class ChatService { /** * Constructor */ - constructor(private _httpClient: HttpClient) {} + constructor(private _httpClient: HttpClient) { + console.log('ChatService') + this.getChats(); + } // ----------------------------------------------------------------------------------------------------- // @ Accessors @@ -73,6 +76,9 @@ export class ChatService { tap(() => { const currentChats = this._chats.value || []; this._chats.next(currentChats.filter(chat => chat.id !== chatId)); + if (this._chat.getValue().id === chatId) { + this._chat.next(null); + } }) ); } @@ -85,7 +91,7 @@ export class ChatService { getChatById(id: string): Observable { if(!id?.trim()) { console.log(`nullish chat id "${id}"`) - const chat: Chat = {messages:[], id: null, title: ''} + const chat: Chat = {messages:[], id: null, title: '', updatedAt: Date.now() } this._chat.next(chat); return this._chats } @@ -104,12 +110,18 @@ export class ChatService { const llmMsg = msg as LlmMessage return { ...msg, - value: llmMsg.text, + value: llmMsg.content, isMine: llmMsg.role === 'user' } - }) + }), + updatedAt: chat.updatedAt } - console.log(chat) + // this._chats doesn't have the messages, so we need to update it when we load a chat + const chats = this._chats.getValue() + const chatIndex = chats.findIndex(chat => chat.id === id); + chats[chatIndex] = chat; + this._chats.next(chats); + this._chat.next(chat); // Return the chat @@ -177,50 +189,12 @@ export class ChatService { ); } - /** - * Update chat - * - * @param id - * @param chat - */ - updateChat2(id: string, chat: Chat): Observable { - return this.chats$.pipe( - take(1), - switchMap((chats) => - this._httpClient - .patch('api/apps/chat/chat', { - id, - chat, - }) - .pipe( - map((updatedChat) => { - // Find the index of the updated chat - const index = chats.findIndex( - (item) => item.id === id - ); - - // Update the chat - chats[index] = updatedChat; - - // Update the chats - this._chats.next(chats); - - // Update the chat if it's selected - this._chat.next(updatedChat); - - // Return the updated chat - return updatedChat; - }) - ) - ) - ); - } /** * Reset the selected chat */ resetChat(): void { - this._chat.next({ id: '', messages: [], title: '' }); + this._chat.next({ id: '', messages: [], title: '', updatedAt: Date.now() }); } @@ -241,31 +215,43 @@ export class ChatService { map((data: any) => { const llmMessage = data.data; - const newMessages = [ + const newMessages: ChatMessage[] = [ { - value: message, + content: message, isMine: true, }, { - value: llmMessage, + content: llmMessage, isMine: false, }, ] - // // Find the index of the updated chat + // Find the index of the updated chat const index = chats.findIndex( (item) => item.id === chatId ); - // - // // Update the chat - const chat = chats[index]; + if(index < 0) { + console.log(`Couldn't find chat with id ${chatId} from ${chats.length} chats`); + } + + // Update the chat + const chat = chats[index]; + if(chat.messages === null || chat.messages === undefined) { + console.log(`nullish messages for ${JSON.stringify(chat)} at index ${index}`) + chat.messages = [] + } chat.messages.push(...newMessages); - // // Update the chats + + // Move the chat to the top of the list + chats.splice(index, 1); + chats.unshift(chat); + + // Update the chats this._chats.next(chats); - // - // // Update the chat if it's selected + + // Update the chat if it's selected this._chat.next(chat); - // - // // Return the updated chat + + // Return the updated chat return chat; }) ) @@ -274,12 +260,54 @@ export class ChatService { } /** - * Send an audio message * * @param chatId + * @param message * @param llmId - * @param audio */ + regenerateMessage(chatId: string, message: string, llmId: string): Observable { + if (!chatId?.trim() || !message?.trim() || !llmId?.trim()) { + return throwError(() => new Error('Invalid parameters for regeneration')); + } + + return this.chats$.pipe( + take(1), + switchMap((chats) => { + const chatIndex = chats.findIndex(item => item.id === chatId); + if (chatIndex === -1) { + return throwError(() => new Error(`Chat not found: ${chatId}`)); + } + + return this._httpClient + .post(`api/chat/${chatId}/regenerate`, { text: message, llmId }) + .pipe( + map((data: any) => { + const llmMessage = data.data; + const newMessage = { + value: llmMessage, + isMine: false, + llmId: llmId, + }; + + const chat = chats[chatIndex]; + chat.messages.push(newMessage); + chat.lastMessage = llmMessage; + + // Update states + this._chats.next(chats); + this._chat.next(chat); + + return chat; + }), + catchError(error => { + console.error('Error regenerating message:', error); + return throwError(() => new Error('Failed to regenerate message')); + }) + ); + }) + ); + } + sendAudioMessage(chatId: string, llmId: string, audio: Blob): Observable { return this.chats$.pipe( take(1), diff --git a/frontend/src/app/modules/admin/apps/chat/chat.types.ts b/frontend/src/app/modules/admin/apps/chat/chat.types.ts index 1c3ee645..2004e316 100644 --- a/frontend/src/app/modules/admin/apps/chat/chat.types.ts +++ b/frontend/src/app/modules/admin/apps/chat/chat.types.ts @@ -33,9 +33,11 @@ export interface Contact { }; } + + export interface LlmMessage { role: 'system' | 'user' | 'assistant'; - text: string; + content: string; /** The LLM which generated the text (only when role=assistant) */ llmId?: string; /** Set the cache_control flag with Claude models */ @@ -43,6 +45,17 @@ export interface LlmMessage { time?: number; } +export interface ChatMessage { + id?: string; + chatId?: string; + contactId?: string; + isMine?: boolean; + content?: string; + llmId?: string; + createdAt?: string; + generating?: boolean; +} + export interface Chat { id: string; title: string; @@ -51,13 +64,6 @@ export interface Chat { unreadCount?: number; lastMessage?: string; lastMessageAt?: string; - messages?: { - id?: string; - chatId?: string; - contactId?: string; - isMine?: boolean; - value?: string; - llmId?: string; - createdAt?: string; - }[]; + updatedAt: number; + messages?: ChatMessage[]; } diff --git a/frontend/src/app/modules/admin/apps/chat/chats/chats.component.html b/frontend/src/app/modules/admin/apps/chat/chats/chats.component.html index f52582ab..1f1450a1 100644 --- a/frontend/src/app/modules/admin/apps/chat/chats/chats.component.html +++ b/frontend/src/app/modules/admin/apps/chat/chats/chats.component.html @@ -92,6 +92,7 @@ > {{ chat.title }}
+
+ @if (hoveredChatId === chat.id) { - + }
diff --git a/frontend/src/app/modules/admin/apps/chat/chats/chats.component.ts b/frontend/src/app/modules/admin/apps/chat/chats/chats.component.ts index 017fa8c6..f65c4a04 100644 --- a/frontend/src/app/modules/admin/apps/chat/chats/chats.component.ts +++ b/frontend/src/app/modules/admin/apps/chat/chats/chats.component.ts @@ -17,6 +17,7 @@ import { RouterLink, RouterOutlet } from '@angular/router'; import { ChatService } from 'app/modules/admin/apps/chat/chat.service'; import { Chat } from 'app/modules/admin/apps/chat/chat.types'; import { Subject, takeUntil } from 'rxjs'; +import { FuseConfirmationService } from '@fuse/services/confirmation'; @Component({ selector: 'chat-chats', @@ -49,6 +50,7 @@ export class ChatsComponent implements OnInit, OnDestroy { constructor( private _chatService: ChatService, private _changeDetectorRef: ChangeDetectorRef, + private confirmationService: FuseConfirmationService, ) {} // ----------------------------------------------------------------------------------------------------- @@ -113,6 +115,23 @@ export class ChatsComponent implements OnInit, OnDestroy { ); } + /** + * Delete the current chat + */ + deleteChat(event: MouseEvent, chat: Chat): void { + // event.stopPropagation(); + this.confirmationService.open({ + message: 'Are you sure you want to delete this chat?', + }).afterClosed().subscribe((result) => { + console.log(result); + if(result === 'confirmed') { + this._chatService.deleteChat(chat.id).subscribe(() => { + // Do we need to handle if it's the currently selected chat? + }); + } + }); + } + /** * Track by function for ngFor loops * diff --git a/frontend/src/app/modules/admin/apps/chat/conversation/conversation.component.html b/frontend/src/app/modules/admin/apps/chat/conversation/conversation.component.html index 335dc367..0798a052 100644 --- a/frontend/src/app/modules/admin/apps/chat/conversation/conversation.component.html +++ b/frontend/src/app/modules/admin/apps/chat/conversation/conversation.component.html @@ -115,9 +115,9 @@
@@ -130,9 +130,9 @@
@@ -143,12 +143,41 @@ >
} + + -
+ - > - + + @if (!message.isMine) { + + + + }
@@ -180,22 +209,25 @@
+
+ + -
-
- + + diff --git a/frontend/src/app/modules/agents/new-agent/new-agent.component.ts b/frontend/src/app/modules/agents/new-agent/new-agent.component.ts index 70f03f27..8ec4998e 100644 --- a/frontend/src/app/modules/agents/new-agent/new-agent.component.ts +++ b/frontend/src/app/modules/agents/new-agent/new-agent.component.ts @@ -24,6 +24,7 @@ import { LlmService } from "../services/llm.service"; import { map } from "rxjs"; import { MatProgressSpinner } from "@angular/material/progress-spinner"; import { MatCheckboxModule } from "@angular/material/checkbox"; +import {MatCard, MatCardContent} from "@angular/material/card"; interface StartAgentResponse { data: { @@ -38,23 +39,25 @@ const defaultType/*: AgentType*/ = 'codegen'; templateUrl: './new-agent.component.html', encapsulation: ViewEncapsulation.None, standalone: true, - imports: [ - MatIconModule, - FormsModule, - MatFormFieldModule, - NgClass, - MatInputModule, - TextFieldModule, - ReactiveFormsModule, - MatButtonToggleModule, - MatButtonModule, - MatSelectModule, - MatOptionModule, - MatCheckboxModule, - MatChipsModule, - MatDatepickerModule, - MatProgressSpinner, - ], + imports: [ + MatIconModule, + FormsModule, + MatFormFieldModule, + NgClass, + MatInputModule, + TextFieldModule, + ReactiveFormsModule, + MatButtonToggleModule, + MatButtonModule, + MatSelectModule, + MatOptionModule, + MatCheckboxModule, + MatChipsModule, + MatDatepickerModule, + MatProgressSpinner, + MatCard, + MatCardContent, + ], }) export class NewAgentComponent implements OnInit { functions: string[] = []; diff --git a/frontend/src/app/modules/code-review/edit/code-review-edit.component.html b/frontend/src/app/modules/code-review/edit/code-review-edit.component.html index 57d390cd..5ff8318f 100644 --- a/frontend/src/app/modules/code-review/edit/code-review-edit.component.html +++ b/frontend/src/app/modules/code-review/edit/code-review-edit.component.html @@ -1,18 +1,15 @@
- -
-
-
-

- {{ configId ? 'Edit' : 'Create' }} Code Review Configuration -

-
+ + +
+
+
{{ configId ? 'Edit' : 'Create' }} Code Review Configuration
+
-
-
-
+ +
Description @@ -137,6 +134,6 @@

-
-
+ +
diff --git a/frontend/src/app/modules/code-review/edit/code-review-edit.component.ts b/frontend/src/app/modules/code-review/edit/code-review-edit.component.ts index 4432cfc6..1f58c16e 100644 --- a/frontend/src/app/modules/code-review/edit/code-review-edit.component.ts +++ b/frontend/src/app/modules/code-review/edit/code-review-edit.component.ts @@ -17,22 +17,25 @@ import {MatButtonModule} from "@angular/material/button"; import {MatFormFieldModule} from "@angular/material/form-field"; import {MatIconModule} from "@angular/material/icon"; import {MatInputModule} from "@angular/material/input"; +import {MatCard, MatCardContent} from "@angular/material/card"; @Component({ selector: 'app-code-review-edit', templateUrl: './code-review-edit.component.html', standalone: true, - imports: [ - CommonModule, - MatSnackBarModule, - ReactiveFormsModule, - MatButtonModule, - MatFormFieldModule, - MatChipsModule, - MatIconModule, - MatFormFieldModule, // Add this line - MatInputModule, // Add this line - ], + imports: [ + CommonModule, + MatSnackBarModule, + ReactiveFormsModule, + MatButtonModule, + MatFormFieldModule, + MatChipsModule, + MatIconModule, + MatFormFieldModule, + MatInputModule, + MatCard, + MatCardContent, + ], }) export class CodeReviewEditComponent implements OnInit { editForm: FormGroup; diff --git a/frontend/src/app/modules/profile/account/account.component.html b/frontend/src/app/modules/profile/account/account.component.html index 97ad701e..119c817a 100644 --- a/frontend/src/app/modules/profile/account/account.component.html +++ b/frontend/src/app/modules/profile/account/account.component.html @@ -55,48 +55,55 @@
LLM API Keys
- +
Anthropic
- +
OpenAI
- +
Groq
- +
TogetherAI
- +
Fireworks
- +
DeepSeek
+ +
+ + Cerebras + + +
diff --git a/frontend/src/app/modules/profile/account/account.component.ts b/frontend/src/app/modules/profile/account/account.component.ts index ede23372..70caebd9 100644 --- a/frontend/src/app/modules/profile/account/account.component.ts +++ b/frontend/src/app/modules/profile/account/account.component.ts @@ -64,6 +64,7 @@ export class SettingsAccountComponent implements OnInit { togetheraiKey: new FormControl(''), fireworksKey: new FormControl(''), deepseekKey: new FormControl(''), + cerebrasKey: new FormControl(''), }), functionConfig: new FormGroup({ GitHub: new FormGroup({ diff --git a/frontend/src/index.html b/frontend/src/index.html index a4c5e57b..4f56a8d6 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -44,9 +44,9 @@ - + + Sophia logo +
@@ -56,5 +56,6 @@ + diff --git a/package-lock.json b/package-lock.json index 308677a5..fdd96768 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,8 +49,7 @@ "@types/axios": "^0.14.0", "@types/chai": "^4.3.16", "@types/pg": "^8.11.4", - "ai": "~3.4", - "anthropic-vertex-ai": "github:nalaso/anthropic-vertex-ai", + "ai": "3.4.20", "api": "^6.1.1", "axios": "^1.7.2", "axios-retry": "^4.1.0", @@ -62,7 +61,6 @@ "fast-glob": "^3.3.2", "fast-xml-parser": "^4.3.6", "fastify": "^4.26.2", - "fastify-cors": "^6.1.0", "fastify-healthcheck": "^4.4.0", "fastify-raw-body": "^4.3.0", "firebase-admin": "^12.1.0", @@ -265,13 +263,13 @@ } }, "node_modules/@ai-sdk/react": { - "version": "0.0.59", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-0.0.59.tgz", - "integrity": "sha512-1WbgO3J2/OoheMuNMxy5itJ3NVqOpqpAQxFNp7AoXgnDv4wDF4kTif61rTlKh7dCPvBHj2HXLmob+TrVFaWhYw==", + "version": "0.0.66", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-0.0.66.tgz", + "integrity": "sha512-hSHm+KR8/ciq2NksxZVyRIibODVrEKPbBrdAzHXI/tmFbpz/9/a/VibgX2+EIlj/M9K8Hkt1epxGLWKRwIfBwg==", "dependencies": { - "@ai-sdk/provider-utils": "1.0.19", - "@ai-sdk/ui-utils": "0.0.44", - "swr": "2.2.5" + "@ai-sdk/provider-utils": "1.0.22", + "@ai-sdk/ui-utils": "0.0.48", + "swr": "^2.2.5" }, "engines": { "node": ">=18" @@ -289,13 +287,46 @@ } } }, + "node_modules/@ai-sdk/react/node_modules/@ai-sdk/provider": { + "version": "0.0.26", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.26.tgz", + "integrity": "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/react/node_modules/@ai-sdk/provider-utils": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.22.tgz", + "integrity": "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==", + "dependencies": { + "@ai-sdk/provider": "0.0.26", + "eventsource-parser": "^1.1.2", + "nanoid": "^3.3.7", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, "node_modules/@ai-sdk/solid": { - "version": "0.0.47", - "resolved": "https://registry.npmjs.org/@ai-sdk/solid/-/solid-0.0.47.tgz", - "integrity": "sha512-lVMxIxtuNqoo/TObSFGflEP2dUeJv7bfPQbS4jHTZGBNlyhgBRY2Xc19yNjA3QKRfvQNDVoQusqxn+18MiHJJQ==", + "version": "0.0.52", + "resolved": "https://registry.npmjs.org/@ai-sdk/solid/-/solid-0.0.52.tgz", + "integrity": "sha512-DPTWHLbMlrOMs8VAXW/finVLSlZDA2JbUKCH6fnlrr29L6Go9C1+xai92iBFF7GVb/bM/pVXXmuuoXWOGWknUQ==", "dependencies": { - "@ai-sdk/provider-utils": "1.0.19", - "@ai-sdk/ui-utils": "0.0.44" + "@ai-sdk/provider-utils": "1.0.22", + "@ai-sdk/ui-utils": "0.0.48" }, "engines": { "node": ">=18" @@ -309,14 +340,47 @@ } } }, + "node_modules/@ai-sdk/solid/node_modules/@ai-sdk/provider": { + "version": "0.0.26", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.26.tgz", + "integrity": "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/solid/node_modules/@ai-sdk/provider-utils": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.22.tgz", + "integrity": "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==", + "dependencies": { + "@ai-sdk/provider": "0.0.26", + "eventsource-parser": "^1.1.2", + "nanoid": "^3.3.7", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, "node_modules/@ai-sdk/svelte": { - "version": "0.0.49", - "resolved": "https://registry.npmjs.org/@ai-sdk/svelte/-/svelte-0.0.49.tgz", - "integrity": "sha512-gV0MhaWxkatjf7uJrCAHO3bWrihokNUwGhuMCgyG+y53lwJKAYhR0zCoDRM2HnTJ89fdnx/PVe3R9fOWEVY5qA==", + "version": "0.0.54", + "resolved": "https://registry.npmjs.org/@ai-sdk/svelte/-/svelte-0.0.54.tgz", + "integrity": "sha512-ZON1RjiKmFYYFB2ukswE2Q6Ca/MjfFlpwCjtmFYPDa6BAXS63ZcgId9l2BkbmOJLJWzZFwmlo905iaNFQDaWAw==", "dependencies": { - "@ai-sdk/provider-utils": "1.0.19", - "@ai-sdk/ui-utils": "0.0.44", - "sswr": "2.1.0" + "@ai-sdk/provider-utils": "1.0.22", + "@ai-sdk/ui-utils": "0.0.48", + "sswr": "^2.1.0" }, "engines": { "node": ">=18" @@ -330,16 +394,82 @@ } } }, + "node_modules/@ai-sdk/svelte/node_modules/@ai-sdk/provider": { + "version": "0.0.26", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.26.tgz", + "integrity": "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/svelte/node_modules/@ai-sdk/provider-utils": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.22.tgz", + "integrity": "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==", + "dependencies": { + "@ai-sdk/provider": "0.0.26", + "eventsource-parser": "^1.1.2", + "nanoid": "^3.3.7", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, "node_modules/@ai-sdk/ui-utils": { - "version": "0.0.44", - "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-0.0.44.tgz", - "integrity": "sha512-0qiyun/n5zqJzQs/WfQT86dZE5DiDhSHJc7b7ZGLYvNMztHkRQmak2zUCZP4IyGVZEicyEPQK6NEEpBgkmd3Dg==", + "version": "0.0.48", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-0.0.48.tgz", + "integrity": "sha512-kKol0xTG4OFDqu77WqVVpPvpABXIXs67Ivlqg6qxD69pD/cFZ2zQVliBcDxPBJoz08Oyf5BxU5Ht9i8J99QKcA==", "dependencies": { - "@ai-sdk/provider": "0.0.23", - "@ai-sdk/provider-utils": "1.0.19", - "json-schema": "0.4.0", - "secure-json-parse": "2.7.0", - "zod-to-json-schema": "3.23.2" + "@ai-sdk/provider": "0.0.26", + "@ai-sdk/provider-utils": "1.0.22", + "json-schema": "^0.4.0", + "secure-json-parse": "^2.7.0", + "zod-to-json-schema": "^3.23.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/ui-utils/node_modules/@ai-sdk/provider": { + "version": "0.0.26", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.26.tgz", + "integrity": "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/ui-utils/node_modules/@ai-sdk/provider-utils": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.22.tgz", + "integrity": "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==", + "dependencies": { + "@ai-sdk/provider": "0.0.26", + "eventsource-parser": "^1.1.2", + "nanoid": "^3.3.7", + "secure-json-parse": "^2.7.0" }, "engines": { "node": ">=18" @@ -354,13 +484,13 @@ } }, "node_modules/@ai-sdk/vue": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@ai-sdk/vue/-/vue-0.0.51.tgz", - "integrity": "sha512-6RjuuRGf749EjnsfbETJpF0fmq6a1lF6qUUUnd/Q1Ojf0tX8fI4qwvNykbECZHWuIj42EqZ3HDuNNR9c8oG4rA==", + "version": "0.0.57", + "resolved": "https://registry.npmjs.org/@ai-sdk/vue/-/vue-0.0.57.tgz", + "integrity": "sha512-p4wwEO/KRfZaRUZu7UIOlb0odHDHTuUsFEFQ2zlFQK0IlCoTvtqJkSyQl26o+6whiUUUNVqZepluX+WHGSrAaA==", "dependencies": { - "@ai-sdk/provider-utils": "1.0.19", - "@ai-sdk/ui-utils": "0.0.44", - "swrv": "1.0.4" + "@ai-sdk/provider-utils": "1.0.22", + "@ai-sdk/ui-utils": "0.0.48", + "swrv": "^1.0.4" }, "engines": { "node": ">=18" @@ -374,6 +504,39 @@ } } }, + "node_modules/@ai-sdk/vue/node_modules/@ai-sdk/provider": { + "version": "0.0.26", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.26.tgz", + "integrity": "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/vue/node_modules/@ai-sdk/provider-utils": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.22.tgz", + "integrity": "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==", + "dependencies": { + "@ai-sdk/provider": "0.0.26", + "eventsource-parser": "^1.1.2", + "nanoid": "^3.3.7", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -1348,18 +1511,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "peer": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "engines": { "node": ">=6.9.0" } @@ -1442,12 +1605,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", - "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "version": "7.26.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.1.tgz", + "integrity": "sha512-reoQYNiAJreZNsJzyrDNzFQ+IQ5JFiIzAHJg9bn94S3l+4++J7RsIhNMoB+lgP/9tpmiAQqspv+xfdxTSzREOw==", "peer": true, "dependencies": { - "@babel/types": "^7.25.6" + "@babel/types": "^7.26.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -1469,14 +1632,13 @@ } }, "node_modules/@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", - "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "peer": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -8463,13 +8625,13 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.8.tgz", - "integrity": "sha512-Uzlxp91EPjfbpeO5KtC0KnXPkuTfGsNDeaKQJxQN718uz+RqDYarEf7UhQJGK+ZYloD2taUbHTI2J4WrUaZQNA==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.12.tgz", + "integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==", "peer": true, "dependencies": { "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.8", + "@vue/shared": "3.5.12", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" @@ -8482,26 +8644,26 @@ "peer": true }, "node_modules/@vue/compiler-dom": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.8.tgz", - "integrity": "sha512-GUNHWvoDSbSa5ZSHT9SnV5WkStWfzJwwTd6NMGzilOE/HM5j+9EB9zGXdtu/fCNEmctBqMs6C9SvVPpVPuk1Eg==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz", + "integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==", "peer": true, "dependencies": { - "@vue/compiler-core": "3.5.8", - "@vue/shared": "3.5.8" + "@vue/compiler-core": "3.5.12", + "@vue/shared": "3.5.12" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.8.tgz", - "integrity": "sha512-taYpngQtSysrvO9GULaOSwcG5q821zCoIQBtQQSx7Uf7DxpR6CIHR90toPr9QfDD2mqHQPCSgoWBvJu0yV9zjg==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz", + "integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==", "peer": true, "dependencies": { "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.8", - "@vue/compiler-dom": "3.5.8", - "@vue/compiler-ssr": "3.5.8", - "@vue/shared": "3.5.8", + "@vue/compiler-core": "3.5.12", + "@vue/compiler-dom": "3.5.12", + "@vue/compiler-ssr": "3.5.12", + "@vue/shared": "3.5.12", "estree-walker": "^2.0.2", "magic-string": "^0.30.11", "postcss": "^8.4.47", @@ -8515,63 +8677,63 @@ "peer": true }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.8.tgz", - "integrity": "sha512-W96PtryNsNG9u0ZnN5Q5j27Z/feGrFV6zy9q5tzJVyJaLiwYxvC0ek4IXClZygyhjm+XKM7WD9pdKi/wIRVC/Q==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz", + "integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==", "peer": true, "dependencies": { - "@vue/compiler-dom": "3.5.8", - "@vue/shared": "3.5.8" + "@vue/compiler-dom": "3.5.12", + "@vue/shared": "3.5.12" } }, "node_modules/@vue/reactivity": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.8.tgz", - "integrity": "sha512-mlgUyFHLCUZcAYkqvzYnlBRCh0t5ZQfLYit7nukn1GR96gc48Bp4B7OIcSfVSvlG1k3BPfD+p22gi1t2n9tsXg==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.12.tgz", + "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==", "peer": true, "dependencies": { - "@vue/shared": "3.5.8" + "@vue/shared": "3.5.12" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.8.tgz", - "integrity": "sha512-fJuPelh64agZ8vKkZgp5iCkPaEqFJsYzxLk9vSC0X3G8ppknclNDr61gDc45yBGTaN5Xqc1qZWU3/NoaBMHcjQ==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.12.tgz", + "integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==", "peer": true, "dependencies": { - "@vue/reactivity": "3.5.8", - "@vue/shared": "3.5.8" + "@vue/reactivity": "3.5.12", + "@vue/shared": "3.5.12" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.8.tgz", - "integrity": "sha512-DpAUz+PKjTZPUOB6zJgkxVI3GuYc2iWZiNeeHQUw53kdrparSTG6HeXUrYDjaam8dVsCdvQxDz6ZWxnyjccUjQ==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.12.tgz", + "integrity": "sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==", "peer": true, "dependencies": { - "@vue/reactivity": "3.5.8", - "@vue/runtime-core": "3.5.8", - "@vue/shared": "3.5.8", + "@vue/reactivity": "3.5.12", + "@vue/runtime-core": "3.5.12", + "@vue/shared": "3.5.12", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.8.tgz", - "integrity": "sha512-7AmC9/mEeV9mmXNVyUIm1a1AjUhyeeGNbkLh39J00E7iPeGks8OGRB5blJiMmvqSh8SkaS7jkLWSpXtxUCeagA==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.12.tgz", + "integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==", "peer": true, "dependencies": { - "@vue/compiler-ssr": "3.5.8", - "@vue/shared": "3.5.8" + "@vue/compiler-ssr": "3.5.12", + "@vue/shared": "3.5.12" }, "peerDependencies": { - "vue": "3.5.8" + "vue": "3.5.12" } }, "node_modules/@vue/shared": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.8.tgz", - "integrity": "sha512-mJleSWbAGySd2RJdX1RBtcrUBX6snyOc0qHpgk3lGi4l9/P/3ny3ELqFWqYdkXIwwNN/kdm8nD9ky8o6l/Lx2A==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", + "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", "peer": true }, "node_modules/abbrev": { @@ -8702,24 +8864,23 @@ } }, "node_modules/ai": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ai/-/ai-3.4.0.tgz", - "integrity": "sha512-79MZD3zoljLBf7Jtmd39bhDA3W7WPsozIi99sLrZlgxzh3JBX1XtCxHwrUYHQtAxl21BGzHmXpgNfLiQjdTAfA==", - "dependencies": { - "@ai-sdk/provider": "0.0.23", - "@ai-sdk/provider-utils": "1.0.19", - "@ai-sdk/react": "0.0.59", - "@ai-sdk/solid": "0.0.47", - "@ai-sdk/svelte": "0.0.49", - "@ai-sdk/ui-utils": "0.0.44", - "@ai-sdk/vue": "0.0.51", + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/ai/-/ai-3.4.20.tgz", + "integrity": "sha512-UnU7VoIzwx0sql5W+39oLazmiovNRqX+AUkvLwuzlRWrp0O9BjqA/ZlvdmfBxhBPZMruzvL659q8ET38l04fUw==", + "dependencies": { + "@ai-sdk/provider": "0.0.26", + "@ai-sdk/provider-utils": "1.0.22", + "@ai-sdk/react": "0.0.66", + "@ai-sdk/solid": "0.0.52", + "@ai-sdk/svelte": "0.0.54", + "@ai-sdk/ui-utils": "0.0.48", + "@ai-sdk/vue": "0.0.57", "@opentelemetry/api": "1.9.0", "eventsource-parser": "1.1.2", - "json-schema": "0.4.0", + "json-schema": "^0.4.0", "jsondiffpatch": "0.6.0", - "nanoid": "3.3.6", - "secure-json-parse": "2.7.0", - "zod-to-json-schema": "3.23.2" + "secure-json-parse": "^2.7.0", + "zod-to-json-schema": "^3.23.3" }, "engines": { "node": ">=18" @@ -8749,6 +8910,39 @@ } } }, + "node_modules/ai/node_modules/@ai-sdk/provider": { + "version": "0.0.26", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.26.tgz", + "integrity": "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ai/node_modules/@ai-sdk/provider-utils": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.22.tgz", + "integrity": "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==", + "dependencies": { + "@ai-sdk/provider": "0.0.26", + "eventsource-parser": "^1.1.2", + "nanoid": "^3.3.7", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, "node_modules/ai/node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -8757,23 +8951,6 @@ "node": ">=8.0.0" } }, - "node_modules/ai/node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -8904,71 +9081,6 @@ "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", "dev": true }, - "node_modules/anthropic-vertex-ai": { - "version": "1.0.0", - "resolved": "git+ssh://git@github.com/nalaso/anthropic-vertex-ai.git#1733d089505e67496cdc83654af13e85917073b7", - "dependencies": { - "@ai-sdk/provider": "0.0.22", - "@ai-sdk/provider-utils": "1.0.17", - "google-auth-library": "^9.12.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.0.0" - } - }, - "node_modules/anthropic-vertex-ai/node_modules/@ai-sdk/provider": { - "version": "0.0.22", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.22.tgz", - "integrity": "sha512-smZ1/2jL/JSKnbhC6ama/PxI2D/psj+YAe0c0qpd5ComQCNFltg72VFf0rpUSFMmFuj1pCCNoBOCrvyl8HTZHQ==", - "dependencies": { - "json-schema": "0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/anthropic-vertex-ai/node_modules/@ai-sdk/provider-utils": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.17.tgz", - "integrity": "sha512-2VyeTH5DQ6AxqvwdyytKIeiZyYTyJffpufWjE67zM2sXMIHgYl7fivo8m5wVl6Cbf1dFPSGKq//C9s+lz+NHrQ==", - "dependencies": { - "@ai-sdk/provider": "0.0.22", - "eventsource-parser": "1.1.2", - "nanoid": "3.3.6", - "secure-json-parse": "2.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/anthropic-vertex-ai/node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -9281,9 +9393,9 @@ "license": "Python-2.0" }, "node_modules/aria-query": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", - "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "peer": true, "engines": { "node": ">= 0.4" @@ -13817,36 +13929,6 @@ "toad-cache": "^3.3.0" } }, - "node_modules/fastify-cors": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/fastify-cors/-/fastify-cors-6.1.0.tgz", - "integrity": "sha512-QBKz32IoY/iuT74CunRY1XOSpjSTIOh9E3FxulXIBhd0D2vdgG0kDvy0eG6HA/88sRfWHeba43LkGEXPz0Rh8g==", - "deprecated": "Please use @fastify/cors@7.0.0 instead", - "dependencies": { - "fastify-cors-deprecated": "npm:fastify-cors@6.0.3", - "process-warning": "^1.0.0" - } - }, - "node_modules/fastify-cors-deprecated": { - "name": "fastify-cors", - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/fastify-cors/-/fastify-cors-6.0.3.tgz", - "integrity": "sha512-fMbXubKKyBHHCfSBtsCi3+7VyVRdhJQmGes5gM+eGKkRErCdm0NaYO0ozd31BQBL1ycoTIjbqOZhJo4RTF/Vlg==", - "dependencies": { - "fastify-plugin": "^3.0.0", - "vary": "^1.1.2" - } - }, - "node_modules/fastify-cors-deprecated/node_modules/fastify-plugin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.1.tgz", - "integrity": "sha512-qKcDXmuZadJqdTm6vlCqioEbyewF60b/0LOFCcYN1B6BIZGlYJumWWOYs70SFYLDAH4YqdE1cxH/RKMG7rFxgA==" - }, - "node_modules/fastify-cors/node_modules/process-warning": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", - "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==" - }, "node_modules/fastify-healthcheck": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/fastify-healthcheck/-/fastify-healthcheck-4.4.0.tgz", @@ -18535,9 +18617,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", "peer": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -24289,15 +24371,6 @@ "node": ">=0.6.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -25220,16 +25293,16 @@ } }, "node_modules/vue": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.8.tgz", - "integrity": "sha512-hvuvuCy51nP/1fSRvrrIqTLSvrSyz2Pq+KQ8S8SXCxTWVE0nMaOnSDnSOxV1eYmGfvK7mqiwvd1C59CEEz7dAQ==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.12.tgz", + "integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==", "peer": true, "dependencies": { - "@vue/compiler-dom": "3.5.8", - "@vue/compiler-sfc": "3.5.8", - "@vue/runtime-dom": "3.5.8", - "@vue/server-renderer": "3.5.8", - "@vue/shared": "3.5.8" + "@vue/compiler-dom": "3.5.12", + "@vue/compiler-sfc": "3.5.12", + "@vue/runtime-dom": "3.5.12", + "@vue/server-renderer": "3.5.12", + "@vue/shared": "3.5.12" }, "peerDependencies": { "typescript": "*" @@ -25896,9 +25969,9 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.23.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.2.tgz", - "integrity": "sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw==", + "version": "3.23.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.5.tgz", + "integrity": "sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==", "peerDependencies": { "zod": "^3.23.3" } diff --git a/package.json b/package.json index ca626a3a..f0cf40a7 100644 --- a/package.json +++ b/package.json @@ -89,8 +89,7 @@ "@types/axios": "^0.14.0", "@types/chai": "^4.3.16", "@types/pg": "^8.11.4", - "ai": "~3.4", - "anthropic-vertex-ai": "github:nalaso/anthropic-vertex-ai", + "ai": "3.4.20", "api": "^6.1.1", "axios": "^1.7.2", "axios-retry": "^4.1.0", @@ -102,7 +101,6 @@ "fast-glob": "^3.3.2", "fast-xml-parser": "^4.3.6", "fastify": "^4.26.2", - "fastify-cors": "^6.1.0", "fastify-healthcheck": "^4.4.0", "fastify-raw-body": "^4.3.0", "firebase-admin": "^12.1.0", diff --git a/src/agent/cachingCodeGenAgentRunner.ts b/src/agent/cachingCodeGenAgentRunner.ts index faac1433..50e13903 100644 --- a/src/agent/cachingCodeGenAgentRunner.ts +++ b/src/agent/cachingCodeGenAgentRunner.ts @@ -128,13 +128,13 @@ export async function runCachingCodegenAgent(agent: AgentContext): Promise response as per the system instructions provided given the user request, available functions, memory items and recent function call history' + content: planningPrompt, // 'Generate a response as per the system instructions provided given the user request, available functions, memory items and recent function call history' }; - agent.messages[6] = { role: 'assistant', text: '' }; + agent.messages[6] = { role: 'assistant', content: '' }; agent.messages.length = 7; // If we've restarted remove any extra messages const agentPlanResponse: string = `\n${await agentLLM.generateTextFromMessages(agent.messages, { id: 'dynamicAgentPlan', stopSequences, temperature: 0.6, })}`; - agent.messages[6] = { role: 'assistant', text: agentPlanResponse }; + agent.messages[6] = { role: 'assistant', content: agentPlanResponse }; // Code gen for function calling ----------- agent.messages[7] = { role: 'user', - text: codingPrompt, // 'Generate a coding response as per the system instructions provided given the user request, memory items, recent function call history and plan' + content: codingPrompt, // 'Generate a coding response as per the system instructions provided given the user request, memory items, recent function call history and plan' }; - agent.messages[8] = { role: 'assistant', text: '' }; + agent.messages[8] = { role: 'assistant', content: '' }; const agentCodeResponse: string = `\n${await agentLLM.generateTextFromMessages(agent.messages, { id: 'dynamicAgentCode', stopSequences, temperature: 0.7, })}`; console.log(agentCodeResponse); - agent.messages[8] = { role: 'assistant', text: agentCodeResponse }; + agent.messages[8] = { role: 'assistant', content: agentCodeResponse }; const llmPythonCode = extractPythonCode(agentCodeResponse); // Function calling ---------------- @@ -327,7 +327,7 @@ main()`.trim(); // TODO output any saved memory items agent.messages[9] = { role: 'user', - text: `${pythonScriptResult}\nReview the results of the scripts and make any observations about the output/errors, then provide an updated planning response.`, + content: `${pythonScriptResult}\nReview the results of the scripts and make any observations about the output/errors, then provide an updated planning response.`, }; currentFunctionHistorySize = agent.functionCallHistory.length; diff --git a/src/chat/chatService.test.ts b/src/chat/chatService.test.ts index 988a6b82..6885ea25 100644 --- a/src/chat/chatService.test.ts +++ b/src/chat/chatService.test.ts @@ -15,8 +15,8 @@ export function runChatServiceTests(createService: () => ChatService, beforeEach const sampleChat: Chat = { id: 'test-chat-id', messages: [ - { role: 'user', text: 'Hello' }, - { role: 'assistant', text: 'Hi there! How can I help you?' }, + { role: 'user', content: 'Hello' }, + { role: 'assistant', content: 'Hi there! How can I help you?' }, ], updatedAt: Date.now(), userId: SINGLE_USER_ID, @@ -63,7 +63,7 @@ export function runChatServiceTests(createService: () => ChatService, beforeEach userId: SINGLE_USER_ID, visibility: 'private', title: 'test', - messages: [{ role: 'user', text: 'Parent message' }], + messages: [{ role: 'user', content: 'Parent message' }], updatedAt: Date.now(), parentId: undefined, rootId: undefined, @@ -77,7 +77,7 @@ export function runChatServiceTests(createService: () => ChatService, beforeEach rootId: parentChat.id, title: 'test', updatedAt: Date.now(), - messages: [{ role: 'assistant', text: 'Child message' }], + messages: [{ role: 'assistant', content: 'Child message' }], }; await service.saveChat(parentChat); diff --git a/src/chat/chatTypes.ts b/src/chat/chatTypes.ts index 74e85989..58331c22 100644 --- a/src/chat/chatTypes.ts +++ b/src/chat/chatTypes.ts @@ -15,6 +15,8 @@ export interface Chat { export type ChatPreview = Omit; +export const CHAT_PREVIEW_KEYS: Array = ['id', 'userId', 'visibility', 'title', 'updatedAt', 'parentId', 'rootId']; + export interface ChatList { chats: ChatPreview[]; hasMore: boolean; diff --git a/src/cli/util.ts b/src/cli/util.ts index bc357cf2..99b7055b 100644 --- a/src/cli/util.ts +++ b/src/cli/util.ts @@ -7,7 +7,7 @@ import { GitLab } from '#functions/scm/gitlab'; import { FileSystemService } from '#functions/storage/fileSystemService'; import { Claude3_Opus, ClaudeLLMs } from '#llm/models/anthropic'; -import { Claude3_5_Sonnet_Vertex, Claude3_Haiku_Vertex, Claude3_Sonnet_Vertex, ClaudeVertexLLMs } from '#llm/models/anthropic-vertex'; +import { Claude3_5_Sonnet_Vertex, Claude3_Haiku_Vertex, ClaudeVertexLLMs } from '#llm/models/anthropic-vertex'; import { GPT4o } from '#llm/models/openai'; import { Gemini_1_5_Pro } from '#llm/models/vertexai'; import { MultiLLM } from '#llm/multi-llm'; diff --git a/src/llm/base-llm.ts b/src/llm/base-llm.ts index d68bdf78..2108cec3 100644 --- a/src/llm/base-llm.ts +++ b/src/llm/base-llm.ts @@ -100,11 +100,10 @@ export abstract class BaseLLM implements LLM { async generateTextFromMessages(llmMessages: LlmMessage[], opts?: GenerateTextOptions): Promise { return withActiveSpan(`generateTextFromMessages ${opts?.id ?? ''}`, async (span) => { const messages: CoreMessage[] = llmMessages.map((msg) => { - const coreMsg: CoreMessage = { role: msg.role, content: msg.text }; if (msg.cache === 'ephemeral') { - coreMsg.experimental_providerMetadata = { anthropic: { cacheControl: { type: 'ephemeral' } } }; + msg.experimental_providerMetadata = { anthropic: { cacheControl: { type: 'ephemeral' } } }; } - return coreMsg; + return msg; }); const prompt = messages.map((m) => m.content).join('\n'); @@ -177,11 +176,10 @@ export abstract class BaseLLM implements LLM { async streamText(llmMessages: LlmMessage[], onChunk: ({ string }) => void, opts?: GenerateTextOptions): Promise> { return withActiveSpan(`streamText ${opts?.id ?? ''}`, async (span) => { const messages: CoreMessage[] = llmMessages.map((msg) => { - const coreMsg: CoreMessage = { role: msg.role, content: msg.text }; if (msg.cache === 'ephemeral') { - coreMsg.experimental_providerMetadata = { anthropic: { cacheControl: { type: 'ephemeral' } } }; + msg.experimental_providerMetadata = { anthropic: { cacheControl: { type: 'ephemeral' } } }; } - return coreMsg; + return msg; }); const prompt = messages.map((m) => m.content).join('\n'); diff --git a/src/llm/llm.ts b/src/llm/llm.ts index c59990fd..7393eb96 100644 --- a/src/llm/llm.ts +++ b/src/llm/llm.ts @@ -1,5 +1,5 @@ // https://github.com/AgentOps-AI/tokencost/blob/main/tokencost/model_prices.json -import { StreamTextResult } from 'ai'; +import { CoreAssistantMessage, CoreMessage, CoreSystemMessage, CoreToolMessage, CoreUserMessage, FilePart, ImagePart, StreamTextResult, TextPart } from 'ai'; export interface GenerateTextOptions { type?: 'text' | 'json'; @@ -31,21 +31,27 @@ export type GenerateJsonOptions = Omit; */ export type GenerateFunctionOptions = Omit; -export interface LlmMessage { - role: 'system' | 'user' | 'assistant'; - text: string; +type AiMessage = CoreSystemMessage | CoreUserMessage | CoreAssistantMessage | CoreToolMessage; + +export type LlmMessage = AiMessage & { + // /** + // * TextPart { type: "text" , text: string } + // * ImagePart { type: "image", image: string | Uint8Array | ArrayBuffer | Buffer | URL, mimeType?: string } + // * FilePart { type: "file", data: string | Uint8Array | ArrayBuffer | Buffer | URL, mimeType: string } + // */ + // content: string | Array; /** The LLM which generated the text (only when role=assistant) */ llmId?: string; /** Set the cache_control flag with Claude models */ cache?: 'ephemeral'; /** Time the message was sent */ time?: number; -} +}; export function system(text: string, cache = false): LlmMessage { return { role: 'system', - text: text, + content: text, cache: cache ? 'ephemeral' : undefined, }; } @@ -53,7 +59,7 @@ export function system(text: string, cache = false): LlmMessage { export function user(text: string, cache = false): LlmMessage { return { role: 'user', - text: text, + content: text, cache: cache ? 'ephemeral' : undefined, }; } @@ -66,7 +72,7 @@ export function user(text: string, cache = false): LlmMessage { export function assistant(text: string): LlmMessage { return { role: 'assistant', - text: text, + content: text, }; } diff --git a/src/llm/llmCallService/llmCallService.ts b/src/llm/llmCallService/llmCallService.ts index cdaa70e0..787812b2 100644 --- a/src/llm/llmCallService/llmCallService.ts +++ b/src/llm/llmCallService/llmCallService.ts @@ -1,4 +1,4 @@ -import { CreateLlmRequest, LlmCall, LlmRequest } from '#llm/llmCallService/llmCall'; +import { CreateLlmRequest, LlmCall } from '#llm/llmCallService/llmCall'; export interface CallerId { agentId?: string; diff --git a/src/llm/llmFactory.ts b/src/llm/llmFactory.ts index 58b5e9e3..f3a08fc5 100644 --- a/src/llm/llmFactory.ts +++ b/src/llm/llmFactory.ts @@ -2,6 +2,7 @@ import { AgentLLMs } from '#agent/agentContextTypes'; import { LLM } from '#llm/llm'; import { anthropicLLMRegistry } from '#llm/models/anthropic'; import { anthropicVertexLLMRegistry } from '#llm/models/anthropic-vertex'; +import { cerebrasLLMRegistry } from '#llm/models/cerebras'; import { deepseekLLMRegistry } from '#llm/models/deepseek'; import { fireworksLLMRegistry } from '#llm/models/fireworks'; import { groqLLMRegistry } from '#llm/models/groq'; @@ -23,6 +24,7 @@ export const LLM_FACTORY: Record LLM> = { ...togetherLLMRegistry(), ...vertexLLMRegistry(), ...deepseekLLMRegistry(), + ...cerebrasLLMRegistry(), ...ollamaLLMRegistry(), ...blueberryLLMRegistry(), ...{ 'mock:mock': () => mockLLM }, diff --git a/src/llm/models/anthropic-vertex.ts b/src/llm/models/anthropic-vertex.ts index 26685946..86794842 100644 --- a/src/llm/models/anthropic-vertex.ts +++ b/src/llm/models/anthropic-vertex.ts @@ -211,12 +211,12 @@ class AnthropicVertexLLM extends BaseLLM { let systemPrompt: string | undefined; if (messages[0].role === 'system') { - systemPrompt = messages[0].text; + systemPrompt = messages[0].content as string; span.setAttribute('systemPrompt', systemPrompt); messages = messages.slice(1); } - const userPrompt = messages.map((msg) => msg.text).join('\n'); + const userPrompt = messages.map((msg) => msg.content).join('\n'); span.setAttributes({ userPrompt, @@ -240,13 +240,79 @@ class AnthropicVertexLLM extends BaseLLM { let systemMessage: Anthropic.Messages.TextBlockParam[] | undefined = undefined; if (messages[0].role === 'system') { const message = messages.splice(0, 1)[0]; - systemMessage = [{ type: 'text', text: message.text }]; + systemMessage = [{ type: 'text', text: message.content as string }]; // if(source.cache) // systemMessage[0].cacheControl = 'ephemeral' } + /* + The Anthropic types are + export interface MessageParam { + content: string | Array; + + role: 'user' | 'assistant'; + } + export interface TextBlockParam { + text: string; + + type: 'text'; + } + export interface ImageBlockParam { + source: ImageBlockParam.Source; + + type: 'image'; + } + + export namespace ImageBlockParam { + export interface Source { + data: string; + + media_type: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp'; + + type: 'base64'; + } + } + */ + const anthropicMessages: Anthropic.Messages.MessageParam[] = messages.map((message) => { - return { role: message.role as 'user' | 'assistant', content: message.text }; + let content: string | Array; + + if (typeof message.content === 'string') { + content = message.content; + } else if (Array.isArray(message.content)) { + content = message.content.map((part) => { + if (part.type === 'text') { + return { + type: 'text', + text: part.text, + } as Anthropic.Messages.TextBlockParam; + } + if (part.type === 'image') { + return { + type: 'image', + source: { + type: 'base64', + data: part.image.toString(), + media_type: part.mimeType || 'image/png', + }, + } as Anthropic.Messages.ImageBlockParam; + // } else if (part.type === 'file') { + // // Convert files to text representation since Anthropic doesn't support files + // return { + // type: 'text', + // text: `[File attachment]`, + // } as Anthropic.Messages.TextBlockParam; + } + throw new Error(`Unsupported message type ${part.type}`); + }); + } else { + content = '[No content]'; + } + + return { + role: message.role as 'user' | 'assistant', + content, + }; }); message = await this.api().messages.create({ system: systemMessage, diff --git a/src/llm/models/cerebras.ts b/src/llm/models/cerebras.ts index 88b364eb..f6008cc3 100644 --- a/src/llm/models/cerebras.ts +++ b/src/llm/models/cerebras.ts @@ -1,14 +1,19 @@ import Cerebras from '@cerebras/cerebras_cloud_sdk'; -import { agentContext } from '#agent/agentContextLocalStorage'; -import { addCost } from '#agent/agentContextLocalStorage'; +import { CompletionCreateParams, CompletionCreateParamsNonStreaming } from '@cerebras/cerebras_cloud_sdk/src/resources/chat/completions'; +import { addCost, agentContext } from '#agent/agentContextLocalStorage'; import { AgentLLMs } from '#agent/agentContextTypes'; import { LlmCall } from '#llm/llmCallService/llmCall'; +import { logger } from '#o11y/logger'; import { withActiveSpan } from '#o11y/trace'; import { currentUser } from '#user/userService/userContext'; import { appContext } from '../../app'; import { RetryableError } from '../../cache/cacheRetry'; import { BaseLLM } from '../base-llm'; -import { GenerateTextOptions, LLM, combinePrompts } from '../llm'; +import { GenerateTextOptions, LLM, LlmMessage, combinePrompts } from '../llm'; + +import SystemMessageRequest = CompletionCreateParams.SystemMessageRequest; +import AssistantMessageRequest = CompletionCreateParams.AssistantMessageRequest; +import UserMessageRequest = CompletionCreateParams.UserMessageRequest; export const CEREBRAS_SERVICE = 'cerebras'; @@ -85,6 +90,107 @@ export class CerebrasLLM extends BaseLLM { return this._client; } + async generateTextFromMessages(llmMessages: LlmMessage[], opts?: GenerateTextOptions): Promise { + return withActiveSpan(`generateTextFromMessages ${opts?.id ?? ''}`, async (span) => { + const messages: Array< + CompletionCreateParams.SystemMessageRequest | CompletionCreateParams.UserMessageRequest | CompletionCreateParams.AssistantMessageRequest + > = llmMessages.map((msg) => { + switch (msg.role) { + case 'system': { + const system: SystemMessageRequest = { + role: 'system', + content: msg.content as string, + }; + return system; + } + case 'user': { + const user: UserMessageRequest = { + role: 'user', + content: msg.content as string, + }; + return user; + } + case 'assistant': { + const assistant: AssistantMessageRequest = { + role: 'assistant', + content: msg.content as string, + }; + return assistant; + } + default: + throw new Error(`Unsupported role ${msg.role}`); + } + }); + + span.setAttributes({ + model: this.model, + service: this.service, + caller: agentContext()?.callStack.at(-1) ?? '', + }); + if (opts?.id) span.setAttribute('id', opts.id); + + const llmCallSave: Promise = appContext().llmCallService.saveRequest({ + messages: llmMessages, + llmId: this.getId(), + agentId: agentContext()?.agentId, + callStack: agentContext()?.callStack.join(' > '), + }); + const requestTime = Date.now(); + + try { + const params: CompletionCreateParamsNonStreaming = { + messages, + model: this.model, + temperature: opts?.temperature ?? undefined, + top_p: opts?.topP ?? undefined, + stop: opts?.stopSequences, + }; + const completion = await this.client().chat.completions.create(params); + + const responseText = completion.choices[0]?.message?.content || ''; + const finishTime = Date.now(); + const llmCall: LlmCall = await llmCallSave; + + const inputTokens = completion.usage.prompt_tokens; + const outputTokens = completion.usage.completion_tokens; + const prompt = messages.map((m) => m.content).join('\n'); + const inputCost = this.calculateInputCost(prompt); + const outputCost = this.calculateOutputCost(responseText); + const cost = inputCost + outputCost; + addCost(cost); + + llmCall.responseText = responseText; + llmCall.timeToFirstToken = null; + llmCall.totalTime = finishTime - requestTime; + llmCall.cost = cost; + llmCall.inputTokens = inputTokens; + llmCall.outputTokens = outputTokens; + + span.setAttributes({ + inputTokens, + outputTokens, + response: responseText, + inputCost: inputCost.toFixed(4), + outputCost: outputCost.toFixed(4), + cost: cost.toFixed(4), + outputChars: responseText.length, + callStack: agentContext()?.callStack.join(' > '), + }); + + try { + await appContext().llmCallService.saveResponse(llmCall); + } catch (e) { + logger.error(e); + } + + return responseText; + } catch (e) { + if (this.isRetryableError(e)) throw new RetryableError(e); + throw e; + } + }); + } + async generateText(userPrompt: string, systemPrompt?: string, opts?: GenerateTextOptions): Promise { return withActiveSpan(`generateText ${opts?.id ?? ''}`, async (span) => { const prompt = combinePrompts(userPrompt, systemPrompt); @@ -124,6 +230,9 @@ export class CerebrasLLM extends BaseLLM { const completion = await this.client().chat.completions.create({ messages, model: this.model, + temperature: opts?.temperature ?? undefined, + top_p: opts?.topP ?? undefined, + stop: opts?.stopSequences, // Cerebras uses 'stop' instead of 'stop_sequences' }); const responseText = completion.choices[0]?.message?.content || ''; diff --git a/src/llm/models/llm.int.ts b/src/llm/models/llm.int.ts index d89a322d..9e832fc7 100644 --- a/src/llm/models/llm.int.ts +++ b/src/llm/models/llm.int.ts @@ -17,11 +17,11 @@ describe('LLMs', () => { const SKY_MESSAGES: LlmMessage[] = [ { role: 'system', - text: 'Answer in one word.', + content: 'Answer in one word.', }, { role: 'user', - text: 'What colour is the day sky? (Hint: starts with b)', + content: 'What colour is the day sky? (Hint: starts with b)', }, ]; diff --git a/src/modules/firestore/firestoreChatService.ts b/src/modules/firestore/firestoreChatService.ts index be1cf97a..b6e56176 100644 --- a/src/modules/firestore/firestoreChatService.ts +++ b/src/modules/firestore/firestoreChatService.ts @@ -1,6 +1,6 @@ import { randomUUID } from 'crypto'; import { Firestore } from '@google-cloud/firestore'; -import { Chat, ChatPreview, ChatService } from '#chat/chatTypes'; +import { CHAT_PREVIEW_KEYS, Chat, ChatPreview, ChatService } from '#chat/chatTypes'; import { logger } from '#o11y/logger'; import { span } from '#o11y/trace'; import { currentUser } from '#user/userService/userContext'; @@ -38,6 +38,13 @@ export class FirestoreChatService implements ChatService { rootId: data.rootId, messages: data.messages, }; + + // Backwards compatability + for (const message of chat.messages) { + const oldMessage = message as any; + if (oldMessage.text) message.content = oldMessage.text; + } + if (chat.visibility !== 'private' && chat.userId !== currentUser().id) { throw new Error('Chat not visible.'); } @@ -70,12 +77,14 @@ export class FirestoreChatService implements ChatService { } @span() - async listChats(startAfterId?: string, limit = 50): Promise<{ chats: ChatPreview[]; hasMore: boolean }> { + async listChats(startAfterId?: string, limit = 100): Promise<{ chats: ChatPreview[]; hasMore: boolean }> { try { const userId = currentUser().id; + logger.info(`list ${limit} chats for ${userId} ${startAfterId ? `after ${startAfterId}` : ''}`); let query = this.db .collection('Chats') + .select(...CHAT_PREVIEW_KEYS) .where('userId', '==', userId) .orderBy('updatedAt', 'desc') .limit(limit + 1); diff --git a/src/routes/chat/chat-routes.ts b/src/routes/chat/chat-routes.ts index c0ba2a0a..76e87073 100644 --- a/src/routes/chat/chat-routes.ts +++ b/src/routes/chat/chat-routes.ts @@ -66,10 +66,10 @@ export async function chatRoutes(fastify: AppFastifyInstance) { 'The following message is the first message in a new chat conversation. Your task is to create a short title for the conversation. Respond only with the title, nothing else', ); - chat.messages.push({ role: 'user', text: text, time: Date.now() }); //, cache: cache ? 'ephemeral' : undefined // remove any previous cache marker + chat.messages.push({ role: 'user', content: text, time: Date.now() }); //, cache: cache ? 'ephemeral' : undefined // remove any previous cache marker const generatedMessage = await llm.generateTextFromMessages(chat.messages); - chat.messages.push({ role: 'assistant', text: generatedMessage, llmId: llmId, time: Date.now() }); + chat.messages.push({ role: 'assistant', content: generatedMessage, llmId: llmId, time: Date.now() }); if (titlePromise) chat.title = await titlePromise; @@ -107,10 +107,10 @@ export async function chatRoutes(fastify: AppFastifyInstance) { } if (!llm.isConfigured()) return sendBadRequest(reply, `LLM ${llm.getId()} is not configured`); - chat.messages.push({ role: 'user', text: text, time: Date.now() }); //, cache: cache ? 'ephemeral' : undefined // remove any previous cache marker + chat.messages.push({ role: 'user', content: text, time: Date.now() }); //, cache: cache ? 'ephemeral' : undefined // remove any previous cache marker const generatedMessage = await llm.generateTextFromMessages(chat.messages); - chat.messages.push({ role: 'assistant', text: generatedMessage, llmId, time: Date.now() }); + chat.messages.push({ role: 'assistant', content: generatedMessage, llmId, time: Date.now() }); await fastify.chatService.saveChat(chat); diff --git a/src/swe/discovery/selectFilesAgent.ts b/src/swe/discovery/selectFilesAgent.ts index 0573fa82..83c21371 100644 --- a/src/swe/discovery/selectFilesAgent.ts +++ b/src/swe/discovery/selectFilesAgent.ts @@ -165,7 +165,7 @@ export async function selectFilesAgent(requirements: string, projectInfo?: Proje ${requirements} `; - messages.push({ role: 'user', text: initialPrompt }); + messages.push({ role: 'user', content: initialPrompt }); const maxIterations = 5; let iterationCount = 0; @@ -207,19 +207,19 @@ ${stageInstructions} `; // Add the current prompt to messages - messages.push({ role: 'user', text: currentPrompt }); + messages.push({ role: 'user', content: currentPrompt }); // Call the LLM with the current messages const assistantResponse = await llms().medium.generateJsonFromMessages(messages); // Add the assistant's response to the conversation history - messages.push({ role: 'assistant', text: JSON.stringify(assistantResponse) }); + messages.push({ role: 'assistant', content: JSON.stringify(assistantResponse) }); // Handle the assistant's response based on the current stage if (currentStage === 'initial' && assistantResponse.inspectFiles) { // Read and provide the contents of the requested files const fileContents = await readFileContents(assistantResponse.inspectFiles); - messages.push({ role: 'user', text: fileContents }); + messages.push({ role: 'user', content: fileContents }); stagedFiles = assistantResponse.inspectFiles; } else if (currentStage === 'post_inspect' && (assistantResponse.selectFiles || assistantResponse.ignoreFiles)) { // Process selected files and remove ignored files from staging @@ -232,7 +232,7 @@ ${stageInstructions} // Ensure all staged files have been processed if (stagedFiles.length > 0) { const message = `Please respond with select or ignore for the remaining files in the same JSON format as before.\n${JSON.stringify(stagedFiles)}`; - messages.push({ role: 'user', text: message }); + messages.push({ role: 'user', content: message }); } else { // Move to next stage stagedFiles = []; @@ -241,7 +241,7 @@ ${stageInstructions} if (assistantResponse.inspectFiles) { // Read and provide the contents of the requested files const fileContents = await readFileContents(assistantResponse.inspectFiles); - messages.push({ role: 'user', text: fileContents }); + messages.push({ role: 'user', content: fileContents }); stagedFiles = assistantResponse.inspectFiles; } else if (assistantResponse.complete) { // Mark the selection process as complete