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
+
+
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