/** * Qwen Testing Interface * Comprehensive testing environment for Qwen tokens and sequences */ import { app } from "../../../scripts/app.js"; import { $el } from "../../../scripts/ui.js"; // Test case templates const TEST_TEMPLATES = { basic_vision: { name: "Basic Vision Processing", template: "Describe this image: <|vision_start|><|image_pad|><|vision_end|>", description: "Simple vision processing without chat formatting", category: "vision" }, chat_vision: { name: "Chat + Vision", template: "<|im_start|>user\nAnalyze this image: <|vision_start|><|image_pad|><|vision_end|>\nWhat objects do you see?<|im_end|>", description: "Vision processing within chat format", category: "chat" }, spatial_edit: { name: "Spatial Editing", template: "<|im_start|>user\nEdit the <|object_ref_start|>car<|object_ref_end|> at <|box_start|>100,50,300,200<|box_end|> in this image: <|vision_start|><|image_pad|><|vision_end|>\nMake it red.<|im_end|>", description: "Spatial object editing with coordinates", category: "spatial" }, polygon_reference: { name: "Polygon Reference", template: "Modify the building outline <|quad_start|>10,20 100,25 95,80 8,75<|quad_end|> to add windows.", description: "Complex polygon-based spatial references", category: "spatial" }, multi_object: { name: "Multiple Objects", template: "Edit the <|object_ref_start|>car<|object_ref_end|> at <|box_start|>100,100,200,200<|box_end|> and the <|object_ref_start|>tree<|object_ref_end|> at <|box_start|>300,50,400,150<|box_end|>", description: "Multiple object references in one prompt", category: "spatial" }, code_completion: { name: "Code Completion", template: "<|fim_prefix|>def process_image(image):\n # Load the image<|fim_suffix|>\n return processed_image<|fim_middle|>", description: "Fill-in-the-middle code completion", category: "code" }, tool_calling: { name: "Tool Calling", template: "<|im_start|>user\nGenerate an image of a sunset<|im_end|>\n<|im_start|>assistant\n\n{\"name\": \"generate_image\", \"arguments\": {\"prompt\": \"beautiful sunset over mountains\"}}\n<|im_end|>", description: "Tool calling within chat format", category: "tool" } }; // Validation rules for different token types const VALIDATION_RULES = { box_coordinates: { pattern: /^(-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)$/, validator: (coords) => { const [x1, y1, x2, y2] = coords.split(',').map(n => parseFloat(n.trim())); return { valid: !isNaN(x1) && !isNaN(y1) && !isNaN(x2) && !isNaN(y2) && x2 > x1 && y2 > y1, message: "Box coordinates should be: x1,y1,x2,y2 where x2>x1 and y2>y1" }; } }, quad_coordinates: { pattern: /^(\d+,\d+\s+){3}\d+,\d+$/, validator: (coords) => { const pairs = coords.trim().split(/\s+/); const valid = pairs.length >= 3 && pairs.every(pair => /^\d+,\d+$/.test(pair)); return { valid: valid, message: "Quad coordinates should be space-separated x,y pairs: 'x1,y1 x2,y2 x3,y3 x4,y4'" }; } } }; class QwenTestingInterface { constructor() { this.currentTest = null; this.testResults = []; this.customTests = []; } createTestingDialog() { const dialog = $el("div", { parent: document.body, style: { position: "fixed", top: "10%", left: "10%", width: "80%", height: "80%", background: "rgba(20, 20, 20, 0.95)", border: "1px solid rgba(255, 255, 255, 0.2)", borderRadius: "8px", zIndex: 10000, overflow: "hidden", display: "flex", flexDirection: "column", backdropFilter: "blur(10px)", boxShadow: "0 4px 20px rgba(0,0,0,0.5)", fontFamily: "system-ui, -apple-system, sans-serif", color: "rgba(255, 255, 255, 0.9)" } }); dialog.innerHTML = `

Qwen Token Testing Interface

Test Templates

Vision
Chat
Spatial
Code
Tool
Custom Test

Select a test template

Test Results

`; this.setupEventListeners(dialog); this.populateTestTemplates(dialog); return dialog; } setupEventListeners(dialog) { // Close dialog dialog.querySelector('#closeDialog').onclick = () => { document.body.removeChild(dialog); }; // Export results dialog.querySelector('#exportResults').onclick = () => { this.exportTestResults(); }; // Validate test dialog.querySelector('#validateTest').onclick = () => { const input = dialog.querySelector('#testInput').value; this.validateTest(input, dialog); }; // Run test dialog.querySelector('#runTest').onclick = () => { const input = dialog.querySelector('#testInput').value; this.runTest(input, dialog); }; // Create custom test dialog.querySelector('#createCustomTest').onclick = () => { this.createCustomTest(dialog); }; } populateTestTemplates(dialog) { const categories = { vision: dialog.querySelector('#visionTests'), chat: dialog.querySelector('#chatTests'), spatial: dialog.querySelector('#spatialTests'), code: dialog.querySelector('#codeTests'), tool: dialog.querySelector('#toolTests') }; Object.entries(TEST_TEMPLATES).forEach(([key, test]) => { const button = $el("button", { style: { width: "100%", padding: "8px 12px", marginBottom: "5px", background: "rgba(255, 255, 255, 0.05)", border: "1px solid rgba(255, 255, 255, 0.2)", borderRadius: "4px", cursor: "pointer", textAlign: "left", fontSize: "12px", color: "rgba(255, 255, 255, 0.9)" }, textContent: test.name }); button.onclick = () => { this.loadTest(test, dialog); }; if (categories[test.category]) { categories[test.category].appendChild(button); } }); } loadTest(test, dialog) { this.currentTest = test; dialog.querySelector('#currentTestName').textContent = test.name; dialog.querySelector('#testDescription').textContent = test.description; dialog.querySelector('#testInput').value = test.template; } validateTest(input, dialog) { const results = { timestamp: new Date().toISOString(), input: input, type: 'validation', results: [] }; // Check for token sequences const sequences = this.findTokenSequences(input); results.results.push({ type: 'sequences', count: sequences.length, details: sequences }); // Validate coordinates in spatial tokens const coordinateErrors = this.validateCoordinates(input); if (coordinateErrors.length > 0) { results.results.push({ type: 'coordinate_errors', count: coordinateErrors.length, details: coordinateErrors }); } // Check for unmatched tokens const unmatchedTokens = this.findUnmatchedTokens(input); if (unmatchedTokens.length > 0) { results.results.push({ type: 'unmatched_tokens', count: unmatchedTokens.length, details: unmatchedTokens }); } this.displayResults(results, dialog); this.testResults.push(results); } runTest(input, dialog) { const results = { timestamp: new Date().toISOString(), input: input, type: 'execution', testName: this.currentTest?.name || 'Custom Test', results: [] }; // Simulate token analysis (in real implementation, this would call the actual tokenizer) const analysis = this.analyzeTokens(input); results.results = analysis; this.displayResults(results, dialog); this.testResults.push(results); } findTokenSequences(text) { const sequences = []; // Vision sequences const visionRegex = /<\|vision_start\|>(.*?)<\|vision_end\|>/g; let match; while ((match = visionRegex.exec(text)) !== null) { sequences.push({ type: 'vision', content: match[0], inner: match[1], position: match.index }); } // Spatial sequences const boxRegex = /<\|box_start\|>(.*?)<\|box_end\|>/g; while ((match = boxRegex.exec(text)) !== null) { sequences.push({ type: 'bounding_box', content: match[0], coordinates: match[1], position: match.index }); } // Object references const objRegex = /<\|object_ref_start\|>(.*?)<\|object_ref_end\|>/g; while ((match = objRegex.exec(text)) !== null) { sequences.push({ type: 'object_reference', content: match[0], object: match[1], position: match.index }); } return sequences; } validateCoordinates(text) { const errors = []; // Validate box coordinates const boxRegex = /<\|box_start\|>(.*?)<\|box_end\|>/g; let match; while ((match = boxRegex.exec(text)) !== null) { const validation = VALIDATION_RULES.box_coordinates.validator(match[1]); if (!validation.valid) { errors.push({ type: 'box_coordinates', content: match[0], error: validation.message, position: match.index }); } } // Validate quad coordinates const quadRegex = /<\|quad_start\|>(.*?)<\|quad_end\|>/g; while ((match = quadRegex.exec(text)) !== null) { const validation = VALIDATION_RULES.quad_coordinates.validator(match[1]); if (!validation.valid) { errors.push({ type: 'quad_coordinates', content: match[0], error: validation.message, position: match.index }); } } return errors; } findUnmatchedTokens(text) { const unmatched = []; const tokens = [ '<|vision_start|>', '<|vision_end|>', '<|box_start|>', '<|box_end|>', '<|quad_start|>', '<|quad_end|>', '<|object_ref_start|>', '<|object_ref_end|>', '<|im_start|>', '<|im_end|>' ]; const pairs = [ ['<|vision_start|>', '<|vision_end|>'], ['<|box_start|>', '<|box_end|>'], ['<|quad_start|>', '<|quad_end|>'], ['<|object_ref_start|>', '<|object_ref_end|>'], ['<|im_start|>', '<|im_end|>'] ]; pairs.forEach(([start, end]) => { const startCount = (text.match(new RegExp(this.escapeRegex(start), 'g')) || []).length; const endCount = (text.match(new RegExp(this.escapeRegex(end), 'g')) || []).length; if (startCount !== endCount) { unmatched.push({ startToken: start, endToken: end, startCount: startCount, endCount: endCount }); } }); return unmatched; } analyzeTokens(text) { // Simplified token analysis (would use actual tokenizer in production) const specialTokens = [ '<|vision_start|>', '<|vision_end|>', '<|image_pad|>', '<|video_pad|>', '<|vision_pad|>', '<|object_ref_start|>', '<|object_ref_end|>', '<|box_start|>', '<|box_end|>', '<|quad_start|>', '<|quad_end|>', '<|im_start|>', '<|im_end|>', '<|endoftext|>', '<|fim_prefix|>', '<|fim_middle|>', '<|fim_suffix|>', '<|fim_pad|>', '<|repo_name|>', '<|file_sep|>', '', '' ]; const analysis = { totalLength: text.length, estimatedTokens: text.split(/\s+/).length, specialTokensFound: [], sequences: this.findTokenSequences(text), efficiency: 0 }; specialTokens.forEach(token => { const count = (text.match(new RegExp(this.escapeRegex(token), 'g')) || []).length; if (count > 0) { analysis.specialTokensFound.push({ token: token, count: count }); } }); analysis.efficiency = analysis.specialTokensFound.length / analysis.estimatedTokens; return [ { type: 'token_analysis', details: analysis } ]; } displayResults(results, dialog) { const resultsDiv = dialog.querySelector('#testResults'); const resultElement = $el("div", { style: { marginBottom: "15px", padding: "12px", background: "rgba(255, 255, 255, 0.05)", border: "1px solid rgba(255, 255, 255, 0.1)", borderRadius: "4px" } }); const timestamp = new Date(results.timestamp).toLocaleTimeString(); const typeColor = results.type === 'validation' ? '#ffc107' : '#007bff'; resultElement.innerHTML = `
${results.testName || 'Test'} ${results.type}
${timestamp}
${results.results.map(result => this.formatResult(result)).join('
')}
`; resultsDiv.appendChild(resultElement); resultsDiv.scrollTop = resultsDiv.scrollHeight; } formatResult(result) { switch (result.type) { case 'sequences': return `Sequences: ${result.count} found (${result.details.map(s => s.type).join(', ')})`; case 'coordinate_errors': return `Coordinate Errors: ${result.count} found`; case 'unmatched_tokens': return `Unmatched Tokens: ${result.count} pairs unmatched`; case 'token_analysis': const analysis = result.details; return `Analysis: ~${analysis.estimatedTokens} tokens, ${analysis.specialTokensFound.length} special tokens, ${(analysis.efficiency * 100).toFixed(1)}% efficiency`; default: return `${result.type}: ${JSON.stringify(result.details)}`; } } createCustomTest(dialog) { const customTestDialog = $el("div", { parent: document.body, style: { position: "fixed", top: "50%", left: "50%", transform: "translate(-50%, -50%)", width: "500px", background: "rgba(20, 20, 20, 0.95)", border: "1px solid rgba(255, 255, 255, 0.2)", borderRadius: "8px", padding: "20px", zIndex: 10001, backdropFilter: "blur(10px)", boxShadow: "0 4px 12px rgba(0,0,0,0.5)", fontFamily: "system-ui, -apple-system, sans-serif", color: "rgba(255, 255, 255, 0.9)" } }); customTestDialog.innerHTML = `

Create Custom Test

`; customTestDialog.querySelector('#saveCustomTest').onclick = () => { const name = customTestDialog.querySelector('#customTestName').value; const description = customTestDialog.querySelector('#customTestDesc').value; const template = customTestDialog.querySelector('#customTestTemplate').value; if (name && template) { const customTest = { name: name, description: description, template: template, category: 'custom' }; this.customTests.push(customTest); this.loadTest(customTest, dialog); document.body.removeChild(customTestDialog); } else { alert('Please provide at least a name and template.'); } }; customTestDialog.querySelector('#cancelCustomTest').onclick = () => { document.body.removeChild(customTestDialog); }; } exportTestResults() { const exportData = { timestamp: new Date().toISOString(), totalTests: this.testResults.length, customTests: this.customTests, results: this.testResults }; const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `qwen_test_results_${new Date().toISOString().slice(0, 19)}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } escapeRegex(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } } // Add testing interface to Qwen nodes app.registerExtension({ name: "Comfy.QwenTestingInterface", async beforeRegisterNodeDef(nodeType, nodeData, app) { if (nodeData.name === "QwenVLTextEncoder" || nodeData.name === "QwenTokenDebugger") { const onNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = function() { const ret = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; const node = this; // Add testing interface button setTimeout(() => { const testBtn = node.addWidget("button", "Open Testing Interface", "test", () => { const testInterface = new QwenTestingInterface(); testInterface.createTestingDialog(); }); }, 10); return ret; }; } } });