llm-testrunner-components 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +113 -10
  2. package/dist/cjs/app-globals-CbbEbofA.js +14 -0
  3. package/dist/cjs/app-globals-CbbEbofA.js.map +1 -0
  4. package/dist/cjs/{index-CY2lQip_.js → index-D-FySkoV.js} +25 -5
  5. package/dist/cjs/index-D-FySkoV.js.map +1 -0
  6. package/dist/cjs/index.cjs.js +159 -66
  7. package/dist/cjs/index.cjs.js.map +1 -1
  8. package/dist/cjs/llm-test-runner.cjs.entry.js +1 -1
  9. package/dist/cjs/llm-testrunner.cjs.js +4 -4
  10. package/dist/cjs/llm-testrunner.cjs.js.map +1 -1
  11. package/dist/cjs/loader.cjs.js +3 -3
  12. package/dist/collection/collection-manifest.json +3 -2
  13. package/dist/collection/components/llm-test-runner/llm-test-runner.js +152 -72
  14. package/dist/collection/components/llm-test-runner/llm-test-runner.js.map +1 -1
  15. package/dist/collection/global/env.js +6 -0
  16. package/dist/collection/global/env.js.map +1 -0
  17. package/dist/collection/lib/rate-limited-fetcher/rate-limited-fetcher.js +39 -0
  18. package/dist/collection/lib/rate-limited-fetcher/rate-limited-fetcher.js.map +1 -0
  19. package/dist/components/index.js +6 -1650
  20. package/dist/components/index.js.map +1 -1
  21. package/dist/components/llm-test-runner.js +1 -1
  22. package/dist/components/p-CYUbsbxt.js +1770 -0
  23. package/dist/components/p-CYUbsbxt.js.map +1 -0
  24. package/dist/esm/app-globals-BOQOUavG.js +12 -0
  25. package/dist/esm/app-globals-BOQOUavG.js.map +1 -0
  26. package/dist/esm/{index-DBp-rMPb.js → index-cncubhtM.js} +25 -6
  27. package/dist/esm/index-cncubhtM.js.map +1 -0
  28. package/dist/esm/index.js +159 -66
  29. package/dist/esm/index.js.map +1 -1
  30. package/dist/esm/llm-test-runner.entry.js +1 -1
  31. package/dist/esm/llm-testrunner.js +5 -5
  32. package/dist/esm/llm-testrunner.js.map +1 -1
  33. package/dist/esm/loader.js +4 -4
  34. package/dist/llm-testrunner/index.esm.js +1 -1
  35. package/dist/llm-testrunner/index.esm.js.map +1 -1
  36. package/dist/llm-testrunner/llm-testrunner.esm.js +1 -1
  37. package/dist/llm-testrunner/llm-testrunner.esm.js.map +1 -1
  38. package/dist/llm-testrunner/p-BOQOUavG.js +2 -0
  39. package/dist/llm-testrunner/p-BOQOUavG.js.map +1 -0
  40. package/dist/llm-testrunner/p-cncubhtM.js +3 -0
  41. package/dist/llm-testrunner/p-cncubhtM.js.map +1 -0
  42. package/dist/llm-testrunner/p-f68fd660.entry.js +2 -0
  43. package/dist/react/components.d.ts +6 -3
  44. package/dist/react/components.d.ts.map +1 -1
  45. package/dist/react/components.js +2 -2
  46. package/dist/types/components/llm-test-runner/llm-test-runner.d.ts +13 -4
  47. package/dist/types/components.d.ts +26 -2
  48. package/dist/types/global/env.d.ts +8 -0
  49. package/dist/types/lib/rate-limited-fetcher/rate-limited-fetcher.d.ts +10 -0
  50. package/dist/types/stencil-public-runtime.d.ts +19 -10
  51. package/package.json +6 -4
  52. package/dist/cjs/app-globals-V2Kpy_OQ.js +0 -8
  53. package/dist/cjs/app-globals-V2Kpy_OQ.js.map +0 -1
  54. package/dist/cjs/index-CY2lQip_.js.map +0 -1
  55. package/dist/esm/app-globals-DQuL1Twl.js +0 -6
  56. package/dist/esm/app-globals-DQuL1Twl.js.map +0 -1
  57. package/dist/esm/index-DBp-rMPb.js.map +0 -1
  58. package/dist/llm-testrunner/p-DBp-rMPb.js +0 -3
  59. package/dist/llm-testrunner/p-DBp-rMPb.js.map +0 -1
  60. package/dist/llm-testrunner/p-DQuL1Twl.js +0 -2
  61. package/dist/llm-testrunner/p-DQuL1Twl.js.map +0 -1
  62. package/dist/llm-testrunner/p-ed2ea423.entry.js +0 -2
  63. /package/dist/llm-testrunner/{p-ed2ea423.entry.js.map → p-f68fd660.entry.js.map} +0 -0
@@ -2,8 +2,10 @@ import { h } from "@stencil/core";
2
2
  import { LLMEvaluationEngine } from "../../lib/evaluation/evaluation-engine";
3
3
  import { v4 as uuidv4 } from "uuid";
4
4
  import { ErrorMessage } from "../error-message/error-message";
5
+ import { RateLimitedFetcher } from "../../lib/rate-limited-fetcher/rate-limited-fetcher";
5
6
  export class LLMTestRunner {
6
- apiKey;
7
+ llmRequest;
8
+ delayMs = 500;
7
9
  testCases = [
8
10
  {
9
11
  id: '1',
@@ -17,15 +19,10 @@ export class LLMTestRunner {
17
19
  error = '';
18
20
  fileInput;
19
21
  isExportingTestSuite = false;
22
+ isExportingTestResults = false;
20
23
  evaluationEngine;
21
- apiUrl = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent';
22
- async componentWillLoad() {
24
+ componentWillLoad() {
23
25
  this.evaluationEngine = new LLMEvaluationEngine();
24
- console.log('🔍 componentWillLoad - apiKey:', this.apiKey ? 'SET' : 'NOT SET');
25
- console.log('🔍 componentWillLoad - apiKey value:', this.apiKey);
26
- if (!this.apiKey) {
27
- throw new Error('API key is required. Please provide the apiKey prop: <llm-test-runner apiKey="your-api-key" />');
28
- }
29
26
  }
30
27
  componentDidLoad() {
31
28
  }
@@ -46,28 +43,39 @@ export class LLMTestRunner {
46
43
  }
47
44
  async runSingleTest(testCase) {
48
45
  console.log('🚀 Starting test for question:', testCase.question);
46
+ const startTime = Date.now();
49
47
  this.updateTestCase(testCase.id, { isRunning: true });
50
- try {
51
- const aiResponse = await this.callGeminiAPI(testCase.question);
52
- console.log('✅ AI call completed for test case:', testCase.id);
53
- this.updateTestCase(testCase.id, {
54
- isRunning: false,
55
- output: aiResponse,
56
- error: null
57
- });
58
- await this.evaluateResponse({
59
- ...testCase,
60
- output: aiResponse
61
- });
62
- }
63
- catch (error) {
64
- console.error('❌ Error in runSingleTest:', error);
65
- this.updateTestCase(testCase.id, {
66
- isRunning: false,
67
- output: null,
68
- error: error instanceof Error ? error.message : 'Unknown error'
48
+ return new Promise((resolve, reject) => {
49
+ this.llmRequest.emit({
50
+ prompt: testCase.question,
51
+ resolve: async (aiResponse) => {
52
+ console.log('✅ AI call completed for test case:', testCase.id);
53
+ const endTime = Date.now();
54
+ const responseTime = endTime - startTime;
55
+ this.updateTestCase(testCase.id, {
56
+ isRunning: false,
57
+ output: aiResponse,
58
+ error: null,
59
+ responseTime: responseTime
60
+ });
61
+ await this.evaluateResponse({
62
+ ...testCase,
63
+ output: aiResponse,
64
+ responseTime: responseTime
65
+ });
66
+ resolve();
67
+ },
68
+ reject: (error) => {
69
+ console.error('❌ Error in runSingleTest:', error);
70
+ this.updateTestCase(testCase.id, {
71
+ isRunning: false,
72
+ output: null,
73
+ error: error instanceof Error ? error.message : 'Unknown error'
74
+ });
75
+ reject(error);
76
+ }
69
77
  });
70
- }
78
+ });
71
79
  }
72
80
  deleteTestCase(id) {
73
81
  this.testCases = this.testCases.filter(tc => tc.id !== id);
@@ -106,38 +114,6 @@ export class LLMTestRunner {
106
114
  this.updateTestCase(testCaseId, { expectedSourceLinks: newLinks });
107
115
  }
108
116
  }
109
- async callGeminiAPI(prompt) {
110
- console.log('🔍 callGeminiAPI - apiKey:', this.apiKey ? 'SET' : 'NOT SET');
111
- console.log('🔍 callGeminiAPI - apiKey value:', this.apiKey ? `${this.apiKey.substring(0, 10)}...` : 'undefined');
112
- if (!this.apiKey) {
113
- throw new Error('API key is required. Please provide the apiKey prop.');
114
- }
115
- const requestBody = {
116
- contents: [{
117
- parts: [{
118
- text: prompt
119
- }]
120
- }]
121
- };
122
- const response = await fetch(`${this.apiUrl}?key=${this.apiKey}`, {
123
- method: 'POST',
124
- headers: {
125
- 'Content-Type': 'application/json',
126
- },
127
- body: JSON.stringify(requestBody)
128
- });
129
- if (!response.ok) {
130
- const errorData = await response.json().catch(() => ({}));
131
- throw new Error(errorData.error?.message || `HTTP error! status: ${response.status}`);
132
- }
133
- const data = await response.json();
134
- if (data.candidates && data.candidates[0] && data.candidates[0].content) {
135
- return data.candidates[0].content.parts[0].text;
136
- }
137
- else {
138
- throw new Error('Unexpected response format from Gemini API');
139
- }
140
- }
141
117
  async evaluateResponse(testCase) {
142
118
  if (!testCase.output) {
143
119
  console.warn('⚠️ No output to evaluate for test case:', testCase.id);
@@ -159,13 +135,21 @@ export class LLMTestRunner {
159
135
  }
160
136
  async runAllTests() {
161
137
  this.isRunningAll = true;
138
+ const tasks = [];
162
139
  for (const testCase of this.testCases) {
163
140
  if (!testCase.isRunning && testCase.question.trim()) {
164
- await this.runSingleTest(testCase);
165
- // Delay between tests to avoid rate limiting
166
- await new Promise(resolve => setTimeout(resolve, 1000));
141
+ tasks.push(() => this.runSingleTest(testCase).catch(err => {
142
+ console.error(`⚠️ Test case ${testCase.id} failed`, err);
143
+ }));
167
144
  }
168
145
  }
146
+ try {
147
+ const fetcher = new RateLimitedFetcher(this.delayMs);
148
+ await fetcher.runAll(tasks);
149
+ }
150
+ catch (err) {
151
+ console.error('⚠️ Error running all tests:', err);
152
+ }
169
153
  this.isRunningAll = false;
170
154
  }
171
155
  generateId() {
@@ -248,8 +232,78 @@ export class LLMTestRunner {
248
232
  this.isExportingTestSuite = false;
249
233
  }
250
234
  }
235
+ async handleExportTestResults() {
236
+ this.isExportingTestResults = true;
237
+ try {
238
+ // Create CSV content with the required fields
239
+ const csvRows = [];
240
+ // Add header row
241
+ const headers = [
242
+ 'Question',
243
+ 'Expected Keywords',
244
+ 'Generated Keywords',
245
+ 'Keywords Match',
246
+ 'Expected Source Links',
247
+ 'Generated Source Links',
248
+ 'Source Links Match',
249
+ 'Response Time (s)'
250
+ ];
251
+ csvRows.push(headers.join(','));
252
+ // Add data rows
253
+ this.testCases.forEach(testCase => {
254
+ const expectedKeywords = testCase.expectedKeywords.join('; ');
255
+ const expectedSourceLinks = testCase.expectedSourceLinks.join('; ');
256
+ let generatedKeywords = '';
257
+ let generatedSourceLinks = '';
258
+ let keywordsMatch = '';
259
+ let sourceLinksMatch = '';
260
+ if (testCase.evaluationResult) {
261
+ const foundKeywords = testCase.evaluationResult.keywordMatches
262
+ .filter(match => match.found)
263
+ .map(match => match.keyword);
264
+ const foundSourceLinks = testCase.evaluationResult.sourceLinkMatches
265
+ .filter(match => match.found)
266
+ .map(match => match.link);
267
+ generatedKeywords = foundKeywords.join('; ');
268
+ generatedSourceLinks = foundSourceLinks.join('; ');
269
+ // Calculate match percentages
270
+ const keywordMatchCount = testCase.evaluationResult.keywordMatches.filter(m => m.found).length;
271
+ const totalKeywords = testCase.evaluationResult.keywordMatches.length;
272
+ const sourceLinkMatchCount = testCase.evaluationResult.sourceLinkMatches.filter(m => m.found).length;
273
+ const totalSourceLinks = testCase.evaluationResult.sourceLinkMatches.length;
274
+ keywordsMatch = totalKeywords > 0 ? `${keywordMatchCount}/${totalKeywords}` : 'N/A';
275
+ sourceLinksMatch = totalSourceLinks > 0 ? `${sourceLinkMatchCount}/${totalSourceLinks}` : 'N/A';
276
+ }
277
+ const responseTime = testCase.responseTime ? (testCase.responseTime / 1000).toFixed(3) : 'N/A';
278
+ const row = [
279
+ this.escapeCsvField(testCase.question),
280
+ this.escapeCsvField(expectedKeywords),
281
+ this.escapeCsvField(generatedKeywords),
282
+ keywordsMatch,
283
+ this.escapeCsvField(expectedSourceLinks),
284
+ this.escapeCsvField(generatedSourceLinks),
285
+ sourceLinksMatch,
286
+ responseTime
287
+ ];
288
+ csvRows.push(row.join(','));
289
+ });
290
+ const csvContent = csvRows.join('\n');
291
+ // Added a small delay to show the loading state
292
+ await new Promise(resolve => setTimeout(resolve, 500));
293
+ this.downloadFile(csvContent, 'test-results.csv', 'text/csv');
294
+ }
295
+ finally {
296
+ this.isExportingTestResults = false;
297
+ }
298
+ }
299
+ escapeCsvField(field) {
300
+ if (field.includes(',') || field.includes('"') || field.includes('\n')) {
301
+ return `"${field.replace(/"/g, '""')}"`;
302
+ }
303
+ return field;
304
+ }
251
305
  render() {
252
- return (h("div", { key: 'dc5b661334cb4d4bb9bf51fb25363fe08ad938e3', class: "test-runner-container" }, h("header", { key: 'eede0d7f00c0bf5ff794fe2e53bad7a239f2d4d4', class: "test-runner-header" }, h("div", { key: '19327ae6f7408843110a10f791e00ffdf3da6d64', class: "header-left" }, h("input", { key: 'b24227b27dc0f562318a67ca9c38b8793b55c644', class: "hidden", type: "file", ref: (el) => (this.fileInput = el), onChange: (e) => this.handleFileChange(e), accept: ".json,application/json" }), h("button", { key: '65adec40d3787115546c9b59e8691bc89c55c688', class: "btn btn-secondary", onClick: () => this.handleFileSelect() }, h("span", { key: '6fd40e85b4c99232330242b41199df1c4f947351', class: "icon" }, "\u2191"), "Import Test Suite"), h("button", { key: 'd7c4d6b7e3e9bb6951395a3c2c10b964ef2d2fca', class: "btn btn-secondary", onClick: () => this.handleExportTestSuite(), disabled: this.isExportingTestSuite }, h("span", { key: 'bab67984c37b2744f35b4b2c34bfa3a500d196fb', class: "icon" }, this.isExportingTestSuite ? '⏳' : '↓'), this.isExportingTestSuite ? 'Exporting...' : 'Export Test Suite')), h("div", { key: '74a7d6e148311151de86a66d7b146bd0d01c215c', class: "header-right" }, h("button", { key: '3e9c33ef795755ebdf11fafa68801294b61a03d1', class: "btn btn-secondary" }, h("span", { key: '89b3582178782392cbea1309ba32b5edc57f9392', class: "icon" }, "\u2699\uFE0F"), "Prompt Editor"), h("button", { key: 'e68e8edd5f532c7a8ec87746fa6d393c4297bc36', class: "btn btn-secondary" }, h("span", { key: '7298d968bbc108cf060a0c309c9a8b2bce88aa90', class: "icon" }, "\u2193"), "Export Test Results"), h("button", { key: 'fb7d10cf9abda9968a2502bd8b4bf8e4cade11e8', class: "btn btn-primary", onClick: () => this.runAllTests(), disabled: this.isRunningAll }, this.isRunningAll ? 'Running...' : 'Run All'))), h(ErrorMessage, { key: '71eb4e561018a176a8c944107d734c32cd74334b', message: this.error, onClear: () => (this.error = '') }), h("div", { key: 'c120a8e40c6c6721dacd400266d4cd6270057c66', class: "test-runner-content" }, h("div", { key: '7b864c6e0671d8be117b001fd87047fabdc851c0', class: "column-headers" }, h("div", { key: 'fcd6ae26d7b98422d800c4e3a4bcec587187be2b', class: "column-header" }, "Input"), h("div", { key: '2855a067b11f8f9ddaf489daffb8a9fd2a317a44', class: "column-header" }, "Output"), h("div", { key: '8b6d75b16005d19bacd8fa6d2008fa64c268e1ef', class: "column-header" }, "Evaluation"), h("div", { key: 'd14ea8cecb426c9692b16044124f3d23ddae671d', class: "column-header" }, "Actions")), h("div", { key: '958aaba85391f7aadabd86c0395d0fe18b942ed4', class: "test-cases" }, this.testCases.map((testCase) => (h("div", { class: "test-case-row", key: testCase.id }, h("div", { class: "input-column" }, h("div", { class: "input-group" }, h("label", null, "Question"), h("textarea", { value: testCase.question, onInput: (e) => this.updateTestCase(testCase.id, {
306
+ return (h("div", { key: 'beb2ab78108fede00c5d759d1ac5c98ae7f037d0', class: "test-runner-container" }, h("header", { key: 'd09b424069227500ffffcab8724b48d91705baab', class: "test-runner-header" }, h("div", { key: '368ef117a1c2cc0be0446e2d373e38e0af1c2040', class: "header-left" }, h("input", { key: '74346a634b36e9d04f75817495834983d970306c', class: "hidden", type: "file", ref: (el) => (this.fileInput = el), onChange: (e) => this.handleFileChange(e), accept: ".json,application/json" }), h("button", { key: 'e095ff9de105cb281e60f70de02fb0de8a0f6478', class: "btn btn-secondary", onClick: () => this.handleFileSelect() }, h("span", { key: '6f828f297867ae54baf1f89c9dbce611eba2056a', class: "icon" }, "\u2191"), "Import Test Suite"), h("button", { key: '5682dc8f70edac9fdf33b394677a6859555c039b', class: "btn btn-secondary", onClick: () => this.handleExportTestSuite(), disabled: this.isExportingTestSuite }, h("span", { key: 'a1e8d3170ce39e499160e578e3e02d39cd0da9d7', class: "icon" }, this.isExportingTestSuite ? '⏳' : '↓'), this.isExportingTestSuite ? 'Exporting...' : 'Export Test Suite')), h("div", { key: 'af96ea230b04f4f6ce8e3488a1f8f7f2a01e74c0', class: "header-right" }, h("button", { key: 'a90bfb3421f5e2e5112b3642ddf81cf19372e0ad', class: "btn btn-secondary" }, h("span", { key: '79611828e5dc1ad53998a74fca10a1a05c5d2a4e', class: "icon" }, "\u2699\uFE0F"), "Prompt Editor"), h("button", { key: '451367ce26183a58add0902e1de2d29b4933add4', class: "btn btn-secondary", onClick: () => this.handleExportTestResults(), disabled: this.isExportingTestResults }, h("span", { key: '11f791f2071b58e0800d14ac7e6bebba67421449', class: "icon" }, this.isExportingTestResults ? '⏳' : '↓'), this.isExportingTestResults ? 'Exporting...' : 'Export Test Results'), h("button", { key: '3cbf7626ccc5f171cdc6fd7ce3750ea3786218aa', class: "btn btn-primary", onClick: () => this.runAllTests(), disabled: this.isRunningAll }, this.isRunningAll ? 'Running...' : 'Run All'))), h(ErrorMessage, { key: 'ffdd72a1db1f45c5db158d2df0be64dc978fa930', message: this.error, onClear: () => (this.error = '') }), h("div", { key: '64edd5c84c4b566f90d1860f6d6bf5aeafb7ae0f', class: "test-runner-content" }, h("div", { key: '4be27d07a13d88611b5fc54b05e826c790fd2145', class: "column-headers" }, h("div", { key: 'eeb9ace5e63b80359d2decb1b42461f77cfe98cf', class: "column-header" }, "Input"), h("div", { key: 'c70cc5af0aaca2636eee298dfaa4da81799df6cf', class: "column-header" }, "Output"), h("div", { key: '9ca69fddafe7267bf5b8b12601e2cf64d65a58e1', class: "column-header" }, "Evaluation"), h("div", { key: 'b800841828c22a6aad0345e58b47f71469594792', class: "column-header" }, "Actions")), h("div", { key: 'da186e25c2111474f65b89a017158bffdbc83e03', class: "test-cases" }, this.testCases.map((testCase) => (h("div", { class: "test-case-row", key: testCase.id }, h("div", { class: "input-column" }, h("div", { class: "input-group" }, h("label", null, "Question"), h("textarea", { value: testCase.question, onInput: (e) => this.updateTestCase(testCase.id, {
253
307
  question: e.target.value
254
308
  }), placeholder: "Enter your question here...", rows: 3 })), h("div", { class: "keywords-group" }, h("label", null, "Expected keywords"), h("div", { class: "tags-container" }, testCase.expectedKeywords.map((keyword, index) => (h("span", { class: "tag", key: index }, keyword, h("button", { class: "tag-remove", onClick: () => this.removeKeyword(testCase.id, index) }, "\u00D7")))), h("input", { type: "text", placeholder: "New item...", onKeyDown: (e) => {
255
309
  if (e.key === 'Enter') {
@@ -261,7 +315,7 @@ export class LLMTestRunner {
261
315
  this.addSourceLink(testCase.id, e.target.value);
262
316
  e.target.value = '';
263
317
  }
264
- } })))), h("div", { class: "output-column" }, testCase.output ? (h("div", { class: "output-content" }, testCase.output)) : (h("div", { class: "output-placeholder" }, testCase.isRunning ? 'Running...' : ''))), h("div", { class: "evaluation-column" }, testCase.evaluationResult ? (h("div", { class: "evaluation-result" }, h("div", { class: `evaluation-status ${testCase.evaluationResult.passed ? 'passed' : 'failed'}` }, testCase.evaluationResult.passed ? '✅ PASSED' : '❌ FAILED'), h("div", { class: "evaluation-details" }, "Keywords: ", testCase.evaluationResult.keywordMatches.filter(m => m.found).length, "/", testCase.evaluationResult.keywordMatches.length, " found"))) : (h("div", { class: "evaluation-placeholder" }, testCase.isRunning ? 'Evaluating...' : ''))), h("div", { class: "actions-column" }, h("button", { class: "btn btn-icon btn-run", onClick: () => this.runSingleTest(testCase), disabled: testCase.isRunning || !testCase.question.trim(), title: !testCase.question.trim() ? "Enter a question first" : "Run this test" }, testCase.isRunning ? '⏳' : '▶️'), h("button", { class: "btn btn-icon btn-delete", onClick: () => this.deleteTestCase(testCase.id), title: "Delete this test" }, "\uD83D\uDDD1\uFE0F")))))), h("div", { key: '678932d3554c7ef2c86373bc4c972eb0f9894916', class: "add-test-case" }, h("button", { key: '7f8447a4681214320647c55e06d1cfc0fc87b3fb', class: "btn btn-outline", onClick: () => this.addNewTestCase() }, "+ Add Question")))));
318
+ } })))), h("div", { class: "output-column" }, testCase.output ? (h("div", { class: "output-content" }, testCase.output)) : (h("div", { class: "output-placeholder" }, testCase.isRunning ? 'Running...' : ''))), h("div", { class: "evaluation-column" }, testCase.evaluationResult ? (h("div", { class: "evaluation-result" }, h("div", { class: `evaluation-status ${testCase.evaluationResult.passed ? 'passed' : 'failed'}` }, testCase.evaluationResult.passed ? '✅ PASSED' : '❌ FAILED'), h("div", { class: "evaluation-details" }, "Keywords: ", testCase.evaluationResult.keywordMatches.filter(m => m.found).length, "/", testCase.evaluationResult.keywordMatches.length, " found"))) : (h("div", { class: "evaluation-placeholder" }, testCase.isRunning ? 'Evaluating...' : ''))), h("div", { class: "actions-column" }, h("button", { class: "btn btn-icon btn-run", onClick: () => this.runSingleTest(testCase), disabled: testCase.isRunning || !testCase.question.trim(), title: !testCase.question.trim() ? "Enter a question first" : "Run this test" }, testCase.isRunning ? '⏳' : '▶️'), h("button", { class: "btn btn-icon btn-delete", onClick: () => this.deleteTestCase(testCase.id), title: "Delete this test" }, "\uD83D\uDDD1\uFE0F")))))), h("div", { key: '2d4446b15a31adfbaf88b3d46fcbb3cf314f8861', class: "add-test-case" }, h("button", { key: '737e9d856db0f9090923bef718bb9bfde974b950', class: "btn btn-outline", onClick: () => this.addNewTestCase() }, "+ Add Question")))));
265
319
  }
266
320
  static get is() { return "llm-test-runner"; }
267
321
  static get encapsulation() { return "shadow"; }
@@ -277,16 +331,16 @@ export class LLMTestRunner {
277
331
  }
278
332
  static get properties() {
279
333
  return {
280
- "apiKey": {
281
- "type": "string",
334
+ "delayMs": {
335
+ "type": "number",
282
336
  "mutable": false,
283
337
  "complexType": {
284
- "original": "string",
285
- "resolved": "string",
338
+ "original": "number",
339
+ "resolved": "number",
286
340
  "references": {}
287
341
  },
288
342
  "required": false,
289
- "optional": false,
343
+ "optional": true,
290
344
  "docs": {
291
345
  "tags": [],
292
346
  "text": ""
@@ -294,7 +348,8 @@ export class LLMTestRunner {
294
348
  "getter": false,
295
349
  "setter": false,
296
350
  "reflect": false,
297
- "attribute": "api-key"
351
+ "attribute": "delay-ms",
352
+ "defaultValue": "500"
298
353
  }
299
354
  };
300
355
  }
@@ -303,8 +358,33 @@ export class LLMTestRunner {
303
358
  "testCases": {},
304
359
  "isRunningAll": {},
305
360
  "error": {},
306
- "isExportingTestSuite": {}
361
+ "isExportingTestSuite": {},
362
+ "isExportingTestResults": {}
307
363
  };
308
364
  }
365
+ static get events() {
366
+ return [{
367
+ "method": "llmRequest",
368
+ "name": "llmRequest",
369
+ "bubbles": true,
370
+ "cancelable": true,
371
+ "composed": true,
372
+ "docs": {
373
+ "tags": [],
374
+ "text": ""
375
+ },
376
+ "complexType": {
377
+ "original": "LLMRequestPayload",
378
+ "resolved": "LLMRequestPayload",
379
+ "references": {
380
+ "LLMRequestPayload": {
381
+ "location": "local",
382
+ "path": "/Users/akhilan/Documents/work/llm-testrunner/packages/stencil-library/src/components/llm-test-runner/llm-test-runner.tsx",
383
+ "id": "src/components/llm-test-runner/llm-test-runner.tsx::LLMRequestPayload"
384
+ }
385
+ }
386
+ }
387
+ }];
388
+ }
309
389
  }
310
390
  //# sourceMappingURL=llm-test-runner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"llm-test-runner.js","sourceRoot":"","sources":["../../../src/components/llm-test-runner/llm-test-runner.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Component, State, Prop, h } from '@stencil/core';\nimport { LLMEvaluationEngine } from '../../lib/evaluation/evaluation-engine';\nimport { EvaluationRequest, EvaluationResult } from '../../lib/evaluation/types';\nimport { v4 as uuidv4 } from 'uuid';\nimport { ErrorMessage } from '../error-message/error-message';\n\nexport interface TestCase {\n id: string;\n question: string;\n expectedKeywords: string[];\n expectedSourceLinks: string[];\n output?: string;\n isRunning?: boolean;\n error?: string;\n evaluationResult?: EvaluationResult;\n}\n\n@Component({\n tag: 'llm-test-runner',\n styleUrl: 'llm-test-runner.css',\n shadow: true,\n})\nexport class LLMTestRunner {\n @Prop() apiKey: string;\n \n @State() testCases: TestCase[] = [\n {\n id: '1',\n question: '',\n expectedKeywords: [],\n expectedSourceLinks: [],\n isRunning: false\n }\n ];\n @State() isRunningAll: boolean = false;\n @State() error: string = '';\n\n private fileInput!: HTMLInputElement;\n @State() isExportingTestSuite: boolean = false;\n\n private evaluationEngine: LLMEvaluationEngine;\n private apiUrl: string = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent';\n\n async componentWillLoad() {\n this.evaluationEngine = new LLMEvaluationEngine();\n \n console.log('🔍 componentWillLoad - apiKey:', this.apiKey ? 'SET' : 'NOT SET');\n console.log('🔍 componentWillLoad - apiKey value:', this.apiKey);\n \n if (!this.apiKey) {\n throw new Error('API key is required. Please provide the apiKey prop: <llm-test-runner apiKey=\"your-api-key\" />');\n }\n }\n\n\n componentDidLoad() {\n }\n\n disconnectedCallback() {\n }\n\n private addNewTestCase() {\n const newTestCase: TestCase = {\n id: this.generateId(),\n question: '',\n expectedKeywords: [],\n expectedSourceLinks: [],\n isRunning: false\n };\n this.testCases = [...this.testCases, newTestCase];\n }\n\n private updateTestCase(id: string, updates: Partial<TestCase>) {\n this.testCases = this.testCases.map(tc => \n tc.id === id ? { ...tc, ...updates } : tc\n );\n }\n\n private async runSingleTest(testCase: TestCase) {\n console.log('🚀 Starting test for question:', testCase.question);\n \n this.updateTestCase(testCase.id, { isRunning: true });\n \n try {\n const aiResponse = await this.callGeminiAPI(testCase.question);\n console.log('✅ AI call completed for test case:', testCase.id);\n \n this.updateTestCase(testCase.id, {\n isRunning: false,\n output: aiResponse,\n error: null\n });\n \n await this.evaluateResponse({\n ...testCase,\n output: aiResponse\n });\n \n } catch (error) {\n console.error('❌ Error in runSingleTest:', error);\n this.updateTestCase(testCase.id, {\n isRunning: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error'\n });\n }\n }\n\n private deleteTestCase(id: string) {\n this.testCases = this.testCases.filter(tc => tc.id !== id);\n }\n\n private addKeyword(testCaseId: string, keyword: string) {\n if (keyword.trim()) {\n const testCase = this.testCases.find(tc => tc.id === testCaseId);\n if (testCase) {\n this.updateTestCase(testCaseId, {\n expectedKeywords: [...testCase.expectedKeywords, keyword.trim()]\n });\n }\n }\n }\n\n private removeKeyword(testCaseId: string, index: number) {\n const testCase = this.testCases.find(tc => tc.id === testCaseId);\n if (testCase) {\n const newKeywords = testCase.expectedKeywords.filter((_, i) => i !== index);\n this.updateTestCase(testCaseId, { expectedKeywords: newKeywords });\n }\n }\n\n private addSourceLink(testCaseId: string, link: string) {\n if (link.trim()) {\n const testCase = this.testCases.find(tc => tc.id === testCaseId);\n if (testCase) {\n this.updateTestCase(testCaseId, {\n expectedSourceLinks: [...testCase.expectedSourceLinks, link.trim()]\n });\n }\n }\n }\n\n private removeSourceLink(testCaseId: string, index: number) {\n const testCase = this.testCases.find(tc => tc.id === testCaseId);\n if (testCase) {\n const newLinks = testCase.expectedSourceLinks.filter((_, i) => i !== index);\n this.updateTestCase(testCaseId, { expectedSourceLinks: newLinks });\n }\n }\n\n private async callGeminiAPI(prompt: string): Promise<string> {\n console.log('🔍 callGeminiAPI - apiKey:', this.apiKey ? 'SET' : 'NOT SET');\n console.log('🔍 callGeminiAPI - apiKey value:', this.apiKey ? `${this.apiKey.substring(0, 10)}...` : 'undefined');\n \n if (!this.apiKey) {\n throw new Error('API key is required. Please provide the apiKey prop.');\n }\n\n const requestBody = {\n contents: [{\n parts: [{\n text: prompt\n }]\n }]\n };\n\n const response = await fetch(`${this.apiUrl}?key=${this.apiKey}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.error?.message || `HTTP error! status: ${response.status}`);\n }\n\n const data = await response.json();\n \n if (data.candidates && data.candidates[0] && data.candidates[0].content) {\n return data.candidates[0].content.parts[0].text;\n } else {\n throw new Error('Unexpected response format from Gemini API');\n }\n }\n\n private async evaluateResponse(testCase: TestCase): Promise<void> {\n if (!testCase.output) {\n console.warn('⚠️ No output to evaluate for test case:', testCase.id);\n return;\n }\n\n const evaluationRequest: EvaluationRequest = {\n testCaseId: testCase.id,\n question: testCase.question,\n expectedKeywords: testCase.expectedKeywords,\n expectedSourceLinks: testCase.expectedSourceLinks,\n actualResponse: testCase.output\n };\n\n await this.evaluationEngine.evaluateResponse(evaluationRequest, (result: EvaluationResult) => {\n console.log('📊 Evaluation result received:', result);\n \n this.updateTestCase(testCase.id, {\n evaluationResult: result\n });\n });\n }\n\n private async runAllTests() {\n this.isRunningAll = true;\n \n for (const testCase of this.testCases) {\n if (!testCase.isRunning && testCase.question.trim()) {\n await this.runSingleTest(testCase);\n // Delay between tests to avoid rate limiting\n await new Promise(resolve => setTimeout(resolve, 1000));\n }\n }\n this.isRunningAll = false;\n }\n\n private generateId(): string {\n return uuidv4();\n }\n\n private handleFileSelect(): void {\n this.fileInput.click();\n }\n\n private async handleFileChange(event: Event): Promise<void> {\n const target = event.target as HTMLInputElement;\n const file = target.files?.[0];\n\n // Immediately clear the input value to allow for a new upload.\n target.value = '';\n\n if (!file) {\n this.error = 'No file selected.';\n return;\n }\n\n const isJsonType = file.type === 'application/json';\n const isJsonExtension = file.name.toLowerCase().endsWith('.json');\n if (!isJsonType && !isJsonExtension) {\n this.error = 'Invalid file type. Please select a JSON file.';\n return;\n }\n\n this.error = '';\n\n try {\n const content = await this.readFileAsync(file);\n const fileContent = JSON.parse(content);\n\n if (!Array.isArray(fileContent)) {\n throw new Error(\"Invalid JSON structure. Expected a JSON array.\");\n } \n const importedTestCases: TestCase[] = fileContent.map((item) => ({\n id: this.generateId(),\n question: item.question || '',\n expectedKeywords: Array.isArray(item.expectedKeywords) ? item.expectedKeywords : [],\n expectedSourceLinks: Array.isArray(item.expectedSourceLinks) ? item.expectedSourceLinks : [],\n isRunning: false\n }));\n this.testCases = importedTestCases; \n } catch (err) {\n this.error = err?.message || 'Error processing file. Please ensure it is a valid JSON array.';\n console.error('File Processing Error:', err);\n }\n }\n\n private readFileAsync(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(reader.result as string);\n reader.onerror = () => reject(reader.error);\n reader.readAsText(file);\n });\n }\n\n private downloadFile(content: string, filename: string, mimeType: string) {\n const blob = new Blob([content], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n }\n\n private async handleExportTestSuite() {\n this.isExportingTestSuite = true;\n try {\n // Exporting only input data (question, expected keywords, expected source links)\n const exportData = this.testCases.map(testCase => ({\n id: testCase.id,\n question: testCase.question,\n expectedKeywords: testCase.expectedKeywords,\n expectedSourceLinks: testCase.expectedSourceLinks\n }));\n\n const jsonContent = JSON.stringify(exportData, null, 2);\n\n // Added a small delay to show the loading state\n await new Promise(resolve => setTimeout(resolve, 500));\n\n this.downloadFile(jsonContent, 'test-suite.json', 'application/json');\n } finally {\n this.isExportingTestSuite = false;\n }\n }\n\n render() {\n return (\n <div class=\"test-runner-container\">\n <header class=\"test-runner-header\">\n <div class=\"header-left\">\n <input\n class=\"hidden\"\n type=\"file\"\n ref={(el) => (this.fileInput = el as HTMLInputElement)}\n onChange={(e) => this.handleFileChange(e)}\n accept=\".json,application/json\"\n />\n <button class=\"btn btn-secondary\" onClick={() => this.handleFileSelect()}>\n <span class=\"icon\">↑</span>\n Import Test Suite\n </button>\n <button\n class=\"btn btn-secondary\"\n onClick={() => this.handleExportTestSuite()}\n disabled={this.isExportingTestSuite}\n >\n <span class=\"icon\">\n {this.isExportingTestSuite ? '⏳' : '↓'}\n </span>\n {this.isExportingTestSuite ? 'Exporting...' : 'Export Test Suite'}\n </button>\n </div>\n \n <div class=\"header-right\">\n <button class=\"btn btn-secondary\">\n <span class=\"icon\">⚙️</span>\n Prompt Editor\n </button>\n <button class=\"btn btn-secondary\">\n <span class=\"icon\">↓</span>\n Export Test Results\n </button>\n <button \n class=\"btn btn-primary\" \n onClick={() => this.runAllTests()}\n disabled={this.isRunningAll}\n >\n {this.isRunningAll ? 'Running...' : 'Run All'}\n </button>\n </div>\n </header>\n <ErrorMessage\n message={this.error}\n onClear={() => (this.error = '')}\n />\n <div class=\"test-runner-content\">\n <div class=\"column-headers\">\n <div class=\"column-header\">Input</div>\n <div class=\"column-header\">Output</div>\n <div class=\"column-header\">Evaluation</div>\n <div class=\"column-header\">Actions</div>\n </div>\n\n <div class=\"test-cases\">\n {this.testCases.map((testCase) => (\n <div class=\"test-case-row\" key={testCase.id}>\n <div class=\"input-column\">\n <div class=\"input-group\">\n <label>Question</label>\n <textarea\n value={testCase.question}\n onInput={(e) => this.updateTestCase(testCase.id, { \n question: (e.target as HTMLTextAreaElement).value \n })}\n placeholder=\"Enter your question here...\"\n rows={3}\n ></textarea>\n </div>\n \n <div class=\"keywords-group\">\n <label>Expected keywords</label>\n <div class=\"tags-container\">\n {testCase.expectedKeywords.map((keyword, index) => (\n <span class=\"tag\" key={index}>\n {keyword}\n <button \n class=\"tag-remove\" \n onClick={() => this.removeKeyword(testCase.id, index)}\n >×</button>\n </span>\n ))}\n <input\n type=\"text\"\n placeholder=\"New item...\"\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n this.addKeyword(testCase.id, (e.target as HTMLInputElement).value);\n (e.target as HTMLInputElement).value = '';\n }\n }}\n />\n </div>\n </div>\n \n <div class=\"links-group\">\n <label>Expected source links</label>\n <div class=\"links-container\">\n {testCase.expectedSourceLinks.map((link, index) => (\n <div class=\"link-item\" key={index}>\n <a href={link} target=\"_blank\" rel=\"noopener noreferrer\">{link}</a>\n <button \n class=\"link-remove\" \n onClick={() => this.removeSourceLink(testCase.id, index)}\n >×</button>\n </div>\n ))}\n <input\n type=\"url\"\n placeholder=\"New item...\"\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n this.addSourceLink(testCase.id, (e.target as HTMLInputElement).value);\n (e.target as HTMLInputElement).value = '';\n }\n }}\n />\n </div>\n </div>\n </div>\n\n <div class=\"output-column\">\n {testCase.output ? (\n <div class=\"output-content\">\n {testCase.output}\n </div>\n ) : (\n <div class=\"output-placeholder\">\n {testCase.isRunning ? 'Running...' : ''}\n </div>\n )}\n </div>\n\n <div class=\"evaluation-column\">\n {testCase.evaluationResult ? (\n <div class=\"evaluation-result\">\n <div class={`evaluation-status ${testCase.evaluationResult.passed ? 'passed' : 'failed'}`}>\n {testCase.evaluationResult.passed ? '✅ PASSED' : '❌ FAILED'}\n </div>\n <div class=\"evaluation-details\">\n Keywords: {testCase.evaluationResult.keywordMatches.filter(m => m.found).length}/{testCase.evaluationResult.keywordMatches.length} found\n </div>\n </div>\n ) : (\n <div class=\"evaluation-placeholder\">\n {testCase.isRunning ? 'Evaluating...' : ''}\n </div>\n )}\n </div>\n\n <div class=\"actions-column\">\n <button \n class=\"btn btn-icon btn-run\"\n onClick={() => this.runSingleTest(testCase)}\n disabled={testCase.isRunning || !testCase.question.trim()}\n title={!testCase.question.trim() ? \"Enter a question first\" : \"Run this test\"}\n >\n {testCase.isRunning ? '⏳' : '▶️'}\n </button>\n <button \n class=\"btn btn-icon btn-delete\"\n onClick={() => this.deleteTestCase(testCase.id)}\n title=\"Delete this test\"\n >\n 🗑️\n </button>\n </div>\n </div>\n ))}\n </div>\n\n <div class=\"add-test-case\">\n <button class=\"btn btn-outline\" onClick={() => this.addNewTestCase()}>\n + Add Question\n </button>\n </div>\n </div>\n </div>\n );\n }\n}"]}
1
+ {"version":3,"file":"llm-test-runner.js","sourceRoot":"","sources":["../../../src/components/llm-test-runner/llm-test-runner.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Component, State, Prop, h, EventEmitter, Event } from '@stencil/core';\nimport { LLMEvaluationEngine } from '../../lib/evaluation/evaluation-engine';\nimport { EvaluationRequest, EvaluationResult } from '../../lib/evaluation/types';\nimport { v4 as uuidv4 } from 'uuid';\nimport { ErrorMessage } from '../error-message/error-message';\nimport { RateLimitedFetcher } from '../../lib/rate-limited-fetcher/rate-limited-fetcher';\n\nexport interface TestCase {\n id: string;\n question: string;\n expectedKeywords: string[];\n expectedSourceLinks: string[];\n output?: string;\n isRunning?: boolean;\n error?: string;\n evaluationResult?: EvaluationResult;\n responseTime?: number; // Time taken by the callback to execute in milliseconds\n}\n\nexport interface LLMRequestPayload {\n prompt: string;\n resolve: (result: string) => void;\n reject: (err: any) => void;\n}\n\n@Component({\n tag: 'llm-test-runner',\n styleUrl: 'llm-test-runner.css',\n shadow: true,\n})\nexport class LLMTestRunner {\n @Event() llmRequest: EventEmitter<LLMRequestPayload>;\n @Prop() delayMs?: number = 500; \n @State() testCases: TestCase[] = [\n {\n id: '1',\n question: '',\n expectedKeywords: [],\n expectedSourceLinks: [],\n isRunning: false\n }\n ];\n @State() isRunningAll: boolean = false;\n @State() error: string = '';\n\n private fileInput!: HTMLInputElement;\n @State() isExportingTestSuite: boolean = false;\n @State() isExportingTestResults: boolean = false;\n\n private evaluationEngine: LLMEvaluationEngine;\n\n componentWillLoad() {\n this.evaluationEngine = new LLMEvaluationEngine();\n }\n\n componentDidLoad() {\n }\n\n disconnectedCallback() {\n }\n\n private addNewTestCase() {\n const newTestCase: TestCase = {\n id: this.generateId(),\n question: '',\n expectedKeywords: [],\n expectedSourceLinks: [],\n isRunning: false\n };\n this.testCases = [...this.testCases, newTestCase];\n }\n\n private updateTestCase(id: string, updates: Partial<TestCase>) {\n this.testCases = this.testCases.map(tc => \n tc.id === id ? { ...tc, ...updates } : tc\n );\n }\n\n private async runSingleTest(testCase: TestCase): Promise<void> {\n console.log('🚀 Starting test for question:', testCase.question);\n const startTime = Date.now();\n this.updateTestCase(testCase.id, { isRunning: true });\n return new Promise<void>((resolve, reject) => {\n this.llmRequest.emit({\n prompt: testCase.question,\n resolve: async (aiResponse: string) => {\n console.log('✅ AI call completed for test case:', testCase.id);\n const endTime = Date.now();\n const responseTime = endTime - startTime;\n this.updateTestCase(testCase.id, {\n isRunning: false,\n output: aiResponse,\n error: null,\n responseTime: responseTime\n });\n\n await this.evaluateResponse({\n ...testCase,\n output: aiResponse,\n responseTime: responseTime\n });\n resolve()\n },\n reject: (error: any) => {\n console.error('❌ Error in runSingleTest:', error);\n this.updateTestCase(testCase.id, {\n isRunning: false,\n output: null,\n error: error instanceof Error ? error.message : 'Unknown error'\n });\n reject(error);\n }\n });\n })\n }\n\n private deleteTestCase(id: string) {\n this.testCases = this.testCases.filter(tc => tc.id !== id);\n }\n\n private addKeyword(testCaseId: string, keyword: string) {\n if (keyword.trim()) {\n const testCase = this.testCases.find(tc => tc.id === testCaseId);\n if (testCase) {\n this.updateTestCase(testCaseId, {\n expectedKeywords: [...testCase.expectedKeywords, keyword.trim()]\n });\n }\n }\n }\n\n private removeKeyword(testCaseId: string, index: number) {\n const testCase = this.testCases.find(tc => tc.id === testCaseId);\n if (testCase) {\n const newKeywords = testCase.expectedKeywords.filter((_, i) => i !== index);\n this.updateTestCase(testCaseId, { expectedKeywords: newKeywords });\n }\n }\n\n private addSourceLink(testCaseId: string, link: string) {\n if (link.trim()) {\n const testCase = this.testCases.find(tc => tc.id === testCaseId);\n if (testCase) {\n this.updateTestCase(testCaseId, {\n expectedSourceLinks: [...testCase.expectedSourceLinks, link.trim()]\n });\n }\n }\n }\n\n private removeSourceLink(testCaseId: string, index: number) {\n const testCase = this.testCases.find(tc => tc.id === testCaseId);\n if (testCase) {\n const newLinks = testCase.expectedSourceLinks.filter((_, i) => i !== index);\n this.updateTestCase(testCaseId, { expectedSourceLinks: newLinks });\n }\n }\n\n private async evaluateResponse(testCase: TestCase): Promise<void> {\n if (!testCase.output) {\n console.warn('⚠️ No output to evaluate for test case:', testCase.id);\n return;\n }\n\n const evaluationRequest: EvaluationRequest = {\n testCaseId: testCase.id,\n question: testCase.question,\n expectedKeywords: testCase.expectedKeywords,\n expectedSourceLinks: testCase.expectedSourceLinks,\n actualResponse: testCase.output\n };\n\n await this.evaluationEngine.evaluateResponse(evaluationRequest, (result: EvaluationResult) => {\n console.log('📊 Evaluation result received:', result);\n \n this.updateTestCase(testCase.id, {\n evaluationResult: result\n });\n });\n }\n\n private async runAllTests() {\n this.isRunningAll = true;\n const tasks = [];\n for (const testCase of this.testCases) {\n if (!testCase.isRunning && testCase.question.trim()) {\n tasks.push(() => this.runSingleTest(testCase).catch(err => {\n console.error(`⚠️ Test case ${testCase.id} failed`, err);\n }))\n }\n }\n try {\n const fetcher = new RateLimitedFetcher(this.delayMs);\n await fetcher.runAll(tasks);\n } catch (err) {\n console.error('⚠️ Error running all tests:', err);\n }\n this.isRunningAll = false;\n }\n\n private generateId(): string {\n return uuidv4();\n }\n\n private handleFileSelect(): void {\n this.fileInput.click();\n }\n\n private async handleFileChange(event: Event): Promise<void> {\n const target = event.target as HTMLInputElement;\n const file = target.files?.[0];\n\n // Immediately clear the input value to allow for a new upload.\n target.value = '';\n\n if (!file) {\n this.error = 'No file selected.';\n return;\n }\n\n const isJsonType = file.type === 'application/json';\n const isJsonExtension = file.name.toLowerCase().endsWith('.json');\n if (!isJsonType && !isJsonExtension) {\n this.error = 'Invalid file type. Please select a JSON file.';\n return;\n }\n\n this.error = '';\n\n try {\n const content = await this.readFileAsync(file);\n const fileContent = JSON.parse(content);\n\n if (!Array.isArray(fileContent)) {\n throw new Error(\"Invalid JSON structure. Expected a JSON array.\");\n } \n const importedTestCases: TestCase[] = fileContent.map((item) => ({\n id: this.generateId(),\n question: item.question || '',\n expectedKeywords: Array.isArray(item.expectedKeywords) ? item.expectedKeywords : [],\n expectedSourceLinks: Array.isArray(item.expectedSourceLinks) ? item.expectedSourceLinks : [],\n isRunning: false\n }));\n this.testCases = importedTestCases; \n } catch (err) {\n this.error = err?.message || 'Error processing file. Please ensure it is a valid JSON array.';\n console.error('File Processing Error:', err);\n }\n }\n\n private readFileAsync(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(reader.result as string);\n reader.onerror = () => reject(reader.error);\n reader.readAsText(file);\n });\n }\n\n private downloadFile(content: string, filename: string, mimeType: string) {\n const blob = new Blob([content], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n }\n\n private async handleExportTestSuite() {\n this.isExportingTestSuite = true;\n try {\n // Exporting only input data (question, expected keywords, expected source links)\n const exportData = this.testCases.map(testCase => ({\n id: testCase.id,\n question: testCase.question,\n expectedKeywords: testCase.expectedKeywords,\n expectedSourceLinks: testCase.expectedSourceLinks\n }));\n\n const jsonContent = JSON.stringify(exportData, null, 2);\n\n // Added a small delay to show the loading state\n await new Promise(resolve => setTimeout(resolve, 500));\n\n this.downloadFile(jsonContent, 'test-suite.json', 'application/json');\n } finally {\n this.isExportingTestSuite = false;\n }\n }\n\n private async handleExportTestResults() {\n this.isExportingTestResults = true;\n try {\n // Create CSV content with the required fields\n const csvRows = [];\n \n // Add header row\n const headers = [\n 'Question',\n 'Expected Keywords',\n 'Generated Keywords',\n 'Keywords Match',\n 'Expected Source Links',\n 'Generated Source Links',\n 'Source Links Match',\n 'Response Time (s)'\n ];\n csvRows.push(headers.join(','));\n \n // Add data rows\n this.testCases.forEach(testCase => {\n const expectedKeywords = testCase.expectedKeywords.join('; ');\n const expectedSourceLinks = testCase.expectedSourceLinks.join('; ');\n \n let generatedKeywords = '';\n let generatedSourceLinks = '';\n let keywordsMatch = '';\n let sourceLinksMatch = '';\n \n if (testCase.evaluationResult) {\n const foundKeywords = testCase.evaluationResult.keywordMatches\n .filter(match => match.found)\n .map(match => match.keyword);\n const foundSourceLinks = testCase.evaluationResult.sourceLinkMatches\n .filter(match => match.found)\n .map(match => match.link);\n \n generatedKeywords = foundKeywords.join('; ');\n generatedSourceLinks = foundSourceLinks.join('; ');\n \n // Calculate match percentages\n const keywordMatchCount = testCase.evaluationResult.keywordMatches.filter(m => m.found).length;\n const totalKeywords = testCase.evaluationResult.keywordMatches.length;\n const sourceLinkMatchCount = testCase.evaluationResult.sourceLinkMatches.filter(m => m.found).length;\n const totalSourceLinks = testCase.evaluationResult.sourceLinkMatches.length;\n \n keywordsMatch = totalKeywords > 0 ? `${keywordMatchCount}/${totalKeywords}` : 'N/A';\n sourceLinksMatch = totalSourceLinks > 0 ? `${sourceLinkMatchCount}/${totalSourceLinks}` : 'N/A';\n }\n \n const responseTime = testCase.responseTime ? (testCase.responseTime / 1000).toFixed(3) : 'N/A';\n \n const row = [\n this.escapeCsvField(testCase.question),\n this.escapeCsvField(expectedKeywords),\n this.escapeCsvField(generatedKeywords),\n keywordsMatch,\n this.escapeCsvField(expectedSourceLinks),\n this.escapeCsvField(generatedSourceLinks), \n sourceLinksMatch,\n responseTime\n ];\n \n csvRows.push(row.join(','));\n });\n \n const csvContent = csvRows.join('\\n');\n \n // Added a small delay to show the loading state\n await new Promise(resolve => setTimeout(resolve, 500));\n \n this.downloadFile(csvContent, 'test-results.csv', 'text/csv');\n } finally {\n this.isExportingTestResults = false;\n }\n }\n\n private escapeCsvField(field: string): string {\n if (field.includes(',') || field.includes('\"') || field.includes('\\n')) {\n return `\"${field.replace(/\"/g, '\"\"')}\"`;\n }\n return field;\n }\n\n render() {\n return (\n <div class=\"test-runner-container\">\n <header class=\"test-runner-header\">\n <div class=\"header-left\">\n <input\n class=\"hidden\"\n type=\"file\"\n ref={(el) => (this.fileInput = el as HTMLInputElement)}\n onChange={(e) => this.handleFileChange(e)}\n accept=\".json,application/json\"\n />\n <button class=\"btn btn-secondary\" onClick={() => this.handleFileSelect()}>\n <span class=\"icon\">↑</span>\n Import Test Suite\n </button>\n <button\n class=\"btn btn-secondary\"\n onClick={() => this.handleExportTestSuite()}\n disabled={this.isExportingTestSuite}\n >\n <span class=\"icon\">\n {this.isExportingTestSuite ? '⏳' : '↓'}\n </span>\n {this.isExportingTestSuite ? 'Exporting...' : 'Export Test Suite'}\n </button>\n </div>\n \n <div class=\"header-right\">\n <button class=\"btn btn-secondary\">\n <span class=\"icon\">⚙️</span>\n Prompt Editor\n </button>\n <button \n class=\"btn btn-secondary\"\n onClick={() => this.handleExportTestResults()}\n disabled={this.isExportingTestResults}\n >\n <span class=\"icon\">\n {this.isExportingTestResults ? '⏳' : '↓'}\n </span>\n {this.isExportingTestResults ? 'Exporting...' : 'Export Test Results'}\n </button>\n <button \n class=\"btn btn-primary\" \n onClick={() => this.runAllTests()}\n disabled={this.isRunningAll}\n >\n {this.isRunningAll ? 'Running...' : 'Run All'}\n </button>\n </div>\n </header>\n <ErrorMessage\n message={this.error}\n onClear={() => (this.error = '')}\n />\n <div class=\"test-runner-content\">\n <div class=\"column-headers\">\n <div class=\"column-header\">Input</div>\n <div class=\"column-header\">Output</div>\n <div class=\"column-header\">Evaluation</div>\n <div class=\"column-header\">Actions</div>\n </div>\n\n <div class=\"test-cases\">\n {this.testCases.map((testCase) => (\n <div class=\"test-case-row\" key={testCase.id}>\n <div class=\"input-column\">\n <div class=\"input-group\">\n <label>Question</label>\n <textarea\n value={testCase.question}\n onInput={(e) => this.updateTestCase(testCase.id, { \n question: (e.target as HTMLTextAreaElement).value \n })}\n placeholder=\"Enter your question here...\"\n rows={3}\n ></textarea>\n </div>\n \n <div class=\"keywords-group\">\n <label>Expected keywords</label>\n <div class=\"tags-container\">\n {testCase.expectedKeywords.map((keyword, index) => (\n <span class=\"tag\" key={index}>\n {keyword}\n <button \n class=\"tag-remove\" \n onClick={() => this.removeKeyword(testCase.id, index)}\n >×</button>\n </span>\n ))}\n <input\n type=\"text\"\n placeholder=\"New item...\"\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n this.addKeyword(testCase.id, (e.target as HTMLInputElement).value);\n (e.target as HTMLInputElement).value = '';\n }\n }}\n />\n </div>\n </div>\n \n <div class=\"links-group\">\n <label>Expected source links</label>\n <div class=\"links-container\">\n {testCase.expectedSourceLinks.map((link, index) => (\n <div class=\"link-item\" key={index}>\n <a href={link} target=\"_blank\" rel=\"noopener noreferrer\">{link}</a>\n <button \n class=\"link-remove\" \n onClick={() => this.removeSourceLink(testCase.id, index)}\n >×</button>\n </div>\n ))}\n <input\n type=\"url\"\n placeholder=\"New item...\"\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n this.addSourceLink(testCase.id, (e.target as HTMLInputElement).value);\n (e.target as HTMLInputElement).value = '';\n }\n }}\n />\n </div>\n </div>\n </div>\n\n <div class=\"output-column\">\n {testCase.output ? (\n <div class=\"output-content\">\n {testCase.output}\n </div>\n ) : (\n <div class=\"output-placeholder\">\n {testCase.isRunning ? 'Running...' : ''}\n </div>\n )}\n </div>\n\n <div class=\"evaluation-column\">\n {testCase.evaluationResult ? (\n <div class=\"evaluation-result\">\n <div class={`evaluation-status ${testCase.evaluationResult.passed ? 'passed' : 'failed'}`}>\n {testCase.evaluationResult.passed ? '✅ PASSED' : '❌ FAILED'}\n </div>\n <div class=\"evaluation-details\">\n Keywords: {testCase.evaluationResult.keywordMatches.filter(m => m.found).length}/{testCase.evaluationResult.keywordMatches.length} found\n </div>\n </div>\n ) : (\n <div class=\"evaluation-placeholder\">\n {testCase.isRunning ? 'Evaluating...' : ''}\n </div>\n )}\n </div>\n\n <div class=\"actions-column\">\n <button \n class=\"btn btn-icon btn-run\"\n onClick={() => this.runSingleTest(testCase)}\n disabled={testCase.isRunning || !testCase.question.trim()}\n title={!testCase.question.trim() ? \"Enter a question first\" : \"Run this test\"}\n >\n {testCase.isRunning ? '⏳' : '▶️'}\n </button>\n <button \n class=\"btn btn-icon btn-delete\"\n onClick={() => this.deleteTestCase(testCase.id)}\n title=\"Delete this test\"\n >\n 🗑️\n </button>\n </div>\n </div>\n ))}\n </div>\n\n <div class=\"add-test-case\">\n <button class=\"btn btn-outline\" onClick={() => this.addNewTestCase()}>\n + Add Question\n </button>\n </div>\n </div>\n </div>\n );\n }\n}"]}
@@ -0,0 +1,6 @@
1
+ export default function () {
2
+ window.env = {
3
+ API_KEY: '__GEMINI_API_KEY__'
4
+ };
5
+ }
6
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/global/env.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,OAAO;IACZ,MAAM,CAAC,GAAG,GAAG;QACX,OAAO,EAAE,oBAAoB;KAC9B,CAAC;AACJ,CAAC","sourcesContent":["declare global {\n interface Window {\n env: {\n API_KEY: string;\n };\n }\n}\n\nexport default function () {\n window.env = {\n API_KEY: '__GEMINI_API_KEY__'\n };\n}"]}
@@ -0,0 +1,39 @@
1
+ export class RateLimitedFetcher {
2
+ queue = [];
3
+ delay; // delay in milliseconds
4
+ intervalId;
5
+ constructor(delayMs) {
6
+ this.delay = delayMs;
7
+ }
8
+ startQueue() {
9
+ if (this.intervalId)
10
+ return;
11
+ this.intervalId = setInterval(() => {
12
+ const task = this.queue.shift();
13
+ if (task)
14
+ task();
15
+ if (this.queue.length === 0) {
16
+ this.stop();
17
+ }
18
+ }, this.delay);
19
+ }
20
+ schedule(task) {
21
+ return new Promise((resolve, reject) => {
22
+ this.queue.push(() => {
23
+ task().then(resolve).catch(reject);
24
+ });
25
+ this.startQueue();
26
+ });
27
+ }
28
+ stop() {
29
+ if (this.intervalId) {
30
+ clearInterval(this.intervalId);
31
+ this.intervalId = undefined;
32
+ }
33
+ }
34
+ async runAll(tasks) {
35
+ const promises = tasks.map(task => this.schedule(task));
36
+ return Promise.all(promises);
37
+ }
38
+ }
39
+ //# sourceMappingURL=rate-limited-fetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limited-fetcher.js","sourceRoot":"","sources":["../../../src/lib/rate-limited-fetcher/rate-limited-fetcher.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,kBAAkB;IACrB,KAAK,GAAmB,EAAE,CAAC;IAC3B,KAAK,CAAS,CAAC,wBAAwB;IACvC,UAAU,CAAO;IAEzB,YAAY,OAAe;QACzB,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC;IACvB,CAAC;IAEO,UAAU;QAChB,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO;QAC5B,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE;YACjC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YAChC,IAAI,IAAI;gBAAE,IAAI,EAAE,CAAC;YACjB,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC;IAEM,QAAQ,CAAI,IAAsB;QACvC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE;gBACnB,IAAI,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACrC,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,IAAI;QACT,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC9B,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,MAAM,CAAI,KAA8B;QACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QACxD,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;CACF","sourcesContent":["export class RateLimitedFetcher {\n private queue: (() => void)[] = [];\n private delay: number; // delay in milliseconds\n private intervalId?: any;\n\n constructor(delayMs: number) {\n this.delay = delayMs;\n }\n\n private startQueue() {\n if (this.intervalId) return;\n this.intervalId = setInterval(() => {\n const task = this.queue.shift();\n if (task) task();\n if (this.queue.length === 0) {\n this.stop();\n }\n }, this.delay);\n }\n\n public schedule<T>(task: () => Promise<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n this.queue.push(() => {\n task().then(resolve).catch(reject);\n });\n this.startQueue(); \n });\n }\n\n public stop() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = undefined;\n }\n }\n\n public async runAll<T>(tasks: Array<() => Promise<T>>): Promise<T[]> {\n const promises = tasks.map(task => this.schedule(task));\n return Promise.all(promises);\n }\n}\n"]}