feat(insight): add shareable card and dev environment support

Add Twitter/X-optimized share card component for exporting insights as an
image. Include Vite dev setup for local development with hot reload. Increase
DataProcessor timeout and concurrency for better reliability with large
datasets.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
DragonnZhang 2026-02-11 16:16:54 +08:00
parent d61ec772a3
commit 83dc4ca4ec
10 changed files with 1013 additions and 34 deletions

View file

@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Qwen Code Insights — Dev</title>
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
</head>
<body>
<div class="min-h-screen" id="container">
<div class="mx-auto max-w-6xl px-6 py-10 md:py-12">
<div id="react-root"></div>
</div>
</div>
<script type="module" src="/src/dev.tsx"></script>
</body>
</html>

View file

@ -3,6 +3,7 @@
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "node build.mjs"
},
"dependencies": {},

View file

@ -12,6 +12,7 @@ import {
FutureOpportunities,
MemorableMoment,
} from './Qualitative';
import { ShareCard } from './ShareCard';
import './styles.css';
import { InsightData } from './types';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -41,7 +42,10 @@ function InsightApp({ data }: { data: InsightData }) {
return (
<div>
<Header data={data} dateRangeStr={dateRangeStr} />
<div className="header-with-action">
<Header data={data} dateRangeStr={dateRangeStr} />
<ShareButton />
</div>
{data.qualitative && (
<>
@ -86,58 +90,73 @@ function InsightApp({ data }: { data: InsightData }) {
</>
)}
<ExportButton />
<ShareCard data={data} />
</div>
);
}
// Export Button Component
function ExportButton() {
// Share Button Component
function ShareButton() {
const [isExporting, setIsExporting] = useState(false);
const handleExport = async () => {
const container = document.getElementById('container');
if (!container || !window.html2canvas) {
const card = document.getElementById('share-card');
if (!card || !window.html2canvas) {
alert('Export functionality is not available.');
return;
}
setIsExporting(true);
try {
const canvas = await window.html2canvas(container, {
// Clone the card off-screen so it renders but isn't visible
const clone = card.cloneNode(true) as HTMLElement;
clone.style.position = 'fixed';
clone.style.left = '-9999px';
clone.style.top = '0';
clone.style.pointerEvents = 'none';
document.body.appendChild(clone);
const canvas = await window.html2canvas(clone, {
scale: 2,
useCORS: true,
logging: false,
width: 1200,
height: clone.scrollHeight,
});
document.body.removeChild(clone);
const imgData = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.href = imgData;
link.download = `qwen-insights-${new Date().toISOString().slice(0, 10)}.png`;
link.download = `qwen-insights-card-${new Date().toISOString().slice(0, 10)}.png`;
link.click();
} catch (error) {
console.error('Export error:', error);
alert('Failed to export image. Please try again.');
console.error('Export card error:', error);
alert('Failed to export card. Please try again.');
} finally {
setIsExporting(false);
}
};
return (
<div className="mt-6 flex justify-center">
<button
onClick={handleExport}
disabled={isExporting}
className="group inline-flex items-center gap-2 rounded-full bg-slate-900 px-5 py-3 text-sm font-semibold text-white shadow-soft transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-400 hover:-translate-y-[1px] hover:shadow-lg active:translate-y-[1px] disabled:opacity-50"
<button onClick={handleExport} disabled={isExporting} className="share-btn">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
{isExporting ? 'Exporting...' : 'Export as Image'}
<span className="text-slate-200 transition group-hover:translate-x-0.5">
</span>
</button>
</div>
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" />
<polyline points="16 6 12 2 8 6" />
<line x1="12" y1="2" x2="12" y2="15" />
</svg>
{isExporting ? 'Exporting...' : 'Share as Card'}
</button>
);
}

View file

@ -0,0 +1,501 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React from 'react';
import { InsightData } from './types';
/**
* A hidden 1200x675 card optimized for Twitter/X sharing.
* Rendered off-screen; captured by html2canvas when the user clicks "Share as Card".
*/
export function ShareCard({ data }: { data: InsightData }) {
const {
totalMessages = 0,
totalSessions = 0,
totalLinesAdded = 0,
totalLinesRemoved = 0,
totalFiles = 0,
currentStreak = 0,
longestStreak = 0,
activeHours = {},
} = data;
// Calculate active days
const heatmapKeys = Object.keys(data.heatmap || {});
let activeDays = 0;
let dateRangeStr = '';
if (heatmapKeys.length > 0) {
activeDays = heatmapKeys.length;
const timestamps = heatmapKeys.map((d) => new Date(d).getTime());
const minDate = new Date(Math.min(...timestamps));
const maxDate = new Date(Math.max(...timestamps));
const fmt = (d: Date) => d.toISOString().split('T')[0];
dateRangeStr = `${fmt(minDate)}${fmt(maxDate)}`;
}
// Key pattern (truncated for card)
const keyPattern = data.qualitative?.interactionStyle?.key_pattern ?? null;
// Memorable moment headline (truncated)
const truncatedHeadline = data.qualitative?.memorableMoment?.headline ?? null;
// Mini heatmap: last 52 weeks (simplified 7-row grid)
const miniHeatmap = buildMiniHeatmap(data.heatmap || {});
return (
<div
id="share-card"
style={{
width: '1200px',
background: 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)',
color: '#f8fafc',
fontFamily:
'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
display: 'flex',
flexDirection: 'column',
padding: '48px 56px',
position: 'absolute',
left: '-9999px',
top: '-9999px',
overflow: 'hidden',
boxSizing: 'border-box',
}}
>
{/* Header */}
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: '32px',
}}
>
<div>
<div
style={{
fontSize: '32px',
fontWeight: 700,
letterSpacing: '-0.02em',
lineHeight: 1.2,
}}
>
Qwen Code Insights
</div>
<div
style={{
fontSize: '14px',
color: '#94a3b8',
marginTop: '6px',
}}
>
{dateRangeStr}
</div>
</div>
<div
style={{
fontSize: '11px',
color: '#64748b',
textTransform: 'uppercase',
letterSpacing: '0.15em',
paddingTop: '8px',
}}
>
qwen.ai
</div>
</div>
{/* Stats Grid */}
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(6, 1fr)',
gap: '16px',
marginBottom: '32px',
}}
>
<StatBox value={String(totalMessages)} label="Messages" />
<StatBox value={String(totalSessions)} label="Sessions" />
<StatBox
value={`+${totalLinesAdded}/-${totalLinesRemoved}`}
label="Lines Changed"
small
/>
<StatBox value={String(totalFiles)} label="Files" />
<StatBox value={`${currentStreak}d`} label="Streak" />
<StatBox value={`${longestStreak}d`} label="Best Streak" />
</div>
{/* Body: Heatmap + Tools + Moment */}
<div
style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '24px',
marginBottom: '16px',
}}
>
{/* Left: Mini Heatmap */}
<div
style={{
background: 'rgba(255,255,255,0.05)',
borderRadius: '12px',
padding: '20px',
display: 'flex',
flexDirection: 'column',
}}
>
<div
style={{
fontSize: '12px',
fontWeight: 600,
color: '#94a3b8',
textTransform: 'uppercase',
letterSpacing: '0.08em',
marginBottom: '12px',
}}
>
Activity · {activeDays} active days
</div>
<div
style={{
flex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<MiniHeatmapGrid cells={miniHeatmap} />
</div>
</div>
{/* Right: Active Hours + Moment */}
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: '16px',
}}
>
{/* Active Hours */}
<div
style={{
background: 'rgba(255,255,255,0.05)',
borderRadius: '12px',
padding: '20px',
display: 'flex',
flexDirection: 'column',
}}
>
<div
style={{
fontSize: '12px',
fontWeight: 600,
color: '#94a3b8',
textTransform: 'uppercase',
letterSpacing: '0.08em',
marginBottom: '12px',
}}
>
Active Hours
</div>
<div
style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
gap: '10px',
}}
>
<ActiveHoursChart activeHours={activeHours} />
</div>
</div>
{/* Key Pattern + Memorable Moment */}
<div
style={{
background: 'rgba(255,255,255,0.04)',
borderRadius: '12px',
padding: '16px 16px',
position: 'relative',
}}
>
{/* Decorative large quote mark */}
<div
style={{
position: 'absolute',
left: '12px',
fontSize: '64px',
fontWeight: 700,
color: 'rgba(99,102,241,0.2)',
lineHeight: 1,
fontFamily: 'Georgia, "Times New Roman", serif',
userSelect: 'none',
pointerEvents: 'none',
}}
>
&ldquo;
</div>
<div
style={{
paddingLeft: '40px',
position: 'relative',
}}
>
{keyPattern && (
<div
style={{
fontSize: '13px',
color: '#e2e8f0',
lineHeight: 1.6,
marginBottom: truncatedHeadline ? '8px' : 0,
}}
>
{keyPattern}
</div>
)}
{truncatedHeadline && (
<div
style={{
fontSize: '12px',
color: '#94a3b8',
lineHeight: 1.5,
fontStyle: 'italic',
}}
>
{truncatedHeadline}
</div>
)}
</div>
</div>
</div>
</div>
{/* Footer */}
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 'auto',
paddingTop: '24px',
borderTop: '1px solid rgba(255,255,255,0.08)',
flexShrink: 0,
}}
>
<div style={{ fontSize: '12px', color: '#64748b' }}>
Generated by Qwen Code · {new Date().toISOString().split('T')[0]}
</div>
<div style={{ fontSize: '12px', color: '#64748b' }}>
github.com/QwenLM/qwen-code
</div>
</div>
</div>
);
}
function StatBox({
value,
label,
small,
}: {
value: string;
label: string;
small?: boolean;
}) {
return (
<div style={{ textAlign: 'center' }}>
<div
style={{
fontSize: small ? '18px' : '28px',
fontWeight: 700,
color: '#f8fafc',
lineHeight: 1.2,
}}
>
{value}
</div>
<div
style={{
fontSize: '11px',
color: '#64748b',
textTransform: 'uppercase',
letterSpacing: '0.05em',
marginTop: '4px',
}}
>
{label}
</div>
</div>
);
}
function ActiveHoursChart({
activeHours,
}: {
activeHours: { [hour: number]: number };
}) {
const phases = [
{
label: 'Morning',
time: '0612',
hours: [6, 7, 8, 9, 10, 11],
color: '#fbbf24',
},
{
label: 'Afternoon',
time: '1218',
hours: [12, 13, 14, 15, 16, 17],
color: '#0ea5e9',
},
{
label: 'Evening',
time: '1800',
hours: [18, 19, 20, 21, 22, 23],
color: '#6366f1',
},
{
label: 'Night',
time: '0006',
hours: [0, 1, 2, 3, 4, 5],
color: '#475569',
},
];
const data = phases.map((phase) => ({
...phase,
total: phase.hours.reduce((acc, h) => acc + (activeHours[h] || 0), 0),
}));
const maxTotal = Math.max(...data.map((d) => d.total), 1);
return (
<>
{data.map((item) => {
const pct = maxTotal > 0 ? (item.total / maxTotal) * 100 : 0;
return (
<div key={item.label}>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
fontSize: '12px',
marginBottom: '4px',
}}
>
<div
style={{ display: 'flex', alignItems: 'center', gap: '6px' }}
>
<span
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
backgroundColor: item.color,
display: 'inline-block',
flexShrink: 0,
}}
/>
<span style={{ color: '#e2e8f0', fontWeight: 500 }}>
{item.label}
</span>
<span style={{ color: '#64748b', fontSize: '11px' }}>
{item.time}
</span>
</div>
<span style={{ color: '#94a3b8', fontWeight: 600 }}>
{item.total}
</span>
</div>
<div
style={{
height: '6px',
background: 'rgba(255,255,255,0.08)',
borderRadius: '3px',
overflow: 'hidden',
}}
>
<div
style={{
width: `${pct}%`,
height: '100%',
backgroundColor: item.color,
borderRadius: '3px',
}}
/>
</div>
</div>
);
})}
</>
);
}
/** Build a 7x~26 grid of intensity values for the mini heatmap (last ~6 months). */
function buildMiniHeatmap(
heatmap: Record<string, number>,
): { color: string }[] {
const today = new Date();
const weeksToShow = 26;
const totalDays = weeksToShow * 7;
const startDate = new Date(today);
startDate.setDate(startDate.getDate() - totalDays + 1);
// Align to the beginning of the week (Sunday)
startDate.setDate(startDate.getDate() - startDate.getDay());
const cells: { color: string }[] = [];
const endDate = new Date(today);
endDate.setDate(endDate.getDate() + (6 - endDate.getDay())); // end of this week
const d = new Date(startDate);
while (d <= endDate) {
const key = d.toISOString().split('T')[0];
const val = heatmap[key] || 0;
cells.push({ color: heatColor(val) });
d.setDate(d.getDate() + 1);
}
return cells;
}
function heatColor(val: number): string {
if (val === 0) return 'rgba(255,255,255,0.06)';
if (val < 2) return '#1e3a5f';
if (val < 4) return '#2563eb';
if (val < 10) return '#3b82f6';
if (val < 20) return '#60a5fa';
return '#93c5fd';
}
function MiniHeatmapGrid({ cells }: { cells: { color: string }[] }) {
const rows = 7;
const cols = Math.ceil(cells.length / rows);
const cellSize = 14;
const gap = 3;
const svgWidth = cols * (cellSize + gap);
const svgHeight = rows * (cellSize + gap);
return (
<svg
width={svgWidth}
height={svgHeight}
viewBox={`0 0 ${svgWidth} ${svgHeight}`}
>
{cells.map((cell, i) => {
const col = Math.floor(i / rows);
const row = i % rows;
return (
<rect
key={i}
x={col * (cellSize + gap)}
y={row * (cellSize + gap)}
width={cellSize}
height={cellSize}
rx={2}
fill={cell.color}
/>
);
})}
</svg>
);
}

View file

@ -0,0 +1,363 @@
/**
* Dev entry point injects mock data then mounts the app.
* Used by `vite` dev server via index.html.
*/
import { InsightData } from './types';
const MOCK_DATA: InsightData = {
heatmap: {
'2026-02-03': 469,
'2026-02-04': 244,
'2026-02-02': 268,
'2026-01-15': 105,
'2026-01-19': 39,
'2026-02-10': 1584,
'2026-02-09': 1175,
'2026-02-05': 338,
'2025-12-11': 29,
'2026-02-11': 585,
'2026-02-06': 671,
'2026-01-16': 17,
'2026-01-14': 120,
'2025-12-31': 18,
'2025-12-05': 24,
},
currentStreak: 3,
longestStreak: 5,
longestWorkDate: '2026-02-03',
longestWorkDuration: 1520,
activeHours: {
'0': 161,
'9': 4,
'10': 211,
'11': 852,
'12': 263,
'13': 682,
'14': 1465,
'15': 518,
'16': 347,
'17': 116,
'18': 194,
'19': 680,
'20': 188,
'21': 5,
},
latestActiveTime: '08:00 AM',
totalSessions: 314,
totalMessages: 5686,
totalHours: 60,
topTools: [
['read_file', 209],
['run_shell_command', 166],
['edit', 98],
['grep_search', 53],
['todo_write', 52],
['web_fetch', 19],
['list_directory', 15],
['glob', 14],
['write_file', 5],
['skill', 4],
],
totalLinesAdded: 3000,
totalLinesRemoved: 285,
totalFiles: 23,
qualitative: {
impressiveWorkflows: {
intro:
"You're a highly active Qwen Code user with 314 sessions over 67 days, achieving strong results in test creation, PR workflows, and debugging with a 68% success rate on clear tasks.",
impressive_workflows: [
{
title: 'Comprehensive Test Creation Workflow',
description:
'You successfully requested and received comprehensive unit tests for DataProcessor.ts, resulting in 34 test cases covering all major methods with proper mocking. When tests initially failed due to mock implementation issues, you iterated through fixes until all tests passed, demonstrating strong test-driven development practices.',
},
{
title: 'Efficient PR Creation Process',
description:
"You've mastered the PR creation workflow, successfully creating pull requests using existing templates multiple times. You know how to leverage Qwen to format PRs properly in English and complete this task efficiently, often in single responses with multiple coordinated tool calls.",
},
{
title: 'Systematic Debugging and Refactoring',
description:
"You effectively use Qwen for debugging failing tests, successfully identifying issues like missing KeypressProvider context and ink mock setup problems. You also completed clean refactoring tasks like migrating openFileInBrowser to use the 'open' package, with all checks passing on the first attempt.",
},
],
},
projectAreas: {
areas: [
{
name: 'Feature Development',
session_count: 6,
description:
'Implementation of new features including ESC cancellation support for slash commands (specifically /compress). Work involved adding signal to CommandContext and implementing Promise.race patterns for proper abort handling.',
},
{
name: 'Pull Request Management',
session_count: 4,
description:
'Creating pull requests from existing commits using PR templates. Sessions involved automated PR generation with proper formatting, with some initial friction around template application that was resolved.',
},
{
name: 'Test Creation & Debugging',
session_count: 3,
description:
'Creating comprehensive unit tests for components like DataProcessor.ts using Vitest, and debugging failing tests including fixing KeypressProvider context issues and ink mock setup problems through iterative refinement.',
},
{
name: 'Code Review',
session_count: 3,
description:
'Reviewing GitHub pull requests and commit ranges to identify issues and provide feedback. Included manual review of large PRs when automated skills failed, focusing on missing tests and error handling problems.',
},
{
name: 'Code Refactoring',
session_count: 2,
description:
"Improving existing code quality through targeted refactoring efforts, such as updating openFileInBrowser to use the 'open' package and reverting unnecessary script file changes.",
},
],
},
futureOpportunities: {
intro:
'As AI-assisted development evolves from reactive pair-programming to proactive autonomous systems, usage patterns reveal massive opportunities for self-improving agent loops that can iterate, test, and refactor without human intervention.',
opportunities: [
{
title: 'Autonomous Test-Iterate-Fix Loop',
whats_possible:
'An agent that runs your full test suite, identifies failures, generates fixes, and re-runs tests in a closed loop until achieving 100% pass rate—no human intervention required. The system could learn from each iteration, applying increasingly targeted fixes while tracking which approaches work best for different failure types.',
how_to_try:
"Configure your AI to watch test output files, parse failure messages, and apply fixes using your test framework's reporters; Vitest and Jest both support JSON output for easy parsing.",
copyable_prompt:
"I want you to run my test suite in watch mode. When tests fail, analyze the failure output, identify the root cause, propose a fix, apply it, and re-run the tests automatically. Keep iterating until all tests pass. For each iteration, show me: (1) which test failed, (2) your diagnosis, (3) the fix you're applying, and (4) the result. If a fix doesn't work after 2 attempts, try a different approach. Track which patterns of fixes work best so you can apply learned strategies.",
},
{
title: 'Parallel Agent Exploration with Consensus',
whats_possible:
'Spawn multiple independent AI agents to solve the same problem using different strategies (TDD vs implementation-first, different architectural patterns, different libraries), then automatically merge the best aspects of each solution. This parallel approach could solve complex problems 5-10x faster while discovering novel solutions a single agent would miss.',
how_to_try:
'Use terminal multiplexers or containerized environments to run multiple AI agent instances concurrently, then use a meta-agent to analyze and synthesize outputs.',
copyable_prompt:
"I want you to solve this problem using parallel exploration. Open 3 separate terminal sessions and in each one, implement a solution using a different approach: (1) Test-driven development - write tests first, (2) Implementation-first with aggressive refactoring, (3) Use a different library/algorithm than you'd normally choose. Work on all three simultaneously, switching between them every few minutes. After 10 minutes, compare all three solutions and create a final version that combines the best aspects of each. Show me the tradeoffs you discovered and why the final hybrid solution is better than any individual approach.",
},
{
title: 'Continuous Code Health Agent',
whats_possible:
'A background agent that continuously monitors your codebase for code smells, missing tests, security vulnerabilities, and refactoring opportunities—automatically creating pull requests with improvements while you sleep. It could maintain a living document of technical debt, prioritize fixes by impact, and learn from code review feedback to improve its suggestions over time.',
how_to_try:
'Set up a scheduled CI/CD job that runs your AI against changed files with a comprehensive code health checklist, using git hooks to trigger analysis on commits.',
copyable_prompt:
'Run as a continuous code health monitor. Scan the entire codebase and create a prioritized list of improvements in these categories: (1) Missing or inadequate tests - identify untested edge cases, (2) Code smells - long functions, duplication, complex conditionals, (3) Security issues - hardcoded secrets, unsafe patterns, (4) Performance bottlenecks, (5) Outdated dependencies with breaking changes. For each issue, assign a priority score (1-10) based on impact and effort. Then automatically fix the top 5 highest-priority issues and create a summary PR. Add a comment on each file explaining what you changed and why. Set this up to run nightly and track improvements over time.',
},
],
},
frictionPoints: {
intro:
'Your usage shows significant friction from unproductive sessions and technical issues, with many interactions never progressing beyond greetings and several work items requiring multiple iterations to resolve.',
categories: [
{
category: 'Unproductive Session Overhead',
description:
"You spent significant time in sessions that never progressed beyond greetings or warmups, with 28 out of 48 analyzed sessions being minimal interactions that didn't result in actual work being completed.",
examples: [
"28 sessions were just warmup_minimal with 'hi' greetings and no actual requests, consuming your time without delivering value",
"Multiple sessions had only 'hi' or 'nihao' exchanges in Chinese where you greeted back but no task was ever requested or completed",
],
},
{
category: 'Buggy Code and Incomplete Solutions',
description:
"You encountered buggy code generation in 5 instances where Qwen's initial implementations were incorrect, forcing you to spend additional time iterating on fixes before achieving working solutions.",
examples: [
'Test creation required multiple iterations to fix incorrect vi.mocked() usage patterns and fs module mocking approaches before tests finally passed',
"ESC cancellation feature initially created AbortController but didn't pass signal to command actions, requiring a follow-up fix to properly implement Promise.race in compressCommand",
],
},
{
category: 'Tool and Skill Reliability Issues',
description:
"You experienced situations where Qwen's tools or skills failed to execute properly, forcing manual workarounds and excessive exploration that extended task completion time.",
examples: [
'The code-reviewer subagent skill failed to execute, forcing you to manually review a large PR with 416 files instead of leveraging the automated review capability',
'PR creation initially generated empty content instead of reading and applying the template file, requiring you to complain and request a fix before getting proper output',
],
},
],
},
memorableMoment: {
headline:
"User paused mid-work to verify Qwen's corporate identity—\"Are you Alibaba's model or Zhipu's GLM?\"",
detail:
"During what appears to be routine development work, a user stopped everything to ask Qwen to confirm its corporate origins. Qwen correctly identified itself as Alibaba's model, not Zhipu's GLM. A rare moment of AI identity verification in the wild.",
},
improvements: {
Qwen_md_additions: [
{
addition:
'## Code Review Standards\n- Always check for test coverage when reviewing new features\n- Flag missing error handling in async functions\n- Prefer focused PRs over large multi-file changes',
why: 'Code review friction showed issues with subagent failures and excessive exploration on large PRs',
prompt_scaffold:
'Create new ## Code Review Standards section in QWEN.md',
},
{
addition:
'## Testing Conventions\n- Use vi.mock() for module mocks before vi.mocked() for function mocking\n- Always wrap components with required providers (e.g., KeypressProvider) in tests\n- Run tests after modifying related files to catch issues early',
why: 'Multiple test sessions had buggy mock implementation issues that required iterative fixes',
prompt_scaffold:
'Create new ## Testing Conventions section in QWEN.md',
},
{
addition:
'## PR Workflow\n- Always read and apply PR template before creating PR content\n- Use English as default language for PR descriptions unless specified otherwise',
why: 'One session created empty PR initially, another required English specification - both are repetitive clarifications',
prompt_scaffold: 'Create new ## PR Workflow section in QWEN.md',
},
],
features_to_try: [
{
feature: 'Custom Skills',
one_liner: 'Create reusable prompt templates for common workflows',
why_for_you:
'You have 6 feature requests and 3+ PR creation sessions - these repetitive workflows would benefit from /pr, /review, /test commands',
example_code:
'Create .qwen/skills/pr/SKILL.md:\n```\nCreate a PR using the template at .github/PULL_REQUEST_TEMPLATE.md\n- Summarize commits since main\n- Use English language\n- Include test coverage notes\n```',
},
{
feature: 'Custom Skills',
one_liner: 'Define test generation standards as a reusable skill',
why_for_you:
'You had 2 test creation sessions with mock implementation issues - a /test skill would ensure consistent patterns',
example_code:
'Create .qwen/skills/test/SKILL.md:\n```\nGenerate Vitest tests for the specified file:\n- Use vi.mock() for modules before vi.mocked()\n- Include provider wrappers as needed\n- Run tests after generation to verify\n```',
},
{
feature: 'Custom Skills',
one_liner: 'Create a code review checklist skill',
why_for_you:
'You had 3 code review sessions where the code-reviewer subagent failed - a manual /review skill would be more reliable',
example_code:
'Create .qwen/skills/review/SKILL.md:\n```\nReview code for:\n1. Test coverage for new features\n2. Error handling in async functions\n3. Security implications\n4. Breaking changes\n```',
},
{
feature: 'Headless Mode',
one_liner: 'Run Qwen non-interactively for batch operations',
why_for_you:
'With 166 run_shell_command calls and repetitive tasks like PR creation, you could automate routine workflows',
example_code:
'# Create PR from latest commits\nqwen -p "Create a PR for recent commits using .github/PULL_REQUEST_TEMPLATE.md"\n\n# Batch test generation\nqwen -p "Generate tests for src/DataProcessor.ts"',
},
{
feature: 'Task Agents',
one_liner: 'Spawn focused sub-agents for complex exploration',
why_for_you:
'Your grep_search (53) and file exploration could be more efficient with dedicated agents for codebase understanding',
example_code:
'Ask Qwen: "Use an agent to explore the test infrastructure and identify all test utilities available"',
},
],
usage_patterns: [
{
title: 'Reduce Empty Warmup Sessions',
suggestion:
'Start sessions with a specific task or use /compress to load context quickly',
detail:
"28 of your sessions are warmup_minimal with just 'hi' greetings. These consume time without progress. Instead, start with your actual request or use a skill like /compress to load recent context and continue previous work.",
copyable_prompt:
"Instead of 'hi', try: 'Continue working on [specific task]' or '/compress'",
},
{
title: 'Leverage Skills for Repetitive Work',
suggestion:
'Create skills for PR creation and test generation since you do these repeatedly',
detail:
"You've created multiple PRs and tests with similar patterns each time. Creating /pr and /test skills would ensure consistency (English language, proper templates, correct mock patterns) and save time across your 314 sessions.",
copyable_prompt:
'Create .qwen/skills/pr/SKILL.md with your PR template requirements, then just type /pr',
},
{
title: 'Improve Session Completion Rate',
suggestion:
'Use todo_write to track progress and ensure full completion before ending sessions',
detail:
"Your outcomes show 7 not_achieved vs 13 fully_achieved. With 52 todo_write calls, you're already tracking tasks - ensure you complete all items or explicitly carry them to the next session rather than leaving work incomplete.",
copyable_prompt:
"At session start: 'Review my recent incomplete tasks and continue from where we left off'",
},
{
title: 'Preempt Common Issues',
suggestion:
'Add testing conventions to QWEN.md to avoid iterative mock fixes',
detail:
'5 buggy_code friction points mostly came from test mock implementation issues. Documenting your testing patterns in QWEN.md would help Qwen generate correct tests on the first attempt.',
copyable_prompt:
"Add to QWEN.md: 'When generating tests, use vi.mock() before vi.mocked() and always include required providers'",
},
],
},
interactionStyle: {
narrative:
'Your interaction pattern shows **high-frequency, low-intensity engagement** with Qwen Code. With 314 sessions over just 68 days (averaging 4-5 sessions daily) and 58% of analyzed sessions being "warmup_minimal," you treat Qwen as a readily-available assistant you check in with frequently but often don\'t have specific tasks ready. You say "hi" or "nihao" and wait to see what happens, suggesting you keep Qwen accessible as a background tool rather than planning extensive work sessions.\n\nWhen you do have actual work, you\'re **iterative and feedback-driven rather than specification-heavy**. Examples: when creating PRs, you accepted an initial implementation that created empty content, then reported the issue for Qwen to fix; when implementing ESC cancellation support, you tested the code, discovered the signal wasn\'t being passed through, and reported the specific bug for correction; when requesting tests for DataProcessor, you worked through multiple rounds of fixing mock implementation issues. You don\'t provide exhaustive upfront requirements—you point Qwen at a problem, see what it produces, and course-correct.\n\n**You trust Qwen to explore autonomously but intervene when things go wrong.** Your tool usage (209 read_file calls, 166 shell commands, 98 edits) shows you let Qwen investigate and modify freely. The friction data reveals low rejection rates (0 user_rejected_action, 0 excessive_changes) and zero misunderstood requests, indicating you give Qwen space to work. However, 5 instances of buggy code required your feedback to fix, and you clearly communicate specific issues when they arise ("abort() was called but execution didn\'t stop"). You\'re a collaborative debugger who provides precise problem descriptions rather than vague complaints.',
key_pattern:
'You use Qwen Code as a high-frequency, low-commitment tool with iterative feedback loops—many brief check-ins with "hi" or minimal interaction, and when real work happens, you prefer to test implementations and report specific issues for correction rather than providing comprehensive upfront specifications.',
},
atAGlance: {
whats_working:
'You take a direct, task-oriented approach—submitting clear requests for PR creation, test generation, and refactoring work. Your test generation sessions were particularly effective, producing comprehensive test suites that passed after iteration, and your debugging work successfully identified root causes like missing context providers.',
whats_hindering:
"On Qwen's side: some implementations needed follow-up fixes (missing signal passing in abort handling, initial empty PR content), and buggy code generation required multiple iterations—especially around mocking patterns. The code-reviewer skill also failed during a large PR review. On your side: many sessions were empty or just greetings without actual requests, and some friction around iterative debugging could be smoothed with more upfront context about testing frameworks and patterns you prefer.",
quick_wins:
'Try using subagents more deliberately for complex tasks like code reviews (you only called skills 4 times)—they can handle multi-step analysis autonomously. When generating tests, specify your preferred mocking patterns upfront to reduce iteration cycles.',
ambitious_workflows:
"As models improve, you'll be able to hand off larger autonomous refactoring tasks across many files (you've only done multi-file changes once). Complex debugging sessions that currently require multiple iterations could become single-shot successes. Large PR reviews that stalled due to skill failures will flow smoothly through automated analysis, letting you focus on architectural decisions rather than line-by-line review.",
},
},
satisfaction: {
unknown: 0,
satisfied: 4,
happy: 0,
likely_satisfied: 7,
dissatisfied: 1,
frustrated: 0,
},
friction: {
missing_transcript: 2,
misunderstood_request: 0,
wrong_approach: 2,
buggy_code: 5,
user_rejected_action: 0,
excessive_changes: 0,
tool_failure: 1,
excessive_exploration: 1,
},
primarySuccess: {
correct_code_edits: 7,
good_debugging: 1,
fast_accurate_search: 4,
multi_file_changes: 1,
good_explanations: 1,
proactive_help: 1,
},
outcomes: {
unclear_from_transcript: 25,
fully_achieved: 13,
not_achieved: 7,
mostly_achieved: 2,
partially_achieved: 1,
},
topGoals: {
unknown: 0,
warmup_minimal: 28,
bug_fix: 2,
feature_request: 6,
debugging: 1,
test_creation: 2,
code_refactoring: 2,
code_review: 3,
},
};
// Inject mock data into window so App.tsx can read it on mount
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).INSIGHT_DATA = MOCK_DATA;
// Dynamic import to ensure INSIGHT_DATA is set before App.tsx executes
import('./App');

View file

@ -1130,3 +1130,48 @@ body {
justify-content: center;
}
}
/* Header with action button */
.header-with-action {
position: relative;
}
/* Share Button */
.share-btn {
position: absolute;
top: 0;
right: 0;
display: inline-flex;
align-items: center;
gap: 8px;
border: none;
border-radius: 8px;
padding: 10px 20px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
color: #fff;
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
transition: all 0.2s;
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.2);
}
.share-btn:hover:not(:disabled) {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.3);
transform: translateY(-1px);
}
.share-btn:active:not(:disabled) {
transform: translateY(0);
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.2);
}
.share-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.share-btn svg {
opacity: 0.8;
}

View file

@ -45,7 +45,7 @@ import {
const logger = createDebugLogger('DataProcessor');
const CONCURRENCY_LIMIT = 2;
const CONCURRENCY_LIMIT = 4;
export class DataProcessor {
constructor(private config: Config) {}
@ -189,7 +189,7 @@ export class DataProcessor {
model: this.config.getModel(),
contents: [{ role: 'user', parts: [{ text: prompt }] }],
schema: INSIGHT_SCHEMA,
abortSignal: AbortSignal.timeout(60000), // 1 minute timeout per session
abortSignal: AbortSignal.timeout(600000), // 10 minute timeout per session
});
if (!result || Object.keys(result).length === 0) {
@ -383,7 +383,7 @@ export class DataProcessor {
model: this.config.getModel(),
contents: [{ role: 'user', parts: [{ text: prompt }] }],
schema,
abortSignal: AbortSignal.timeout(60000),
abortSignal: AbortSignal.timeout(600000),
});
return result as T;
} catch (error) {
@ -627,6 +627,23 @@ export class DataProcessor {
),
]);
logger.debug(
JSON.stringify(
{
impressiveWorkflows,
projectAreas,
futureOpportunities,
frictionPoints,
memorableMoment,
improvements,
interactionStyle,
atAGlance,
},
null,
2,
),
);
return {
impressiveWorkflows,
projectAreas,

View file

@ -94,10 +94,31 @@ export const PROMPT_IMPROVEMENTS = `Analyze this Qwen Code usage data and sugges
1. **MCP Servers**: Connect Qwen to external tools, databases, and APIs via Model Context Protocol.
- How to use: Run \`qwen mcp add --transport http <server-name> <http-url>\`
- Good for: database queries, Slack integration, GitHub issue lookup, connecting to internal APIs
- Example: "To connect to GitHub, run \`qwen mcp add --header "Authorization: Bearer your_github_mcp_pat" --transport http github https://api.githubcopilot.com/mcp/\` and set the AUTHORIZATION header with your PAT. Then you can ask Qwen to query issues, PRs, or repos."
2. **Custom Skills**: Reusable prompts you define as markdown files that run with a single /command.
- How to use: Create \`.qwen/skills/commit/SKILL.md\` with instructions. Then type \`/commit\` to run it.
- Good for: repetitive workflows - /commit, /review, /test, /deploy, /pr, or complex multi-step workflows
- SKILL.md format:
\`\`\`
---
name: skill-name
description: A description of what this skill does and when to use it.
---
# Steps
1. First, do X.
2. Then do Y.
3. Finally, verify Z.
# Examples
- Input: "fix lint errors in src/" Output: runs eslint --fix, commits changes
- Input: "review this PR" Output: reads diff, posts inline comments
# Edge Cases
- If no files match, report "nothing to do" instead of failing.
- If the user didn't specify a branch, default to the current branch.
\`\`\`
3. **Headless Mode**: Run Qwen non-interactively from scripts and CI/CD.
- How to use: \`qwen -p "fix lint errors"\`

File diff suppressed because one or more lines are too long

View file

@ -17,7 +17,7 @@ interface InsightProgressMessageProps {
export const InsightProgressMessage: React.FC<InsightProgressMessageProps> = ({
progress,
}) => {
const { stage, progress: percent, detail, isComplete, error } = progress;
const { stage, progress: percent, isComplete, error } = progress;
const width = 30;
const completedWidth = Math.round((percent / 100) * width);
const remainingWidth = width - completedWidth;
@ -56,11 +56,6 @@ export const InsightProgressMessage: React.FC<InsightProgressMessageProps> = ({
{bar} {Math.round(percent)}%
</Text>
</Box>
{detail && (
<Box marginLeft={2}>
<Text color={theme.text.secondary}>{detail}</Text>
</Box>
)}
</Box>
);
};