basant307/AI_Governance_Project
048
1/* eslint-disable @typescript-eslint/no-explicit-any */2import { useState } from 'react';3import { DashboardCards, HeatmapSection } from './Charts';4import type { InsightData, QualitativeData } from './types';5import { CopyButton, MarkdownText } from './Components';6// eslint-disable-next-line @typescript-eslint/no-unused-vars7import React from 'react';8 9// -----------------------------------------------------------------------------10// Qualitative Insight Components11// -----------------------------------------------------------------------------12 13function hasMeaningfulText(value: unknown): value is string {14 return typeof value === 'string' && value.trim().length > 0;15}16 17export interface AtAGlanceTargetSections {18 wins: boolean;19 friction: boolean;20 features: boolean;21 horizon: boolean;22}23 24export function AtAGlance({25 qualitative,26 targetSections,27}: {28 qualitative: QualitativeData;29 targetSections: AtAGlanceTargetSections;30}) {31 const { atAGlance } = qualitative;32 if (!atAGlance) return null;33 34 const sections = [35 {36 key: 'wins',37 label: "What's working:",38 body: atAGlance.whats_working,39 href: '#section-wins',40 link: 'Impressive Things You Did →',41 showLink: targetSections.wins,42 },43 {44 key: 'friction',45 label: "What's hindering you:",46 body: atAGlance.whats_hindering,47 href: '#section-friction',48 link: 'Where Things Go Wrong →',49 showLink: targetSections.friction,50 },51 {52 key: 'features',53 label: 'Quick wins to try:',54 body: atAGlance.quick_wins,55 href: '#section-features',56 link: 'Features to Try →',57 showLink: targetSections.features,58 },59 {60 key: 'horizon',61 label: 'Ambitious workflows:',62 body: atAGlance.ambitious_workflows,63 href: '#section-horizon',64 link: 'On the Horizon →',65 showLink: targetSections.horizon,66 },67 ].filter((section) => hasMeaningfulText(section.body));68 69 if (sections.length === 0) return null;70 71 return (72 <div className="at-a-glance">73 <div className="glance-title">At a Glance</div>74 <div className="glance-sections">75 {sections.map((section) => (76 <div key={section.key} className="glance-section">77 <strong>{section.label}</strong>{' '}78 <MarkdownText>{section.body}</MarkdownText>79 {section.showLink && (80 <a href={section.href} className="see-more">81 {section.link}82 </a>83 )}84 </div>85 ))}86 </div>87 </div>88 );89}90 91export interface NavTocSection {92 href: string;93 label: string;94}95 96export function NavToc({ sections }: { sections: NavTocSection[] }) {97 if (sections.length === 0) return null;98 99 return (100 <nav className="nav-toc">101 {sections.map((section) => (102 <a key={section.href} href={section.href}>103 {section.label}104 </a>105 ))}106 </nav>107 );108}109 110export function ProjectAreas({111 qualitative,112 topGoals,113 topTools,114}: {115 qualitative: QualitativeData;116 topGoals?: Record<string, number>;117 topTools?: Record<string, number> | Array<[string, number]>;118}) {119 const { projectAreas } = qualitative;120 121 // Convert topTools (array of tuples) to object for chart if needed122 const topToolsObj = Array.isArray(topTools)123 ? Object.fromEntries(topTools)124 : topTools;125 126 return (127 <>128 <h2129 id="section-work"130 className="text-xl font-semibold text-slate-900 mt-8 mb-4"131 >132 What You Work On133 </h2>134 135 {Array.isArray(projectAreas?.areas) && projectAreas.areas.length > 0 && (136 <div className="project-areas mb-6">137 {projectAreas.areas.map((area, idx) => (138 <div key={idx} className="project-area">139 <div className="area-header">140 <span className="area-name">{area.name}</span>141 <span className="area-count">142 ~{area.session_count} sessions143 </span>144 </div>145 <div className="area-desc">146 <MarkdownText>{area.description}</MarkdownText>147 </div>148 </div>149 ))}150 </div>151 )}152 153 <div154 style={{155 display: 'grid',156 gridTemplateColumns: 'repeat(2, 1fr)',157 gap: '24px',158 marginBottom: '24px',159 }}160 >161 {topGoals && Object.keys(topGoals).length > 0 && (162 <HorizontalBarChart163 data={topGoals}164 title="What You Wanted"165 color="#0ea5e9"166 />167 )}168 {topToolsObj && Object.keys(topToolsObj).length > 0 && (169 <HorizontalBarChart170 data={topToolsObj}171 title="Top Tools Used"172 color="#6366f1"173 />174 )}175 </div>176 </>177 );178}179 180export function InteractionStyle({181 qualitative,182 insights,183}: {184 qualitative: QualitativeData;185 insights: InsightData;186}) {187 const { interactionStyle } = qualitative;188 if (!interactionStyle) return null;189 190 return (191 <>192 <h2193 id="section-usage"194 className="text-xl font-semibold text-slate-900 mt-8 mb-4"195 >196 How You Use Qwen Code197 </h2>198 <div className="narrative">199 <p>200 <MarkdownText>{interactionStyle.narrative}</MarkdownText>201 </p>202 {interactionStyle.key_pattern && (203 <div className="key-insight">204 <strong>Key pattern:</strong>{' '}205 <MarkdownText>{interactionStyle.key_pattern}</MarkdownText>206 </div>207 )}208 </div>209 210 <DashboardCards insights={insights} />211 <HeatmapSection heatmap={insights.heatmap} />212 </>213 );214}215 216export function ImpressiveWorkflows({217 qualitative,218 primarySuccess,219 outcomes,220}: {221 qualitative?: QualitativeData;222 primarySuccess: Record<string, number>;223 outcomes: Record<string, number>;224}) {225 const impressiveWorkflows = qualitative?.impressiveWorkflows;226 227 return (228 <>229 <h2230 id="section-wins"231 className="text-xl font-semibold text-slate-900 mt-8 mb-4"232 >233 Impressive Things You Did234 </h2>235 {impressiveWorkflows?.intro && (236 <p className="section-intro">237 <MarkdownText>{impressiveWorkflows.intro}</MarkdownText>238 </p>239 )}240 {impressiveWorkflows && (241 <div className="big-wins">242 {Array.isArray(impressiveWorkflows.impressive_workflows) &&243 impressiveWorkflows.impressive_workflows.map((win, idx) => (244 <div key={idx} className="big-win">245 <div className="big-win-title">{win.title}</div>246 <div className="big-win-desc">247 <MarkdownText>{win.description}</MarkdownText>248 </div>249 </div>250 ))}251 </div>252 )}253 254 <div255 style={{256 display: 'grid',257 gridTemplateColumns: 'repeat(2, 1fr)',258 gap: '24px',259 marginTop: '24px',260 marginBottom: '24px',261 }}262 >263 {primarySuccess && Object.keys(primarySuccess).length > 0 && (264 <HorizontalBarChart265 data={primarySuccess}266 title="What Helped Most (Qwen's Capabilities)"267 color="#3b82f6"268 allowedKeys={[269 'fast_accurate_search',270 'correct_code_edits',271 'good_explanations',272 'proactive_help',273 'multi_file_changes',274 'good_debugging',275 ]}276 />277 )}278 {outcomes && Object.keys(outcomes).length > 0 && (279 <HorizontalBarChart280 data={outcomes}281 title="Outcomes"282 color="#8b5cf6"283 allowedKeys={[284 'fully_achieved',285 'mostly_achieved',286 'partially_achieved',287 'not_achieved',288 'unclear_from_transcript',289 ]}290 />291 )}292 </div>293 </>294 );295}296 297// Format label for display (capitalize and replace underscores with spaces)298function formatLabel(label: string) {299 if (label === 'unclear_from_transcript') {300 return 'Unclear';301 }302 return label303 .split('_')304 .map((word) => word.charAt(0).toUpperCase() + word.slice(1))305 .join(' ');306}307 308// Horizontal Bar Chart Component309function HorizontalBarChart({310 data,311 title,312 color = '#3b82f6',313 allowedKeys = null,314}: {315 data: Record<string, number>;316 title: string;317 color?: string;318 allowedKeys?: string[] | null;319}) {320 if (!data || Object.keys(data).length === 0) return null;321 322 // Filter and sort entries323 let entries = Object.entries(data).filter(324 ([, count]) => Number.isFinite(count) && count !== 0,325 );326 if (allowedKeys) {327 entries = entries.filter(([key]) => allowedKeys.includes(key));328 }329 entries.sort((a, b) => b[1] - a[1]);330 331 // Limit to at most 10 items332 entries = entries.slice(0, 10);333 334 if (entries.length === 0) return null;335 336 const maxValue = Math.max(...entries.map(([, count]) => count));337 338 return (339 <div340 className="bar-chart-card"341 style={{342 flex: 1,343 minWidth: 0,344 backgroundColor: '#ffffff',345 borderRadius: '12px',346 padding: '20px',347 boxShadow: '0 1px 3px rgba(0, 0, 0, 0.08)',348 border: '1px solid #e2e8f0',349 }}350 >351 <h3352 style={{353 fontSize: '13px',354 fontWeight: 700,355 color: '#64748b',356 marginTop: 0,357 marginBottom: '16px',358 textTransform: 'uppercase',359 letterSpacing: '0.5px',360 }}361 >362 {title}363 </h3>364 <div365 className="bar-chart"366 style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}367 >368 {entries.map(([label, count]) => {369 const percentage = maxValue > 0 ? (count / maxValue) * 100 : 0;370 return (371 <div372 key={label}373 className="bar-row"374 style={{ display: 'flex', alignItems: 'center', gap: '12px' }}375 >376 <div377 className="bar-label"378 style={{379 width: '130px',380 fontSize: '13px',381 color: '#475569',382 textAlign: 'left',383 flexShrink: 0,384 whiteSpace: 'nowrap',385 overflow: 'hidden',386 textOverflow: 'ellipsis',387 }}388 >389 {formatLabel(label)}390 </div>391 <div392 className="bar-wrapper"393 style={{394 flex: 1,395 display: 'flex',396 alignItems: 'center',397 gap: '10px',398 minWidth: 0,399 }}400 >401 <div402 className="bar-bg"403 style={{404 flex: 1,405 height: '8px',406 backgroundColor: '#f1f5f9',407 borderRadius: '4px',408 overflow: 'hidden',409 }}410 >411 <div412 className="bar-fill"413 style={{414 width: `${percentage}%`,415 height: '100%',416 backgroundColor: color,417 borderRadius: '4px',418 transition: 'width 0.3s ease',419 }}420 />421 </div>422 <span423 className="bar-value"424 style={{425 fontSize: '13px',426 fontWeight: 600,427 color: '#475569',428 minWidth: '24px',429 textAlign: 'right',430 }}431 >432 {count}433 </span>434 </div>435 </div>436 );437 })}438 </div>439 </div>440 );441}442 443export function FrictionPoints({444 qualitative,445 satisfaction,446 friction,447}: {448 qualitative?: QualitativeData;449 satisfaction?: Record<string, number>;450 friction?: Record<string, number>;451}) {452 const frictionPoints = qualitative?.frictionPoints;453 454 return (455 <>456 <h2457 id="section-friction"458 className="text-xl font-semibold text-slate-900 mt-8 mb-4"459 >460 Where Things Go Wrong461 </h2>462 {frictionPoints?.intro && (463 <p className="section-intro">464 <MarkdownText>{frictionPoints.intro}</MarkdownText>465 </p>466 )}467 {frictionPoints && (468 <div className="friction-categories">469 {Array.isArray(frictionPoints.categories) &&470 frictionPoints.categories.map((cat, idx) => (471 <div key={idx} className="friction-category">472 <div className="friction-title">{cat.category}</div>473 <div className="friction-desc">474 <MarkdownText>{cat.description}</MarkdownText>475 </div>476 {Array.isArray(cat.examples) && cat.examples.length > 0 && (477 <ul className="friction-examples">478 {cat.examples.map((ex, i) => (479 <li key={i}>480 <MarkdownText>{ex}</MarkdownText>481 </li>482 ))}483 </ul>484 )}485 </div>486 ))}487 </div>488 )}489 490 {/* Facets Data Charts */}491 <div492 style={{493 display: 'grid',494 gridTemplateColumns: 'repeat(2, 1fr)',495 gap: '24px',496 marginTop: '24px',497 marginBottom: '24px',498 }}499 >500 {friction && Object.keys(friction).length > 0 && (501 <HorizontalBarChart502 data={friction}503 title="Primary Friction Types"504 color="#ef4444"505 allowedKeys={[506 'misunderstood_request',507 'wrong_approach',508 'buggy_code',509 'user_rejected_action',510 'excessive_changes',511 ]}512 />513 )}514 {satisfaction && Object.keys(satisfaction).length > 0 && (515 <HorizontalBarChart516 data={satisfaction}517 title="Inferred Satisfaction (model-estimated)"518 color="#10b981"519 allowedKeys={[520 'happy',521 'satisfied',522 'likely_satisfied',523 'dissatisfied',524 'frustrated',525 ]}526 />527 )}528 </div>529 </>530 );531}532 533// Qwen.md Additions Section Component534function QwenMdAdditionsSection({535 additions,536}: {537 additions: NonNullable<538 NonNullable<QualitativeData['improvements']>['Qwen_md_additions']539 >;540}) {541 const [checkedState, setCheckedState] = useState(542 new Array(additions.length).fill(true),543 );544 const [copiedAll, setCopiedAll] = useState(false);545 546 const handleCheckboxChange = (position: number) => {547 const updatedCheckedState = checkedState.map((item, index) =>548 index === position ? !item : item,549 );550 setCheckedState(updatedCheckedState);551 };552 553 const handleCopyAll = () => {554 const textToCopy = additions555 .filter((_, index) => checkedState[index])556 .map((item: { addition: any }) => item.addition)557 .join('\n\n');558 559 if (!textToCopy) return;560 561 navigator.clipboard.writeText(textToCopy).then(() => {562 setCopiedAll(true);563 setTimeout(() => setCopiedAll(false), 2000);564 });565 };566 567 const checkedCount = checkedState.filter(Boolean).length;568 569 return (570 <div className="qwen-md-section">571 <h3>Suggested QWEN.md Additions</h3>572 <p className="text-xs text-slate-500 mb-3">573 Just copy this into Qwen Code to add it to your QWEN.md.574 </p>575 576 <div className="qwen-md-actions" style={{ marginBottom: '12px' }}>577 <button578 className={`copy-all-btn ${copiedAll ? 'copied' : ''}`}579 onClick={handleCopyAll}580 disabled={checkedCount === 0}581 >582 {copiedAll ? 'Copied All!' : `Copy All Checked (${checkedCount})`}583 </button>584 </div>585 586 {additions.map((item, idx) => (587 <div key={idx} className="qwen-md-item">588 <input589 type="checkbox"590 checked={checkedState[idx]}591 onChange={() => handleCheckboxChange(idx)}592 className="cmd-checkbox"593 />594 <div style={{ flex: 1 }}>595 <code className="cmd-code">{item.addition}</code>596 <div className="cmd-why">597 <MarkdownText>{item.why}</MarkdownText>598 </div>599 </div>600 <CopyButton text={item.addition} />601 </div>602 ))}603 </div>604 );605}606 607export function Improvements({608 qualitative,609}: {610 qualitative: QualitativeData;611}) {612 const { improvements } = qualitative;613 if (!improvements) return null;614 615 const hasFeatureSuggestions =616 (Array.isArray(improvements.Qwen_md_additions) &&617 improvements.Qwen_md_additions.length > 0) ||618 (Array.isArray(improvements.features_to_try) &&619 improvements.features_to_try.length > 0);620 const hasUsagePatterns =621 Array.isArray(improvements.usage_patterns) &&622 improvements.usage_patterns.length > 0;623 624 if (!hasFeatureSuggestions && !hasUsagePatterns) return null;625 626 return (627 <>628 {hasFeatureSuggestions && (629 <>630 <h2631 id="section-features"632 className="text-xl font-semibold text-slate-900 mt-8 mb-4"633 >634 Existing Qwen Code Features to Try635 </h2>636 637 {/* QWEN.md Additions */}638 {Array.isArray(improvements.Qwen_md_additions) &&639 improvements.Qwen_md_additions.length > 0 && (640 <QwenMdAdditionsSection641 additions={improvements.Qwen_md_additions}642 />643 )}644 645 <p className="text-xs text-slate-500 mb-3">646 Just copy this into Qwen Code and it'll set it up for you.647 </p>648 649 {/* Features to Try */}650 <div className="features-section">651 {Array.isArray(improvements.features_to_try) &&652 improvements.features_to_try.map((feat, idx) => (653 <div key={idx} className="feature-card">654 <div className="feature-title">{feat.feature}</div>655 <div className="feature-oneliner">656 <MarkdownText>{feat.one_liner}</MarkdownText>657 </div>658 <div className="feature-why">659 <strong>Why for you:</strong>{' '}660 <MarkdownText>{feat.why_for_you}</MarkdownText>661 </div>662 <div className="feature-examples">663 <div className="feature-example">664 <div className="example-code-row">665 <code className="example-code">666 {feat.example_code}667 </code>668 <CopyButton text={feat.example_code} />669 </div>670 </div>671 </div>672 </div>673 ))}674 </div>675 </>676 )}677 678 {hasUsagePatterns && (679 <>680 <h2681 id="section-patterns"682 className="text-xl font-semibold text-slate-900 mt-8 mb-4"683 >684 New Ways to Use Qwen Code685 </h2>686 <p className="text-xs text-slate-500 mb-3">687 Just copy this into Qwen Code and it'll walk you through it.688 </p>689 690 <div className="patterns-section">691 {Array.isArray(improvements.usage_patterns) &&692 improvements.usage_patterns.map((pat, idx) => (693 <div key={idx} className="pattern-card">694 <div className="pattern-title">{pat.title}</div>695 <div className="pattern-summary">696 <MarkdownText>{pat.suggestion}</MarkdownText>697 </div>698 <div className="pattern-detail">699 <MarkdownText>{pat.detail}</MarkdownText>700 </div>701 <div className="copyable-prompt-section">702 <div className="prompt-label">Paste into Qwen Code:</div>703 <div className="copyable-prompt-row">704 <code className="copyable-prompt">705 {pat.copyable_prompt}706 </code>707 <CopyButton text={pat.copyable_prompt} />708 </div>709 </div>710 </div>711 ))}712 </div>713 </>714 )}715 </>716 );717}718 719export function FutureOpportunities({720 qualitative,721}: {722 qualitative: QualitativeData;723}) {724 const { futureOpportunities } = qualitative;725 if (!futureOpportunities) return null;726 727 return (728 <>729 <h2730 id="section-horizon"731 className="text-xl font-semibold text-slate-900 mt-8 mb-4"732 >733 On the Horizon734 </h2>735 {futureOpportunities.intro && (736 <p className="section-intro">737 <MarkdownText>{futureOpportunities.intro}</MarkdownText>738 </p>739 )}740 741 <div className="horizon-section">742 {Array.isArray(futureOpportunities.opportunities) &&743 futureOpportunities.opportunities.map((opp, idx) => (744 <div key={idx} className="horizon-card">745 <div className="horizon-title">{opp.title}</div>746 <div className="horizon-possible">747 <MarkdownText>{opp.whats_possible}</MarkdownText>748 </div>749 <div className="horizon-tip">750 <strong>Getting started:</strong>{' '}751 <MarkdownText>{opp.how_to_try}</MarkdownText>752 </div>753 <div className="pattern-prompt">754 <div className="prompt-label">Paste into Qwen Code:</div>755 <div756 style={{757 display: 'flex',758 alignItems: 'flex-start',759 gap: '8px',760 }}761 >762 <code style={{ flex: 1 }}>{opp.copyable_prompt}</code>763 <CopyButton text={opp.copyable_prompt} />764 </div>765 </div>766 </div>767 ))}768 </div>769 </>770 );771}772 773export function MemorableMoment({774 qualitative,775}: {776 qualitative: QualitativeData;777}) {778 const { memorableMoment } = qualitative;779 if (!memorableMoment) return null;780 781 return (782 <div className="fun-ending">783 <div className="fun-headline">"{memorableMoment.headline}"</div>784 <div className="fun-detail">785 <MarkdownText>{memorableMoment.detail}</MarkdownText>786 </div>787 </div>788 );789}790 