reach-vb/github-issue-generator-webgpu
56
1import { prebuiltAppConfig, CreateMLCEngine } from "@mlc-ai/web-llm";2import hljs from "highlight.js";3import ace from "ace-builds";4 5// Required for ace to resolve the module correctly6require("ace-builds/src-noconflict/mode-javascript");7require("ace-builds/webpack-resolver");8 9let engine = null;10let useCustomGrammar = false;11 12document.addEventListener("DOMContentLoaded", () => {13 const modelSelection = document.getElementById("model-selection");14 const promptTextarea = document.getElementById("prompt");15 const outputDiv = document.getElementById("output");16 const statsParagraph = document.getElementById("stats");17 18 // Populate model selection dropdown19 const availableModels = prebuiltAppConfig.model_list20 .filter(21 (m) =>22 m.model_id.startsWith("SmolLM2")23 )24 .map((m) => m.model_id);25 26 let selectedModel = availableModels[0];27 28 availableModels.forEach((modelId) => {29 const option = document.createElement("option");30 option.value = modelId;31 option.textContent = modelId;32 modelSelection.appendChild(option);33 });34 35 modelSelection.value = selectedModel;36 37 modelSelection.onchange = (e) => {38 selectedModel = e.target.value;39 engine = null; // Reset the engine when the model changes40 };41 42 // Setup JSON Schema Editor43 const jsonSchemaEditor = ace.edit("schema", {44 mode: "ace/mode/javascript",45 theme: "ace/theme/github",46 wrap: true,47 });48 49 // Set default schema50 jsonSchemaEditor.setValue(`{51 "title": "GitHubIssue",52 "type": "object",53 "properties": {54 "title": {55 "type": "string",56 "description": "The title of the issue"57 },58 "description": {59 "type": "string",60 "description": "Detailed description of the issue"61 },62 "labels": {63 "type": "array",64 "items": {65 "type": "string",66 "enum": ["bug", "enhancement", "documentation", "high-priority", "security"]67 },68 "description": "Labels to categorize the issue"69 },70 "priority": {71 "type": "string",72 "enum": ["low", "medium", "high", "critical"],73 "description": "Priority level of the issue"74 },75 "assignees": {76 "type": "array",77 "items": {78 "type": "string"79 },80 "maxItems": 3,81 "description": "GitHub usernames of assigned users (max 3)"82 },83 "estimated_time": {84 "type": "string",85 "pattern": "^[0-9]+[hdwm]$",86 "description": "Estimated time to resolve (e.g., '2h', '3d', '1w', '1m')"87 }88 },89 "required": [90 "title",91 "description",92 "priority",93 "labels"94 ]95 }`);96 97 // Set default prompt98 promptTextarea.value = `Create a detailed issue for the following problem:99 100The login page is not properly handling password reset requests. Users report that they are not receiving the password reset emails, and when they do receive them, the reset links are sometimes expired. This is affecting about 20% of our users and needs immediate attention.101 102Please include appropriate labels, priority level, and time estimation.`;103 104 // Generate button click handler105 document.getElementById("generate").onclick = async () => {106 try {107 outputDiv.innerHTML = '<div class="loading">Initializing model...</div>';108 109 if (!engine) {110 engine = await CreateMLCEngine(selectedModel, {111 initProgressCallback: (progress) => {112 console.log(progress);113 outputDiv.innerHTML = `<div class="loading">${progress.text}</div>`;114 },115 });116 }117 118 const schemaInput = jsonSchemaEditor.getValue();119 const schema = JSON.stringify(JSON.parse(schemaInput)); // Validate JSON120 const response_format = { type: "json_object", schema };121 122 const request = {123 stream: true,124 stream_options: { include_usage: true },125 messages: [{ role: "user", content: promptTextarea.value }],126 max_tokens: 512,127 response_format,128 };129 130 let curMessage = "";131 let usage = null;132 outputDiv.innerHTML = '<div class="loading">Generating response...</div>';133 134 const generator = await engine.chatCompletion(request);135 136 for await (const chunk of generator) {137 const curDelta = chunk.choices[0]?.delta.content;138 if (curDelta) curMessage += curDelta;139 if (chunk.usage) {140 console.log(chunk.usage);141 usage = chunk.usage;142 }143 outputDiv.innerHTML = `<pre><code class="language-json">${curMessage}</code></pre>`;144 }145 146 const finalMessage = await engine.getMessage();147 outputDiv.innerHTML = hljs.highlight(finalMessage, {148 language: "json",149 }).value;150 151 try {152 const issueData = JSON.parse(finalMessage);153 updateIssuePreview(issueData);154 } catch (error) {155 console.error('Failed to parse issue data:', error);156 }157 158 if (usage) {159 const statsTextParts = [];160 if (usage.extra.prefill_tokens_per_s) {161 statsTextParts.push(`Prefill Speed: ${usage.extra.prefill_tokens_per_s.toFixed(1)} tok/s`);162 }163 if (usage.extra.decode_tokens_per_s) {164 statsTextParts.push(`Decode Speed: ${usage.extra.decode_tokens_per_s.toFixed(1)} tok/s`);165 }166 statsParagraph.textContent = statsTextParts.join(" | ");167 statsParagraph.classList.remove("hidden");168 }169 } catch (error) {170 console.error("Generation error:", error);171 outputDiv.innerHTML = `<div class="error">Error: ${error.message}</div>`;172 }173 };174});175 176function updateIssuePreview(issueData) {177 const preview = document.getElementById('issue-preview');178 179 // Update title180 document.getElementById('preview-title').textContent = issueData.title;181 182 // Update priority badge183 const priorityBadge = document.getElementById('preview-priority');184 priorityBadge.textContent = issueData.priority.toUpperCase();185 priorityBadge.className = `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium priority-${issueData.priority}`;186 187 // Update estimated time188 document.getElementById('preview-time-text').textContent = issueData.estimated_time;189 190 // Update labels191 const labelsContainer = document.getElementById('preview-labels');192 labelsContainer.innerHTML = '';193 issueData.labels.forEach(label => {194 const labelEl = document.createElement('span');195 labelEl.className = `issue-label label-${label}`;196 labelEl.textContent = label;197 labelsContainer.appendChild(labelEl);198 });199 200 // Update description201 document.getElementById('preview-description').textContent = issueData.description;202 203 // Update assignees204 const assigneesContainer = document.getElementById('preview-assignees');205 assigneesContainer.innerHTML = '';206 if (issueData.assignees && issueData.assignees.length > 0) {207 issueData.assignees.forEach(assignee => {208 const avatarEl = document.createElement('div');209 avatarEl.className = 'assignee-avatar';210 avatarEl.textContent = assignee.charAt(0).toUpperCase();211 assigneesContainer.appendChild(avatarEl);212 });213 } else {214 assigneesContainer.innerHTML = '<span class="text-sm text-gray-500">No assignees</span>';215 }216 217 // Show the preview with animation218 preview.classList.remove('hidden');219 // Force a reflow220 preview.offsetHeight;221 preview.classList.add('visible');222}223 