From aa75c2117b52982d0838549652d7a3c42b1aa3d8 Mon Sep 17 00:00:00 2001 From: Templight41 Date: Wed, 17 Dec 2025 13:19:54 +0530 Subject: [PATCH] completed cost estimator feature --- package-lock.json | 70 ++++++++---------- .../chat-panel/chat-panel.component.html | 34 +++++++++ .../chat-panel/chat-panel.component.scss | 74 +++++++++++++++++++ .../chat-panel/chat-panel.component.ts | 20 +++++ src/app/components/chat/chat.component.html | 4 + src/app/components/chat/chat.component.ts | 22 ++++++ src/app/core/models/types.ts | 8 ++ src/assets/config/runtime-config.json | 2 +- 8 files changed, 192 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index 026f79d1..2953c494 100644 --- a/package-lock.json +++ b/package-lock.json @@ -326,7 +326,6 @@ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-19.2.15.tgz", "integrity": "sha512-eq9vokLU8bjs7g/Znz8zJUQEOhT0MAJ/heBCHbB35S+CtZXJmItrsEqkI1tsRiR58NKXB6cbhBhULVo6qJbhXQ==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -429,7 +428,6 @@ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.1.4.tgz", "integrity": "sha512-PyvJ1VbYjW8tVnVHvcasiqI9eNWf8EJnr0in1QWnhpSbpVpVpc4yjbgnu2pTrW9mPo/YjV4pF+qs6E97y9mdYQ==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -481,7 +479,6 @@ "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.2.15.tgz", "integrity": "sha512-aVa/ctBYH/4qgA7r4sS7TV+/DzRYmcS+3d6l89pNKUXkI8gpmsd+r3FjccaemX4Wqru1QOrMvC+i+e7IBIVv0g==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -498,7 +495,6 @@ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-19.2.15.tgz", "integrity": "sha512-hMHZU6/03xG0tbPDIm1hbVSTFLnRkGYfh+xdBwUMnIFYYTS0QJ2hdPfEZKCJIXm+fz9IAI5MPdDTfeyp0sgaHQ==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -512,7 +508,6 @@ "integrity": "sha512-4r5tvGA2Ok3o8wROZBkF9qNKS7L0AEpdBIkAVJbLw2rBY2SlyycFIRYyV2+D1lJ1jq/f9U7uN6oon0MjTvNYkA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "7.26.9", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -589,7 +584,6 @@ "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.2.15.tgz", "integrity": "sha512-PxhzCwwm23N4Mq6oV7UPoYiJF4r6FzGhRSxOBBlEp322k7zEQbIxd/XO6F3eoG73qC1UsOXMYYv6GnQpx42y3A==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -606,7 +600,6 @@ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-19.2.15.tgz", "integrity": "sha512-pZDElcYPmNzPxvWJpZQCIizsNApDIfk9xLJE4I8hzLISfWGbQvfjuuarDAuQZEXudeLXoDOstDXkDja40muLGg==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -643,7 +636,6 @@ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.15.tgz", "integrity": "sha512-OelQ6weCjon8kZD8kcqNzwugvZJurjS3uMJCwsA2vXmP/3zJ31SWtNqE2zLT1R2csVuwnp0h+nRMgq+pINU7Rg==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -752,7 +744,6 @@ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -2517,7 +2508,6 @@ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", "license": "MIT", - "peer": true, "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", @@ -2554,7 +2544,6 @@ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", "license": "MIT", - "peer": true, "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } @@ -2564,7 +2553,6 @@ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.2.tgz", "integrity": "sha512-bTWAJxL6EOFLPzTx+O5P5xAO3gTqpatQ2b/ARQ8itfU/v2LlpS3pH2fkL0A3E/Fx8Y2St2KES7ZEV0sHTsSW/A==", "license": "MIT", - "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -3310,7 +3298,6 @@ "integrity": "sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@inquirer/checkbox": "^4.1.2", "@inquirer/confirm": "^5.1.6", @@ -5679,7 +5666,6 @@ "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/d3-selection": "*" } @@ -5797,8 +5783,7 @@ "version": "3.0.11", "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/d3-shape": { "version": "3.1.7", @@ -5846,7 +5831,6 @@ "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", "license": "MIT", - "peer": true, "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" @@ -5957,7 +5941,6 @@ "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.12.0" } @@ -6293,7 +6276,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6345,7 +6327,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6850,7 +6831,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001741", @@ -7141,6 +7121,21 @@ "dev": true, "license": "MIT" }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, "node_modules/chevrotain-allstar": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", @@ -7865,6 +7860,16 @@ "dev": true, "license": "MIT" }, + "node_modules/cytoscape": { + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", + "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10" + } + }, "node_modules/cytoscape-cose-bilkent": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", @@ -8052,7 +8057,6 @@ "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", "license": "ISC", - "peer": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" @@ -8313,7 +8317,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -8390,7 +8393,6 @@ "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "license": "ISC", - "peer": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", @@ -10671,8 +10673,7 @@ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.5.0.tgz", "integrity": "sha512-NHOvoPO6o9gVR6pwqEACTEpbgcH+JJ6QDypyymGbSUIFIFsMMbBJ/xsFNud8MSClfnWclXd7RQlAZBz7yVo5TQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-worker": { "version": "27.5.1", @@ -10711,7 +10712,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -10750,7 +10750,6 @@ "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 10.16.0" } @@ -10863,7 +10862,6 @@ "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@colors/colors": "1.5.0", "body-parser": "^1.19.0", @@ -11266,7 +11264,6 @@ "integrity": "sha512-tkuLHQlvWUTeQ3doAqnHbNn8T6WX1KA8yvbKG9x4VtKtIjHsVKQZCH11zRgAfbDAXC2UNIg/K9BYAAcEzUIrNg==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -13357,7 +13354,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -14082,7 +14078,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -15346,7 +15341,6 @@ "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -15544,8 +15538,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tuf-js": { "version": "3.1.0", @@ -15589,7 +15582,6 @@ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16396,7 +16388,6 @@ "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -16474,7 +16465,6 @@ "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", @@ -16932,7 +16922,6 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -17060,8 +17049,7 @@ "version": "0.15.1", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==", - "license": "MIT", - "peer": true + "license": "MIT" } } } diff --git a/src/app/components/chat-panel/chat-panel.component.html b/src/app/components/chat-panel/chat-panel.component.html index 1d642d66..95aaccdd 100644 --- a/src/app/components/chat-panel/chat-panel.component.html +++ b/src/app/components/chat-panel/chat-panel.component.html @@ -403,6 +403,40 @@ > videocam + + +
+
+
{{ getUsagePercentageFormatted() }}%
+
{{ sessionTotalTokens() }} tokens
+
+
+
+
+
+
+ Input + {{ sessionInputTokens() }} +
+
+ Output + {{ sessionOutputTokens() }} +
+
+ Cost $ + ${{ formatCost(sessionTotalCost()) }} +
+
+
+
diff --git a/src/app/components/chat-panel/chat-panel.component.scss b/src/app/components/chat-panel/chat-panel.component.scss index 5e288951..6adbe61e 100644 --- a/src/app/components/chat-panel/chat-panel.component.scss +++ b/src/app/components/chat-panel/chat-panel.component.scss @@ -436,3 +436,77 @@ button.video-rec-btn { width: 12px; } } + +button.usage-stats-btn { + background-color: var(--chat-card-background-color); +} + +:host ::ng-deep .usage-stats-menu { + .mat-mdc-menu-content { + padding: 0; + } +} + +.usage-stats-container { + padding: 16px; + min-width: 240px; + background-color: var(--chat-panel-bot-message-message-card-background-color); + border-radius: 8px; + cursor: default; +} + +.usage-stats-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +.usage-percentage { + font-size: 20px; + font-weight: 600; + color: var(--chat-panel-input-field-textarea-color); +} + +.usage-tokens { + font-size: 14px; + color: var(--chat-panel-input-field-textarea-color); + opacity: 0.8; +} + +.usage-progress-bar { + height: 6px; + background-color: var(--chat-panel-input-field-mat-mdc-text-field-wrapper-border-color); + border-radius: 3px; + overflow: hidden; + margin-bottom: 16px; +} + +.usage-progress-fill { + height: 100%; + background: linear-gradient(90deg, #4285f4, #34a853); + transition: width 0.3s ease; + border-radius: 3px; +} + +.usage-stats-details { + display: flex; + flex-direction: column; + gap: 8px; +} + +.usage-stat-row { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 14px; + color: var(--chat-panel-input-field-textarea-color); +} + +.usage-stat-label { + opacity: 0.7; +} + +.usage-stat-value { + font-weight: 500; +} diff --git a/src/app/components/chat-panel/chat-panel.component.ts b/src/app/components/chat-panel/chat-panel.component.ts index 8583fa39..b691b1ed 100644 --- a/src/app/components/chat-panel/chat-panel.component.ts +++ b/src/app/components/chat-panel/chat-panel.component.ts @@ -84,6 +84,10 @@ export class ChatPanelComponent implements OnChanges, AfterViewInit { @Input() isAudioRecording: boolean = false; @Input() isVideoRecording: boolean = false; @Input() hoveredEventMessageIndices: number[] = []; + @Input() sessionTotalTokens: () => number = () => 0; + @Input() sessionInputTokens: () => number = () => 0; + @Input() sessionOutputTokens: () => number = () => 0; + @Input() sessionTotalCost: () => number = () => 0; @Output() readonly userInputChange = new EventEmitter(); @Output() readonly userEditEvalCaseMessageChange = new EventEmitter(); @@ -201,4 +205,20 @@ export class ChatPanelComponent implements OnChanges, AfterViewInit { emitFeedback(direction: 'up'|'down') { this.feedback.emit({direction}); } + + getUsagePercentage(): number { + const total = this.sessionTotalTokens(); + // Assuming a max context of 1M tokens for display purposes + const maxTokens = 1000000; + const percentage = Math.min((total / maxTokens) * 100, 100); + return Math.round(percentage * 100) / 100; // Round to 2 decimal places + } + + getUsagePercentageFormatted(): string { + return this.getUsagePercentage().toFixed(2); + } + + formatCost(cost: number): string { + return cost.toFixed(6); + } } diff --git a/src/app/components/chat/chat.component.html b/src/app/components/chat/chat.component.html index 8a8f573f..e6b3433a 100644 --- a/src/app/components/chat/chat.component.html +++ b/src/app/components/chat/chat.component.html @@ -300,6 +300,10 @@ [isAudioRecording]="isAudioRecording" [isVideoRecording]="isVideoRecording" [hoveredEventMessageIndices]="hoveredEventMessageIndices" + [sessionTotalTokens]="sessionTotalTokens" + [sessionInputTokens]="sessionInputTokens" + [sessionOutputTokens]="sessionOutputTokens" + [sessionTotalCost]="sessionTotalCost" (clickEvent)="clickEvent($event)" (handleKeydown)="handleKeydown($event.event, $event.message)" (cancelEditMessage)="cancelEditMessage($event)" diff --git a/src/app/components/chat/chat.component.ts b/src/app/components/chat/chat.component.ts index 79be5d35..03e0680b 100644 --- a/src/app/components/chat/chat.component.ts +++ b/src/app/components/chat/chat.component.ts @@ -227,6 +227,12 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { selectedFiles: {file: File; url: string}[] = []; + // Token usage and cost tracking + sessionTotalTokens = signal(0); + sessionInputTokens = signal(0); + sessionOutputTokens = signal(0); + sessionTotalCost = signal(0); + protected MediaType = MediaType; // Sync query params with value from agent picker. @@ -424,6 +430,11 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { this.artifacts = []; this.userInput = ''; this.longRunningEvents = []; + // Reset usage tracking + this.sessionTotalTokens.set(0); + this.sessionInputTokens.set(0); + this.sessionOutputTokens.set(0); + this.sessionTotalCost.set(0); } createSession() { @@ -498,6 +509,15 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { this.openSnackBar(chunkJson.error, 'OK'); return; } + // Track usage metadata from streaming response + if (chunkJson.usageMetadata) { + this.sessionTotalTokens.update(total => total + (chunkJson.usageMetadata?.totalTokenCount || 0)); + this.sessionInputTokens.update(total => total + (chunkJson.usageMetadata?.promptTokenCount || 0)); + this.sessionOutputTokens.update(total => total + (chunkJson.usageMetadata?.candidatesTokenCount || 0)); + } + if (chunkJson.costUsd) { + this.sessionTotalCost.update(total => total + (chunkJson.costUsd || 0)); + } if (chunkJson.content) { for (let part of this.combineTextParts(chunkJson.content.parts)) { this.processPart(chunkJson, part); @@ -745,6 +765,8 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { toolUseIndex: additionalIndices?.toolUseIndex !== undefined ? additionalIndices.toolUseIndex : undefined, + usageMetadata: e?.usageMetadata, + costUsd: e?.costUsd, }; if (part) { if (part.inlineData) { diff --git a/src/app/core/models/types.ts b/src/app/core/models/types.ts index a1bd6eef..647b8be0 100644 --- a/src/app/core/models/types.ts +++ b/src/app/core/models/types.ts @@ -69,12 +69,20 @@ export declare interface LlmRequest { contents: GenAiContent[]; } +export declare interface UsageMetadata { + candidatesTokenCount?: number; + promptTokenCount?: number; + totalTokenCount?: number; +} + export declare interface LlmResponse { content: GenAiContent; error?: string; errorMessage?: string; errorCode?: string; longRunningToolIds?: string[]; + usageMetadata?: UsageMetadata; + costUsd?: number; } export declare interface EventActions { diff --git a/src/assets/config/runtime-config.json b/src/assets/config/runtime-config.json index c8f49d88..c6732406 100644 --- a/src/assets/config/runtime-config.json +++ b/src/assets/config/runtime-config.json @@ -1,3 +1,3 @@ { - "backendUrl": "" + "backendUrl": "http://localhost:8000" } \ No newline at end of file