copilot-tracer 1.0.2 → 1.0.4
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.
- package/README.md +42 -2
- package/dist/otlpReceiver.js +3 -4
- package/dist/webServer.js +47 -0
- package/package.json +2 -2
- package/web/index.html +148 -2
- package/web/index.html.patch +0 -0
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# copilot-tracer
|
|
2
2
|
|
|
3
|
-
Real-time tracing and
|
|
3
|
+
Real-time tracing and prompt-refinement tool for **GitHub Copilot CLI** and **VS Code Copilot extension**.
|
|
4
4
|
|
|
5
|
-
Captures every prompt, response, token usage, AI credits, tool calls, skill invocations and duration — all in one place. Works via native **OpenTelemetry (OTLP)** integration built into GitHub Copilot
|
|
5
|
+
Captures every prompt, response, token usage, AI credits, tool calls, skill invocations, and duration — all in one place. It also includes a prompt optimizer that rewrites prompts using role grounding, imperative clarity, output formatting, constraint injection, and noise removal. Works via native **OpenTelemetry (OTLP)** integration built into GitHub Copilot, with an optional ACP proxy mode for live CLI tracing.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -10,11 +10,13 @@ Captures every prompt, response, token usage, AI credits, tool calls, skill invo
|
|
|
10
10
|
|
|
11
11
|
- **Zero-intrusion capture** — uses Copilot's built-in OTel support. Set 2 env vars, done.
|
|
12
12
|
- **Works everywhere** — captures both Copilot CLI (`copilot -p "..."`) and VS Code Copilot Chat
|
|
13
|
+
- **Prompt refinement** — rewrites prompts with stronger instructions, clearer outputs, and less noise
|
|
13
14
|
- **Real-time web UI** — live dashboard at `http://localhost:4747` with dark theme
|
|
14
15
|
- **Full prompt & response** — see exactly what you sent and what Copilot replied
|
|
15
16
|
- **Token breakdown** — input, output, cached, reasoning, written tokens per request
|
|
16
17
|
- **AI Credits tracking** — matches exactly what Copilot terminal reports (e.g. `2.59 cr`)
|
|
17
18
|
- **Tool call visibility** — see every tool/skill/MCP invoked during a session
|
|
19
|
+
- **ACP proxy mode** — wrap the Copilot CLI to trace live JSON-RPC traffic when needed
|
|
18
20
|
- **Persistent storage** — SQLite at `~/.copilot-tracer/traces.db`, survives restarts
|
|
19
21
|
- **Console + Web UI** — CLI table view or browser dashboard, your choice
|
|
20
22
|
|
|
@@ -175,6 +177,44 @@ Live updating table in terminal. Same columns as web UI. TOTALS row pinned at to
|
|
|
175
177
|
|
|
176
178
|
---
|
|
177
179
|
|
|
180
|
+
## Prompt Refinement
|
|
181
|
+
|
|
182
|
+
The web UI exposes `POST /api/refine` to improve a raw prompt before sending it to Copilot.
|
|
183
|
+
|
|
184
|
+
It currently applies these techniques:
|
|
185
|
+
|
|
186
|
+
- **Role grounding** — adds an expert persona when the prompt has none
|
|
187
|
+
- **Imperative clarity** — turns soft phrasing into direct instructions
|
|
188
|
+
- **Output format** — asks for JSON, markdown, bullets, or steps when missing
|
|
189
|
+
- **Reasoning guidance** — adds step-by-step thinking for multi-step tasks
|
|
190
|
+
- **Noise removal** — strips filler like “please”, “could you”, and “I think”
|
|
191
|
+
- **Constraint injection** — adds language, tone, length, and audience constraints
|
|
192
|
+
- **Redundancy cleanup** — removes repeated or conflicting instructions
|
|
193
|
+
|
|
194
|
+
Example request:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
curl -s http://localhost:4747/api/refine \
|
|
198
|
+
-H 'content-type: application/json' \
|
|
199
|
+
-d '{"prompt":"help me write a plan for migrating a monolith"}'
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Response shape:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"ok": true,
|
|
207
|
+
"optimized": "…",
|
|
208
|
+
"issues": [],
|
|
209
|
+
"techniques": [],
|
|
210
|
+
"origTok": 10,
|
|
211
|
+
"newTok": 22,
|
|
212
|
+
"inputSavingPct": 0
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
178
218
|
## CLI Flags
|
|
179
219
|
|
|
180
220
|
| Flag | Description |
|
package/dist/otlpReceiver.js
CHANGED
|
@@ -108,15 +108,14 @@ function processSpans(spans, sessionId) {
|
|
|
108
108
|
return text.trim();
|
|
109
109
|
})
|
|
110
110
|
.join(' ')
|
|
111
|
-
.trim()
|
|
112
|
-
.slice(0, 300);
|
|
111
|
+
.trim();
|
|
113
112
|
}
|
|
114
113
|
else {
|
|
115
|
-
promptText = (userMsg?.content ?? String(raw)).
|
|
114
|
+
promptText = (userMsg?.content ?? String(raw)).trim();
|
|
116
115
|
}
|
|
117
116
|
}
|
|
118
117
|
catch {
|
|
119
|
-
promptText = String(raw).
|
|
118
|
+
promptText = String(raw).trim();
|
|
120
119
|
}
|
|
121
120
|
}
|
|
122
121
|
}
|
package/dist/webServer.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createServer } from 'http';
|
|
|
3
3
|
import { Server } from 'socket.io';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
6
7
|
import { getTraces, getTrace, getSessionSummary } from './db.js';
|
|
7
8
|
import { traceEvents } from './proxy.js';
|
|
8
9
|
import { registerOtlpRoutes } from './otlpReceiver.js';
|
|
@@ -22,6 +23,52 @@ export function startWebServer(port = 4747, sessionId) {
|
|
|
22
23
|
const traces = getTraces(sid, 200);
|
|
23
24
|
res.json(traces);
|
|
24
25
|
});
|
|
26
|
+
app.post('/api/refine', async (req, res) => {
|
|
27
|
+
const { prompt } = req.body;
|
|
28
|
+
if (!prompt?.trim()) {
|
|
29
|
+
res.status(400).json({ error: 'prompt required' });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const origTok = Math.ceil(prompt.trim().length / 4);
|
|
33
|
+
// Meta-prompt grounded in prompt engineering best practices (promptingguide.ai)
|
|
34
|
+
const metaPrompt = `You are a world-class prompt engineering expert. Rewrite the user prompt below using these techniques where applicable:
|
|
35
|
+
1. Role grounding — prepend "You are a <expert role>" if no persona is set
|
|
36
|
+
2. Imperative clarity — replace indirect/hedging phrases with direct imperatives (Explain / List / Generate / Analyze)
|
|
37
|
+
3. Output format — specify format (JSON, markdown, numbered steps, bullet list) when missing
|
|
38
|
+
4. Chain-of-thought — add "Think step by step." for multi-step reasoning or debugging tasks
|
|
39
|
+
5. Remove noise — remove filler (please, could you, I want you to, thank you, if you don't mind, maybe, I think, kind of, sort of)
|
|
40
|
+
6. Add constraints — specify language, length, audience, tone if not present
|
|
41
|
+
7. Redundancy — collapse repeated or contradictory instructions
|
|
42
|
+
|
|
43
|
+
Respond ONLY with a valid JSON object (no markdown, no code fences):
|
|
44
|
+
{"optimized":"<rewritten prompt>","issues":[{"type":"warn","msg":"<what was wrong>"}],"techniques":["<applied>"]}
|
|
45
|
+
|
|
46
|
+
Prompt to optimize:
|
|
47
|
+
${prompt.trim()}`;
|
|
48
|
+
try {
|
|
49
|
+
const raw = execSync(`copilot -p ${JSON.stringify(metaPrompt)} --model claude-sonnet-4.6`, { timeout: 45000, encoding: 'utf8', maxBuffer: 2 * 1024 * 1024 });
|
|
50
|
+
// Strip copilot CLI chrome (trailing "Changes +0 -0" line, ANSI codes)
|
|
51
|
+
const cleaned = raw
|
|
52
|
+
.replace(/\x1b\[[0-9;]*m/g, '') // ANSI
|
|
53
|
+
.replace(/\r/g, '')
|
|
54
|
+
.split('\n')
|
|
55
|
+
.filter(l => !/^Changes\s+\+\d/.test(l.trim()))
|
|
56
|
+
.join('\n')
|
|
57
|
+
.trim();
|
|
58
|
+
// Extract JSON — copilot may wrap with prose
|
|
59
|
+
const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
|
|
60
|
+
if (!jsonMatch)
|
|
61
|
+
throw new Error('No JSON in response: ' + cleaned.slice(0, 200));
|
|
62
|
+
const result = JSON.parse(jsonMatch[0]);
|
|
63
|
+
const newTok = Math.ceil((result.optimized ?? '').length / 4);
|
|
64
|
+
const inputSavingPct = origTok > 0 ? Math.max(0, Math.round((1 - newTok / origTok) * 100)) : 0;
|
|
65
|
+
res.json({ ok: true, ...result, origTok, newTok, inputSavingPct });
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
69
|
+
res.status(500).json({ ok: false, error: msg });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
25
72
|
app.get('/api/traces/:id', (req, res) => {
|
|
26
73
|
const trace = getTrace(req.params.id);
|
|
27
74
|
if (!trace)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "copilot-tracer",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Real-time
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "Real-time tracing and prompt-refinement companion for GitHub Copilot CLI and VS Code Copilot — tracks tokens, AI credits, tool calls, and refined prompts with console and web UI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"copilot",
|
|
7
7
|
"github-copilot",
|
package/web/index.html
CHANGED
|
@@ -5,6 +5,126 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
6
6
|
<title>Copilot Tracer</title>
|
|
7
7
|
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
|
|
8
|
+
<script type="module">
|
|
9
|
+
import * as webllm from 'https://esm.run/@mlc-ai/web-llm';
|
|
10
|
+
|
|
11
|
+
// ─── REFINE ENGINE — Phase 1: instant heuristics · Phase 2: webLLM ───────
|
|
12
|
+
let refineOpen = false;
|
|
13
|
+
let engine = null;
|
|
14
|
+
|
|
15
|
+
const tok = t => Math.ceil((t || '').length / 4);
|
|
16
|
+
|
|
17
|
+
// Phase 1 — instant local analysis
|
|
18
|
+
function analyzePrompt(t) {
|
|
19
|
+
const issues = [], suggestions = [];
|
|
20
|
+
if ([/\bplease\b/,/\bcould you\b/,/\bcan you\b/,/\bI want you to\b/,/\bkindly\b/,/\bthank you\b/,/\bif you don.t mind\b/].some(r => r.test(t)))
|
|
21
|
+
issues.push({ type:'warn', msg:'Filler / polite noise (please, can you, thank you…)' });
|
|
22
|
+
if ([/\bI think\b/,/\bmaybe\b/,/\bkind of\b/,/\bsort of\b/].some(r => r.test(t)))
|
|
23
|
+
issues.push({ type:'warn', msg:'Hedging language reduces precision (I think, maybe, kind of…)' });
|
|
24
|
+
if (/\bhelp me with\b|\bhelp me understand\b/i.test(t))
|
|
25
|
+
issues.push({ type:'warn', msg:'Indirect phrasing — use an imperative verb: Explain / List / Generate / Analyze' });
|
|
26
|
+
if (/\bmake it (?:good|nice|better|great)\b/i.test(t))
|
|
27
|
+
issues.push({ type:'warn', msg:'Vague quality instruction — specify criteria (concise, bullet-list, ≤200 words…)' });
|
|
28
|
+
const hasFormat = /\bjson\b|\bmarkdown\b|\bbullet\b|\bnumbered\b|\bstep.by.step\b/i.test(t);
|
|
29
|
+
if (!hasFormat && t.length > 60) issues.push({ type:'info', msg:'No output format — add: "Respond in bullet points / JSON / numbered steps"' });
|
|
30
|
+
const hasRole = /\byou are\b|\bact as\b|\bexpert\b|\bsenior\b/i.test(t);
|
|
31
|
+
if (!hasRole && t.length > 80) issues.push({ type:'info', msg:'No role/persona — prepend "You are a <expert>." for better quality' });
|
|
32
|
+
if (!/\bstep by step\b|\bthink\b/i.test(t) && /\bdebug\b|\banalyze\b|\bcompare\b|\bdesign\b/i.test(t))
|
|
33
|
+
issues.push({ type:'info', msg:'Complex task — add "Think step by step." for chain-of-thought reasoning' });
|
|
34
|
+
if (!issues.length) issues.push({ type:'info', msg:'No obvious issues — prompt looks well-structured' });
|
|
35
|
+
if (!hasRole && t.length > 80) suggestions.push('Add a role: "You are a senior software engineer."');
|
|
36
|
+
if (!hasFormat && t.length > 60) suggestions.push('Specify output: "Respond in numbered steps."');
|
|
37
|
+
return { issues, suggestions };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function escHtml(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
|
41
|
+
|
|
42
|
+
window.toggleRefine = async function(prompt) {
|
|
43
|
+
const panel = document.getElementById('refine-panel');
|
|
44
|
+
if (!panel) return;
|
|
45
|
+
if (refineOpen) { panel.classList.remove('open'); refineOpen = false; return; }
|
|
46
|
+
refineOpen = true;
|
|
47
|
+
panel.classList.add('open');
|
|
48
|
+
|
|
49
|
+
const origTok = tok(prompt);
|
|
50
|
+
const { issues, suggestions } = analyzePrompt(prompt);
|
|
51
|
+
|
|
52
|
+
const issueHtml = issues.map(i => `<div class="refine-issue ${i.type}">⚠ ${escHtml(i.msg)}</div>`).join('');
|
|
53
|
+
const suggHtml = suggestions.length
|
|
54
|
+
? `<div class=refine-section-title>Quick Suggestions</div><div class=refine-suggestions>${suggestions.map(s=>`<div class=refine-suggestion>💡 ${escHtml(s)}</div>`).join('')}</div>`
|
|
55
|
+
: '';
|
|
56
|
+
|
|
57
|
+
panel.innerHTML = `
|
|
58
|
+
<div class=refine-section-title>Issues Found</div>
|
|
59
|
+
<div class=refine-issues>${issueHtml}</div>
|
|
60
|
+
${suggHtml}
|
|
61
|
+
<div class=refine-section-title>Optimized Prompt</div>
|
|
62
|
+
<div id=refine-optimizing style="color:#8b949e;font-size:12px;padding:6px 0">
|
|
63
|
+
<span class=refine-spinner></span> Optimizing with WebLLM…
|
|
64
|
+
<div id=refine-progress style="margin-top:5px;font-size:10px;color:#58a6ff"></div>
|
|
65
|
+
</div>
|
|
66
|
+
<div class=refine-optimized id=refined-text style="display:none"></div>
|
|
67
|
+
<div id=refine-actions style="display:none">
|
|
68
|
+
<button class="refine-copy-btn" onclick="copyRefined()">📋 Copy optimized prompt</button>
|
|
69
|
+
<div class=refine-stats id=refine-stats></div>
|
|
70
|
+
</div>`;
|
|
71
|
+
|
|
72
|
+
// Phase 2 — webLLM
|
|
73
|
+
const progressEl = () => document.getElementById('refine-progress');
|
|
74
|
+
const optimizingEl = document.getElementById('refine-optimizing');
|
|
75
|
+
const refinedEl = document.getElementById('refined-text');
|
|
76
|
+
const actionsEl = document.getElementById('refine-actions');
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
if (!engine) {
|
|
80
|
+
progressEl() && (progressEl().textContent = 'Loading model… (first run ~650 MB, cached after)');
|
|
81
|
+
engine = await webllm.CreateMLCEngine('Llama-3.2-1B-Instruct-q4f16_1-MLC', {
|
|
82
|
+
initProgressCallback: p => { const el = progressEl(); if (el) el.textContent = p.text || ''; }
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (progressEl()) progressEl().textContent = 'Generating…';
|
|
86
|
+
|
|
87
|
+
const sys = `You are a prompt engineering expert. Rewrite the user prompt by:
|
|
88
|
+
1. Adding a role ("You are a <expert>.") if missing
|
|
89
|
+
2. Replacing indirect/hedging phrases with direct imperatives (Explain / List / Generate)
|
|
90
|
+
3. Adding output format (numbered steps / bullet list / JSON) if missing
|
|
91
|
+
4. Adding "Think step by step." for complex tasks (debug, analyze, compare)
|
|
92
|
+
5. Removing filler words (please, can you, thank you, maybe, I think)
|
|
93
|
+
6. Adding constraints (language, length, audience) if missing
|
|
94
|
+
Reply with ONLY the rewritten prompt. No explanation, no preamble.`;
|
|
95
|
+
|
|
96
|
+
const reply = await engine.chat.completions.create({
|
|
97
|
+
messages: [{ role:'system', content:sys }, { role:'user', content:prompt }],
|
|
98
|
+
temperature: 0.3,
|
|
99
|
+
max_tokens: 512,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const optimized = (reply.choices[0]?.message?.content || '').trim();
|
|
103
|
+
const newTok = tok(optimized);
|
|
104
|
+
const pct = origTok > 0 ? Math.max(0, Math.round((1 - newTok / origTok) * 100)) : 0;
|
|
105
|
+
const col = pct >= 20 ? '#7ee787' : pct >= 5 ? '#d29922' : '#8b949e';
|
|
106
|
+
|
|
107
|
+
optimizingEl.style.display = 'none';
|
|
108
|
+
refinedEl.textContent = optimized;
|
|
109
|
+
refinedEl.style.display = 'block';
|
|
110
|
+
actionsEl.style.display = 'block';
|
|
111
|
+
document.getElementById('refine-stats').innerHTML = `
|
|
112
|
+
<div class=refine-stat><div class=rv style=color:${col}>${pct > 0 ? '-'+pct+'%' : '0%'}</div><div class=rl>Input Saving</div></div>
|
|
113
|
+
<div class=refine-stat><div class=rv style=color:#8b949e>${origTok} → ${newTok}</div><div class=rl>Approx Tokens</div></div>`;
|
|
114
|
+
} catch(e) {
|
|
115
|
+
optimizingEl.innerHTML = `<span style="color:#f85149">⚠ WebLLM error: ${escHtml(e.message)}</span>`;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
window.copyRefined = function() {
|
|
120
|
+
const el = document.getElementById('refined-text');
|
|
121
|
+
if (!el) return;
|
|
122
|
+
navigator.clipboard.writeText(el.innerText).then(() => {
|
|
123
|
+
const btn = document.querySelector('.refine-copy-btn');
|
|
124
|
+
if (btn) { btn.textContent = '✅ Copied!'; setTimeout(() => btn.textContent = '📋 Copy optimized prompt', 1500); }
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
</script>
|
|
8
128
|
<style>
|
|
9
129
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
10
130
|
body { background: #0d1117; color: #c9d1d9; font-family: 'Segoe UI', system-ui, monospace; font-size: 13px; }
|
|
@@ -68,7 +188,7 @@
|
|
|
68
188
|
.detail-box { background: #0d1117; border: 1px solid #30363d; border-radius: 6px; padding: 10px; font-size: 12px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; max-height: 160px; overflow: auto; }
|
|
69
189
|
.detail-box.reasoning { color: #d2a8ff; }
|
|
70
190
|
.detail-box.response { color: #56d364; }
|
|
71
|
-
.detail-box.prompt { color: #e6edf3; }
|
|
191
|
+
.detail-box.prompt { color: #e6edf3; max-height: none; overflow: visible; }
|
|
72
192
|
|
|
73
193
|
/* Call graph */
|
|
74
194
|
.call-tree { font-family: monospace; font-size: 12px; }
|
|
@@ -83,7 +203,30 @@
|
|
|
83
203
|
.call-io .label { color: #58a6ff; margin-right: 4px; }
|
|
84
204
|
.call-io.error { color: #f85149; }
|
|
85
205
|
|
|
86
|
-
/*
|
|
206
|
+
/* Refine panel */
|
|
207
|
+
.refine-btn { background: #1f2d1f; border: 1px solid #238636; color: #7ee787; border-radius: 6px; padding: 5px 12px; font-size: 11px; cursor: pointer; margin-top: 8px; display: inline-flex; align-items: center; gap: 6px; transition: all .15s; }
|
|
208
|
+
.refine-btn:hover { background: #238636; color: #fff; }
|
|
209
|
+
.refine-panel { margin-top: 12px; background: #0d1117; border: 1px solid #238636; border-radius: 8px; padding: 14px; display: none; }
|
|
210
|
+
.refine-panel.open { display: block; }
|
|
211
|
+
.refine-section-title { font-size: 10px; text-transform: uppercase; letter-spacing: .6px; color: #8b949e; margin-bottom: 6px; margin-top: 12px; }
|
|
212
|
+
.refine-section-title:first-child { margin-top: 0; }
|
|
213
|
+
.refine-issues { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
|
|
214
|
+
.refine-issue { font-size: 11px; padding: 4px 8px; border-radius: 4px; background: #21262d; border-left: 3px solid; }
|
|
215
|
+
.refine-issue.warn { border-color: #d29922; color: #d29922; }
|
|
216
|
+
.refine-issue.info { border-color: #58a6ff; color: #8b949e; }
|
|
217
|
+
.refine-optimized { background: #161b22; border: 1px solid #30363d; border-radius: 6px; padding: 10px; font-size: 12px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; color: #7ee787; max-height: 260px; overflow: auto; }
|
|
218
|
+
.refine-stats { display: flex; gap: 12px; margin-top: 10px; flex-wrap: wrap; }
|
|
219
|
+
.refine-stat { background: #21262d; border-radius: 6px; padding: 6px 12px; text-align: center; }
|
|
220
|
+
.refine-stat .rv { font-size: 18px; font-weight: 700; }
|
|
221
|
+
.refine-stat .rl { font-size: 10px; color: #8b949e; margin-top: 1px; }
|
|
222
|
+
.refine-copy-btn { margin-top: 10px; background: #21262d; border: 1px solid #30363d; color: #8b949e; border-radius: 6px; padding: 4px 12px; font-size: 11px; cursor: pointer; }
|
|
223
|
+
.refine-copy-btn:hover { color: #e6edf3; border-color: #58a6ff; }
|
|
224
|
+
.refine-techniques { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 8px; }
|
|
225
|
+
.refine-tag { background: #0d2230; border: 1px solid #1f6feb; color: #58a6ff; border-radius: 10px; padding: 2px 9px; font-size: 10px; }
|
|
226
|
+
.refine-suggestions { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
|
|
227
|
+
.refine-suggestion { font-size: 11px; padding: 4px 8px; border-radius: 4px; background: #0d2230; border-left: 3px solid #1f6feb; color: #8b949e; }
|
|
228
|
+
@keyframes refine-spin { to { transform: rotate(360deg); } }
|
|
229
|
+
.refine-spinner { display: inline-block; width: 10px; height: 10px; border: 2px solid #30363d; border-top-color: #58a6ff; border-radius: 50%; animation: refine-spin .7s linear infinite; vertical-align: middle; margin-right: 4px; }
|
|
87
230
|
.modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 100; align-items: center; justify-content: center; }
|
|
88
231
|
.modal-overlay.open { display: flex; }
|
|
89
232
|
.modal { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 20px; width: 560px; max-height: 80vh; overflow: auto; }
|
|
@@ -265,12 +408,15 @@
|
|
|
265
408
|
}
|
|
266
409
|
|
|
267
410
|
function renderDetail(t) {
|
|
411
|
+
refineOpen = false;
|
|
268
412
|
document.getElementById('detail-title').textContent = truncate(t.prompt || 'Trace Detail', 50);
|
|
269
413
|
const body = document.getElementById('detail-body');
|
|
270
414
|
body.innerHTML = `
|
|
271
415
|
<div class="detail-section">
|
|
272
416
|
<h4>Prompt</h4>
|
|
273
417
|
<div class="detail-box prompt">${escHtml(t.prompt||'')}</div>
|
|
418
|
+
<button class="refine-btn" onclick="toggleRefine(${JSON.stringify(t.prompt||'').replace(/</g,'\\u003c').replace(/"/g,'"')})">✨ Refine Prompt</button>
|
|
419
|
+
<div class="refine-panel" id="refine-panel"></div>
|
|
274
420
|
</div>
|
|
275
421
|
${t.reasoning ? `
|
|
276
422
|
<div class="detail-section">
|
|
File without changes
|