Ai Code Review Assistant
blockdevopsGitHub PR automated AI code review assistant with AST syntax diff annotations, performance suggestions, and 1-click patch application.
Installation
$pnpm dlx uipkge-ng@latest add ai-code-review-assistant$npx uipkge-ng@latest add ai-code-review-assistant$yarn dlx uipkge-ng@latest add ai-code-review-assistant$bunx uipkge-ng@latest add ai-code-review-assistantManifest:
https://uipkge.dev/r/angular/ai-code-review-assistant.jsonInstalls to:src/app/components/blocks/ai-code-review-assistant/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
DiffLineinterface DiffLine {
id: string
oldNum?: number
newNum?: number
type: 'neutral' | 'deletion' | 'addition' | 'hunk'
text: string
annotationId?: string
}ChatMessageinterface ChatMessage {
id: string
sender: 'ai' | 'user'
author: string
timestamp: string
text: string
codeSnippet?: string
}Theming
CSS custom properties referenced in this item. Override any of them in your :root or per-element to retheme.
--background--border--card--card-foreground--color-ring--foreground--input--primary--primary-hslFiles installed (2)
src/app/components/blocks/ai-code-review-assistant/ai-code-review-assistant.component.ts45.4 kB
// Edit this file to change the sample diff, AI annotations, chat thread and copy. // Patches toggle on click; "Apply All AI Patches" applies both after a short delay. import { ChangeDetectionStrategy, Component, DestroyRef, Input, computed, inject, signal } from '@angular/core' import { Bot, Check, CheckCheck, CircleCheck, CodeXml, Copy, Cpu, FileCodeCorner, GitBranch, GitCommitHorizontal, GitPullRequest, Layers, LucideAngularModule, MessageSquare, RefreshCw, RotateCcw, Send, ShieldCheck, Sparkles, TrendingDown, TrendingUp, X, Zap, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent } from '@/ui/avatar/avatar.component' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiTabsComponent, UiTabsContentComponent, UiTabsListComponent, UiTabsTriggerComponent, } from '@/ui/tabs/tabs.component' export interface DiffLine { id: string oldNum?: number newNum?: number type: 'neutral' | 'deletion' | 'addition' | 'hunk' text: string annotationId?: string } export interface ChatMessage { id: string sender: 'ai' | 'user' author: string timestamp: string text: string codeSnippet?: string } const initialChatMessages: ChatMessage[] = [ { id: 'msg-1', sender: 'ai', author: 'Glia AI Code Reviewer', timestamp: '2 min ago', text: "I completed the AST review for PR #412. Migrating to OKLCH perceptual contrast is a massive win. I've pinpointed 2 optimization opportunities to maximize token efficiency and eliminate 20 DOM variables.", }, { id: 'msg-2', sender: 'user', author: 'Elena Rostova', timestamp: '1 min ago', text: 'Why is `color-mix(in oklab, ... 10%, transparent)` better than declaring separate opacity custom properties?', }, { id: 'msg-3', sender: 'ai', author: 'Glia AI Code Reviewer', timestamp: 'Just now', text: 'Direct OKLab color-mix calculates the alpha composite on the GPU shader pipeline without registering 20 CSS custom properties in the browser style recalculation tree. This cuts ~1.8 KB from DOM memory while keeping luminance completely linear.', codeSnippet: 'color-mix(in oklab, var(--primary) 10%, transparent)', }, ] function buildDiffLines(applied: ReadonlySet<string>): DiffLine[] { return [ { id: 'hunk-1', type: 'hunk', text: '@@ -32,18 +32,24 @@ packages/shared/styles/tailwind.css' }, { id: 'line-32', oldNum: 32, newNum: 32, type: 'neutral', text: '@import "tailwindcss";' }, { id: 'line-33', oldNum: 33, newNum: 33, type: 'neutral', text: '@import "tw-animate-css";' }, { id: 'line-34', oldNum: 34, newNum: 34, type: 'neutral', text: '' }, { id: 'line-35', oldNum: 35, newNum: 35, type: 'neutral', text: '@theme inline {' }, { id: 'line-36', oldNum: 36, newNum: 36, type: 'neutral', text: ' --font-sans: "Inter", system-ui, sans-serif;' }, { id: 'line-37', oldNum: 37, newNum: 37, type: 'neutral', text: ' --font-mono: "DM Mono", monospace;' }, { id: 'line-38', oldNum: 38, type: 'deletion', text: '- --primary-10: color-mix(in srgb, var(--primary) 10%, #ffffff);', }, { id: 'line-39', oldNum: 39, type: 'deletion', text: '- --primary-20: color-mix(in srgb, var(--primary) 20%, #ffffff);', }, { id: 'line-40', oldNum: 40, type: 'deletion', text: '- --primary-hover: hsl(var(--primary-hsl) / 0.85);' }, { id: 'line-41', newNum: 38, type: 'addition', text: '+ --primary-hover: oklch(from var(--primary) calc(l * 0.92) c h);', }, { id: 'line-42', newNum: 39, type: 'addition', text: applied.has('patch-1') ? '+ --primary-accent: color-mix(in oklab, var(--primary) 10%, transparent); /* [Glia AI Patch Applied] */' : '+ --primary-accent: color-mix(in oklab, var(--primary) 15%, transparent);', annotationId: 'patch-1', }, { id: 'line-43', oldNum: 41, newNum: 40, type: 'neutral', text: ' --color-background: var(--background);' }, { id: 'line-44', oldNum: 42, newNum: 41, type: 'neutral', text: ' --color-foreground: var(--foreground);' }, { id: 'line-45', oldNum: 43, type: 'deletion', text: '- --muted-border: rgb(229 231 235 / 0.6);' }, { id: 'line-46', newNum: 42, type: 'addition', text: '+ --muted-border: oklch(0.92 0.005 240 / 0.8);' }, { id: 'line-47', oldNum: 44, newNum: 43, type: 'neutral', text: ' --color-border: var(--border);' }, { id: 'line-48', oldNum: 45, newNum: 44, type: 'neutral', text: ' --color-input: var(--input);' }, { id: 'line-49', oldNum: 46, type: 'deletion', text: '- --ring-offset-width: 2px;' }, { id: 'line-50', newNum: 45, type: 'addition', text: '+ --color-ring: oklch(from var(--primary) l c h / 0.5);' }, { id: 'line-51', newNum: 46, type: 'addition', text: applied.has('patch-2') ? '+ --ring-focus-shadow: 0 0 0 2px var(--color-ring); /* [Glia AI Patch Applied] */' : '+ --ring-focus-shadow: 0 0 0 2px var(--color-ring);', annotationId: 'patch-2', }, { id: 'line-52', oldNum: 47, newNum: 47, type: 'neutral', text: ' --color-card: var(--card);' }, { id: 'line-53', oldNum: 48, newNum: 48, type: 'neutral', text: ' --color-card-foreground: var(--card-foreground);', }, { id: 'line-54', oldNum: 49, newNum: 49, type: 'neutral', text: '}' }, ] } /** * AiCodeReviewAssistant -- the React block, 1:1: a PR header with review status, four * health cards, an annotated unified diff (with 1-click AI patches) in tabs, and an * AI chat thread that opens from the header or the annotations. */ @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-ai-code-review-assistant, [ui-ai-code-review-assistant]', standalone: true, imports: [ LucideAngularModule, UiAvatarComponent, UiAvatarImageComponent, UiAvatarFallbackComponent, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardHeaderComponent, UiCardTitleComponent, UiCardDescriptionComponent, UiCardContentComponent, UiSeparatorComponent, UiTabsComponent, UiTabsListComponent, UiTabsTriggerComponent, UiTabsContentComponent, ], host: { 'data-uipkge': '', 'data-slot': 'ai-code-review-assistant', '[class]': 'hostClass', }, template: ` <!-- Header: Pull Request Info & AI Review Status --> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-header class="pb-4"> <div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between"> <div class="space-y-2"> <!-- PR Badge & Title --> <div class="flex flex-wrap items-center gap-2"> <span ui-badge wrap variant="outline" class="gap-1.5 font-mono text-xs"> <lucide-icon [img]="GitPullRequest" class="text-primary size-3.5" /> <span>PR #{{ prNumber }}</span> </span> <h2 class="text-foreground text-base font-bold tracking-tight sm:text-lg">{{ prTitle }}</h2> </div> <!-- Meta: Author, Branches, Commit --> <div class="text-muted-foreground flex flex-wrap items-center gap-2 text-xs sm:text-sm"> <div class="flex items-center gap-1.5"> <span ui-avatar class="size-5 border"> <ui-avatar-image [src]="authorAvatar" [alt]="authorName" /> <span ui-avatar-fallback class="text-xs">ER</span> </span> <span class="text-foreground font-medium">{{ authorName }}</span> <span class="text-muted-foreground">(@{{ authorHandle }})</span> </div> <ui-separator orientation="vertical" class="h-3.5" /> <div class="flex items-center gap-1.5 font-mono text-xs"> <lucide-icon [img]="GitBranch" class="text-primary size-3.5" /> <span class="text-foreground font-semibold">{{ targetBranch }}</span> <span class="text-muted-foreground">←</span> <span class="text-foreground font-semibold">{{ sourceBranch }}</span> </div> <ui-separator orientation="vertical" class="h-3.5" /> <div class="flex items-center gap-1 font-mono text-xs"> <lucide-icon [img]="GitCommitHorizontal" class="text-muted-foreground size-3.5" /> <span>8f2a49b</span> <span>·</span> <span class="text-success font-medium">+142</span> <span class="text-destructive font-medium">-68</span> </div> </div> </div> <!-- Actions & Review Status --> <div class="flex flex-wrap items-center gap-2.5"> @if (!allPatchesApplied()) { <span ui-badge wrap variant="success" class="border-success/20 bg-success/10 text-success gap-1.5 px-3 py-1 text-xs font-semibold" > <lucide-icon [img]="Sparkles" class="size-3.5 shrink-0" /> <span>Review Completed · 2 Suggestions · 0 Security Flaws</span> </span> } @else { <span ui-badge wrap variant="success" class="border-success/30 bg-success/15 text-success gap-1.5 px-3 py-1 text-xs font-semibold" > <lucide-icon [img]="CheckCheck" class="size-3.5 shrink-0" /> <span>All Patches Applied · Ready to Merge</span> </span> } @if (!allPatchesApplied()) { <button ui-button size="sm" variant="default" class="gap-1.5 text-xs font-medium shadow-xs" [disabled]="isApplyingAll()" (click)="applyAllPatches()" > @if (isApplyingAll()) { <lucide-icon [img]="RefreshCw" class="size-3.5 animate-spin" /> } @else { <lucide-icon [img]="Sparkles" class="size-3.5" /> } <span>{{ isApplyingAll() ? 'Applying Patches...' : 'Apply All AI Patches' }}</span> </button> } @else { <button ui-button size="sm" variant="outline" class="gap-1.5 text-xs font-medium shadow-xs" (click)="resetPatches()" > <lucide-icon [img]="RotateCcw" class="size-3.5" /> <span>Reset State</span> </button> } <button ui-button size="sm" variant="outline" class="gap-1.5 text-xs font-medium shadow-xs" (click)="openChatWithTopic()" > <lucide-icon [img]="MessageSquare" class="size-3.5" /> <span>AI Chat (3)</span> </button> </div> </div> </div> </div> <!-- 4 Code Review Health Cards --> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"> <!-- Card 1: Quality Score --> <div ui-card class="border-border bg-card shadow-xs transition-colors"> <div ui-card-content class="p-4 sm:p-5"> <div class="flex items-center justify-between"> <span class="text-muted-foreground text-xs font-medium">Code Quality Score</span> <div class="bg-success/10 text-success flex size-8 items-center justify-center rounded-md"> <lucide-icon [img]="CircleCheck" class="size-4" /> </div> </div> <div class="mt-3"> <div class="text-foreground text-2xl font-bold tracking-tight">{{ qualityScore() }} / 100</div> <p class="text-muted-foreground mt-1 text-xs"> Clean Architecture · <span class="text-success font-medium">+14 pts</span> vs baseline </p> </div> <div class="text-success mt-3 flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="Check" class="size-3.5" /> <span>0 Anti-patterns · Clean CVA tokens</span> </div> </div> </div> <!-- Card 2: Security & Vulnerability Check --> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-content class="p-4 sm:p-5"> <div class="flex items-center justify-between"> <span class="text-muted-foreground text-xs font-medium">Security & SAST Check</span> <div class="bg-success/10 text-success flex size-8 items-center justify-center rounded-md"> <lucide-icon [img]="ShieldCheck" class="size-4" /> </div> </div> <div class="mt-3"> <div class="text-foreground text-2xl font-bold tracking-tight">0 Vulnerabilities</div> <p class="text-muted-foreground mt-1 text-xs"> 0 Vulnerabilities Detected · <span class="text-success font-medium">Pass</span> </p> </div> <div class="text-success mt-3 flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="Check" class="size-3.5" /> <span>OWASP Top 10 validated · Zero leaks</span> </div> </div> </div> <!-- Card 3: Performance & Bundle Impact --> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-content class="p-4 sm:p-5"> <div class="flex items-center justify-between"> <span class="text-muted-foreground text-xs font-medium">Performance & Bundle Impact</span> <div class="bg-warning/10 text-warning flex size-8 items-center justify-center rounded-md"> <lucide-icon [img]="Zap" class="size-4" /> </div> </div> <div class="mt-3"> <div class="text-foreground text-2xl font-bold tracking-tight">-4.2 KB (-18%)</div> <p class="text-muted-foreground mt-1 text-xs">Bundle Size Reduction · 14.8 KB → 10.6 KB</p> </div> <div class="text-warning mt-3 flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="TrendingDown" class="size-3.5" /> <span>20 CSS variables eliminated from DOM</span> </div> </div> </div> <!-- Card 4: Test Coverage Delta --> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-content class="p-4 sm:p-5"> <div class="flex items-center justify-between"> <span class="text-muted-foreground text-xs font-medium">Test Coverage Delta</span> <div class="bg-info/10 text-info flex size-8 items-center justify-center rounded-md"> <lucide-icon [img]="Layers" class="size-4" /> </div> </div> <div class="mt-3"> <div class="text-foreground text-2xl font-bold tracking-tight">+2.4% Coverage</div> <p class="text-muted-foreground mt-1 text-xs">48 new tests · 94.8% total coverage</p> </div> <div class="text-success mt-3 flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="TrendingUp" class="size-3.5" /> <span>100% token regression suites pass</span> </div> </div> </div> </div> <!-- Annotated Code Diff Reviewer Card --> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-header class="border-border border-b pb-4"> <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"> <div class="space-y-1"> <div class="flex flex-wrap items-center gap-2"> <lucide-icon [img]="FileCodeCorner" class="text-primary size-4 shrink-0" /> <h3 ui-card-title class="text-foreground font-mono text-sm font-semibold sm:text-base"> packages/shared/styles/tailwind.css </h3> <span ui-badge wrap variant="outline" class="font-mono text-xs">CSS / Tailwind v4</span> <span ui-badge wrap variant="secondary" class="font-mono text-xs">+24 -12 lines</span> </div> <p ui-card-description class="text-muted-foreground text-xs"> AST parser inspected 162 declarations · 2 optimization nodes annotated </p> </div> <div class="flex items-center gap-2"> <button ui-button variant="outline" size="sm" class="h-8 gap-1.5 text-xs font-medium shadow-xs" (click)="copyRawDiff()" > @if (copiedDiff()) { <lucide-icon [img]="Check" class="text-success size-3.5" /> } @else { <lucide-icon [img]="Copy" class="size-3.5" /> } <span>{{ copiedDiff() ? 'Copied Raw Diff' : 'Copy Raw Diff' }}</span> </button> </div> </div> </div> <div ui-card-content class="p-4 sm:p-6"> <div ui-tabs [value]="activeTab()" (valueChange)="activeTab.set($event)" class="w-full space-y-4"> <div ui-tabs-list class="grid w-full grid-cols-3 sm:inline-flex sm:w-auto"> <button ui-tabs-trigger value="diff" class="gap-1.5 text-xs"> <lucide-icon [img]="CodeXml" class="size-3.5" /> <span>Unified Code Diff</span> </button> <button ui-tabs-trigger value="rules" class="gap-1.5 text-xs"> <lucide-icon [img]="ShieldCheck" class="size-3.5" /> <span>AST Rule Checks (4)</span> </button> <button ui-tabs-trigger value="impact" class="gap-1.5 text-xs"> <lucide-icon [img]="Cpu" class="size-3.5" /> <span>Token Matrix & Gamut</span> </button> </div> <!-- Tab 1: Visual Unified Code Diff with Inline AI Annotations --> <div ui-tabs-content value="diff" class="space-y-4 focus-visible:outline-none"> <div class="border-border bg-muted/40 flex items-center justify-between rounded-t-md border-x border-t px-3 py-2 text-xs" > <div class="text-muted-foreground flex items-center gap-2"> <span class="text-foreground font-medium">Unified Diff View</span> <span>·</span> <span class="text-success font-medium">+24 additions</span> <span>·</span> <span class="text-destructive font-medium">-12 deletions</span> </div> <span class="text-muted-foreground font-mono">AST Analyzed</span> </div> <div class="border-border bg-muted/20 overflow-x-auto rounded-b-md border font-mono text-xs leading-relaxed" > <div class="max-w-[620px] min-w-full py-2"> @for (line of diffLines(); track line.id) { @if (line.type === 'hunk') { <!-- Hunk Line --> <div class="bg-muted/60 text-muted-foreground px-3 py-1 font-mono text-xs font-semibold select-none" > {{ line.text }} </div> } @else { <!-- Diff Code Row --> <div [class]="rowClass(line)"> <span class="text-muted-foreground/50 w-8 shrink-0 pr-2 text-right select-none">{{ line.oldNum ?? '' }}</span> <span class="text-muted-foreground/50 w-8 shrink-0 pr-3 text-right select-none">{{ line.newNum ?? '' }}</span> <span [class]="prefixClass(line)">{{ prefix(line) }}</span> <span class="pl-1 whitespace-pre">{{ line.text }}</span> </div> } <!-- Inline AI Review Annotation Card #1 (Pinned under line 42) --> @if (line.annotationId === 'patch-1') { <div class="border-warning/40 bg-warning/[0.04] border-warning/30 dark:bg-warning/10] mx-4 my-3 rounded-lg border p-4 text-xs shadow-xs" > <div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between"> <div class="space-y-1.5"> <div class="flex flex-wrap items-center gap-2"> <div class="bg-warning/20 text-warning flex size-6 items-center justify-center rounded-md"> <lucide-icon [img]="Bot" class="size-3.5" /> </div> <span class="text-foreground font-semibold"> Glia AI Code Review · AST Suggestion </span> <span ui-badge wrap variant="warning" class="gap-1 font-semibold"> <lucide-icon [img]="Sparkles" class="size-3" /> <span>Performance Optimization · High Impact</span> </span> <span class="text-muted-foreground text-xs">Confidence 99.4% · AST Node #42</span> </div> <!-- Explanation --> <p class="text-foreground font-sans text-xs leading-relaxed font-medium"> Using <code class="bg-muted text-foreground rounded px-1.5 py-0.5 font-mono" >color-mix(in oklab, var(--primary) 10%, transparent)</code > achieves identical contrast without injecting 20 additional CSS variables into the global DOM. </p> <!-- Code Preview Replacement --> <div class="border-border/60 bg-muted/50 rounded-md border p-2.5 font-mono text-xs"> <div class="text-muted-foreground mb-1 text-xs select-none"> Proposed AST Patch Replacement (Line 39): </div> <div class="text-success font-semibold"> + --primary-accent: color-mix(in oklab, var(--primary) 10%, transparent); </div> </div> </div> <!-- Action Buttons --> <div class="flex shrink-0 flex-wrap items-center gap-2 self-start sm:self-auto"> <button ui-button size="xs" variant="outline" class="h-7 gap-1 text-xs" (click)="copyPatchCode('patch-1', 'color-mix(in oklab, var(--primary) 10%, transparent)')" > @if (copiedPatchId() === 'patch-1') { <lucide-icon [img]="Check" class="text-success size-3" /> } @else { <lucide-icon [img]="Copy" class="size-3" /> } <span>{{ copiedPatchId() === 'patch-1' ? 'Copied' : 'Copy Patch' }}</span> </button> <button ui-button size="xs" variant="outline" class="h-7 gap-1 text-xs" (click)=" openChatWithTopic( 'Explain why OKLab color-mix avoids DOM stylesheet recalculation overhead' ) " > <lucide-icon [img]="MessageSquare" class="size-3" /> <span>Explain in Chat</span> </button> <button ui-button size="xs" [variant]="isApplied('patch-1') ? 'secondary' : 'default'" [class]="applyButtonClass('patch-1')" (click)="togglePatch('patch-1')" > @if (isApplied('patch-1')) { <lucide-icon [img]="Check" class="text-success size-3" /> } @else { <lucide-icon [img]="Zap" class="size-3" /> } <span>{{ isApplied('patch-1') ? 'Patch Applied ✓' : 'Apply Suggestion (1-Click)' }}</span> </button> </div> </div> </div> } <!-- Inline AI Review Annotation Card #2 (Pinned under line 51) --> @if (line.annotationId === 'patch-2') { <div class="border-success/40 bg-success/[0.04] border-success/30 dark:bg-success/10] mx-4 my-3 rounded-lg border p-4 text-xs shadow-xs" > <div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between"> <div class="space-y-1.5"> <div class="flex flex-wrap items-center gap-2"> <div class="bg-success/20 text-success flex size-6 items-center justify-center rounded-md"> <lucide-icon [img]="ShieldCheck" class="size-3.5" /> </div> <span class="text-foreground font-semibold"> Glia AI Code Review · Gamut Engine </span> <span ui-badge wrap variant="success" class="gap-1 font-semibold"> <lucide-icon [img]="CircleCheck" class="size-3" /> <span>Accessibility & Gamut · WCAG AAA Verified</span> </span> <span class="text-muted-foreground text-xs">Confidence 98.7% · AST Node #51</span> </div> <!-- Explanation --> <p class="text-foreground font-sans text-xs leading-relaxed font-medium"> OKLCH relative gamut mapping guarantees 7:1 contrast ratio across both light and dark themes with zero perceptual hue shift on wide-gamut displays. </p> <!-- Code Preview Replacement --> <div class="border-border/60 bg-muted/50 rounded-md border p-2.5 font-mono text-xs"> <div class="text-muted-foreground mb-1 text-xs select-none"> Verified Relative Color Syntax: </div> <div class="text-success font-semibold"> + --ring-focus-shadow: 0 0 0 2px var(--color-ring); </div> </div> </div> <!-- Action Buttons --> <div class="flex shrink-0 flex-wrap items-center gap-2 self-start sm:self-auto"> <button ui-button size="xs" variant="outline" class="h-7 gap-1 text-xs" (click)=" openChatWithTopic( 'How does OKLCH guarantee WCAG AAA contrast without shift on P3 monitors?' ) " > <lucide-icon [img]="MessageSquare" class="size-3" /> <span>Explain in Chat</span> </button> <button ui-button size="xs" [variant]="isApplied('patch-2') ? 'secondary' : 'default'" [class]="applyButtonClass('patch-2')" (click)="togglePatch('patch-2')" > @if (isApplied('patch-2')) { <lucide-icon [img]="Check" class="text-success size-3" /> } @else { <lucide-icon [img]="Zap" class="size-3" /> } <span>{{ isApplied('patch-2') ? 'Patch Applied ✓' : 'Apply Suggestion (1-Click)' }}</span> </button> </div> </div> </div> } } </div> </div> </div> <!-- Tab 2: AST Rule Verification Suite --> <div ui-tabs-content value="rules" class="space-y-4 focus-visible:outline-none"> <div class="grid grid-cols-1 gap-3 sm:grid-cols-2"> <div class="border-border bg-muted/20 space-y-2 rounded-lg border p-4"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2 text-sm font-semibold"> <lucide-icon [img]="ShieldCheck" class="text-success size-4" /> <span>Rule 1: Zero Global DOM Pollution</span> </div> <span ui-badge wrap variant="success" class="text-xs">Pass · Verified</span> </div> <p class="text-muted-foreground text-xs leading-relaxed"> Eliminates 20 static opacity custom properties. All transparency channels are evaluated dynamically in OKLab color space. </p> </div> <div class="border-border bg-muted/20 space-y-2 rounded-lg border p-4"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2 text-sm font-semibold"> <lucide-icon [img]="Zap" class="text-success size-4" /> <span>Rule 2: Perceptual Uniformity (ΔE < 0.8)</span> </div> <span ui-badge wrap variant="success" class="text-xs">Pass · Verified</span> </div> <p class="text-muted-foreground text-xs leading-relaxed"> OKLCH lightness (L) scale provides mathematical linearity. Step changes in lightness feel uniform to human perception across all hues. </p> </div> <div class="border-border bg-muted/20 space-y-2 rounded-lg border p-4"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2 text-sm font-semibold"> <lucide-icon [img]="Cpu" class="text-success size-4" /> <span>Rule 3: PostCSS Lightning Fallbacks</span> </div> <span ui-badge wrap variant="success" class="text-xs">Pass · Verified</span> </div> <p class="text-muted-foreground text-xs leading-relaxed"> Tailwind v4 engine generates automated sRGB fallback values for legacy web clients while streaming raw OKLCH to modern browsers. </p> </div> <div class="border-border bg-muted/20 space-y-2 rounded-lg border p-4"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2 text-sm font-semibold"> <lucide-icon [img]="CircleCheck" class="text-success size-4" /> <span>Rule 4: WCAG 2.2 AAA Contrast Ratios</span> </div> <span ui-badge wrap variant="success" class="text-xs">Pass · 7.1:1 Ratio</span> </div> <p class="text-muted-foreground text-xs leading-relaxed"> Background to foreground contrast exceeds the 7:1 ratio threshold required for government and enterprise accessible software. </p> </div> </div> </div> <!-- Tab 3: Token Matrix & Gamut Breakdown --> <div ui-tabs-content value="impact" class="space-y-4 focus-visible:outline-none"> <div class="border-border overflow-x-auto rounded-lg border"> <div class="bg-muted/40 border-border max-w-[480px] min-w-full border-b px-4 py-3 text-xs font-semibold"> Token Architecture Comparison: Legacy vs. OKLCH Engine </div> <div class="divide-border max-w-[480px] min-w-full divide-y text-xs"> <div class="grid grid-cols-3 p-3 font-medium"> <span class="text-muted-foreground">Evaluation Metric</span> <span class="text-muted-foreground">Legacy HSL / sRGB</span> <span class="text-success font-bold">OKLCH Perceptual Engine</span> </div> <div class="grid grid-cols-3 p-3"> <span class="font-medium">Total Token CSS Bundle</span> <span class="text-muted-foreground font-mono">14.8 KB</span> <span class="text-success font-mono font-bold">10.6 KB (-28.4%)</span> </div> <div class="grid grid-cols-3 p-3"> <span class="font-medium">DOM Custom Properties</span> <span class="text-muted-foreground font-mono">64 variables</span> <span class="text-success font-mono font-bold">44 variables (-31.2%)</span> </div> <div class="grid grid-cols-3 p-3"> <span class="font-medium">Display P3 Wide Gamut</span> <span class="text-destructive">Clipping on 14 colors</span> <span class="text-success font-bold">100% P3 Coverage</span> </div> <div class="grid grid-cols-3 p-3"> <span class="font-medium">Perceptual Uniformity</span> <span class="text-muted-foreground">Non-linear (HSL shift)</span> <span class="text-success font-bold">Mathematically Uniform (LCH)</span> </div> </div> </div> </div> </div> </div> </div> <!-- Interactive AI Review Assistant Chat Panel --> @if (isChatOpen()) { <div ui-card class="border-primary/30 bg-card shadow-md"> <div ui-card-header class="border-border border-b pb-3"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2.5"> <div class="bg-primary/10 text-primary flex size-8 items-center justify-center rounded-md"> <lucide-icon [img]="Bot" class="size-4" /> </div> <div> <h3 ui-card-title class="text-sm font-semibold"> Glia AI Assistant · PR #{{ prNumber }} Review Thread </h3> <p ui-card-description class="text-xs"> Contextual code review dialogue with active AST parser & contrast engine </p> </div> </div> <button ui-button variant="ghost" size="xs" class="size-7 p-0" aria-label="Close chat" (click)="isChatOpen.set(false)" > <lucide-icon [img]="X" class="size-4" /> </button> </div> </div> <div ui-card-content class="space-y-4 p-4"> <!-- Message History --> <div class="max-h-[320px] space-y-3 overflow-y-auto pr-1"> @for (msg of chatMessages(); track msg.id) { <div [class]="messageClass(msg)"> <div class="flex items-center justify-between font-semibold"> <span class="flex items-center gap-1.5"> @if (msg.sender === 'ai') { <lucide-icon [img]="Bot" class="text-primary size-3.5" /> } @else { <span ui-avatar class="size-4 border"> <ui-avatar-image [src]="authorAvatar" /> <span ui-avatar-fallback class="text-xs">ER</span> </span> } <span>{{ msg.author }}</span> </span> <span class="text-muted-foreground text-xs font-normal">{{ msg.timestamp }}</span> </div> <p class="mt-1 leading-relaxed">{{ msg.text }}</p> @if (msg.codeSnippet) { <div class="border-border/80 bg-muted/80 text-foreground mt-2 rounded p-2 font-mono text-xs select-all" > {{ msg.codeSnippet }} </div> } </div> } </div> <!-- Preset Prompt Chips --> <div class="flex flex-wrap items-center gap-1.5 pt-1"> <span class="text-muted-foreground text-xs">Quick ask:</span> <button type="button" class="border-border bg-muted/40 hover:bg-muted text-foreground cursor-pointer rounded-full border px-2.5 py-0.5 text-xs transition-colors" (click)="openChatWithTopic('Why is OKLab preferred over sRGB color space?')" > Why OKLab over sRGB? </button> <button type="button" class="border-border bg-muted/40 hover:bg-muted text-foreground cursor-pointer rounded-full border px-2.5 py-0.5 text-xs transition-colors" (click)="openChatWithTopic('How does this affect browser performance?')" > Browser performance impact? </button> </div> <!-- Chat Input Field --> <form class="flex items-center gap-2 pt-1" (submit)="handleSendMessage($event)"> <input [value]="chatInput()" (input)="onChatInput($event)" type="text" placeholder="Ask Glia AI about this code review or AST transformation..." class="border-input bg-background placeholder:text-muted-foreground focus-visible:ring-ring flex-1 rounded-md border px-3 py-2 text-xs focus-visible:ring-2 focus-visible:outline-none" /> <button ui-button type="submit" size="sm" class="gap-1.5 text-xs"> <lucide-icon [img]="Send" class="size-3.5" /> <span>Send</span> </button> </form> </div> </div> } `, }) export class UiAiCodeReviewAssistantComponent { @Input() prNumber = 412 @Input() prTitle = 'refactor(tokens): migrate to OKLCH perceptual contrast engine' @Input() authorName = 'Elena Rostova' @Input() authorHandle = 'elena-rostova' @Input() authorRole = 'Staff Design Systems Engineer' @Input() authorAvatar = 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=96&h=96&fit=crop&crop=face' @Input() sourceBranch = 'feature/oklch-engine' @Input() targetBranch = 'main' @Input('class') className?: string readonly activeTab = signal('diff') readonly isApplyingAll = signal(false) readonly appliedPatches = signal<ReadonlySet<string>>(new Set()) readonly isChatOpen = signal(false) readonly chatInput = signal('') readonly copiedDiff = signal(false) readonly copiedPatchId = signal<string | null>(null) readonly chatMessages = signal<ChatMessage[]>(initialChatMessages) readonly diffLines = computed(() => buildDiffLines(this.appliedPatches())) readonly allPatchesApplied = computed(() => this.appliedPatches().size === 2) readonly qualityScore = computed(() => (this.allPatchesApplied() ? 99 : 96)) protected readonly Bot = Bot protected readonly Check = Check protected readonly CheckCheck = CheckCheck protected readonly CircleCheck = CircleCheck protected readonly CodeXml = CodeXml protected readonly Copy = Copy protected readonly Cpu = Cpu protected readonly FileCodeCorner = FileCodeCorner protected readonly GitBranch = GitBranch protected readonly GitCommitHorizontal = GitCommitHorizontal protected readonly GitPullRequest = GitPullRequest protected readonly Layers = Layers protected readonly MessageSquare = MessageSquare protected readonly RefreshCw = RefreshCw protected readonly RotateCcw = RotateCcw protected readonly Send = Send protected readonly ShieldCheck = ShieldCheck protected readonly Sparkles = Sparkles protected readonly TrendingDown = TrendingDown protected readonly TrendingUp = TrendingUp protected readonly X = X protected readonly Zap = Zap private readonly timeouts = new Set<ReturnType<typeof setTimeout>>() constructor() { inject(DestroyRef).onDestroy(() => this.timeouts.forEach((t) => clearTimeout(t))) } get hostClass(): string { return cn('block w-full space-y-6', this.className) } isApplied(patchId: string): boolean { return this.appliedPatches().has(patchId) } rowClass(line: DiffLine): string { return cn( 'flex items-center px-3 py-0.5 transition-colors', line.type === 'addition' && 'border-success bg-success/10 text-success border-l-2 font-medium', line.type === 'deletion' && 'border-destructive bg-destructive/10 text-foreground dark:text-foreground border-l-2 font-medium line-through opacity-85', line.type === 'neutral' && 'text-muted-foreground border-l-2 border-transparent', ) } prefixClass(line: DiffLine): string { return cn( 'w-4 shrink-0 text-center font-bold select-none', line.type === 'addition' && 'text-success', line.type === 'deletion' && 'text-destructive', line.type === 'neutral' && 'text-transparent', ) } prefix(line: DiffLine): string { return line.type === 'addition' ? '+' : line.type === 'deletion' ? '-' : ' ' } applyButtonClass(patchId: string): string { return cn( 'h-7 gap-1 text-xs font-medium shadow-xs', this.isApplied(patchId) && 'border-success/30 text-success font-semibold', ) } messageClass(msg: ChatMessage): string { return cn( 'flex flex-col gap-1 rounded-lg p-3 text-xs', msg.sender === 'ai' ? 'border-border/60 bg-muted/40 mr-8 border' : 'bg-primary/10 text-foreground border-primary/20 ml-8 border', ) } togglePatch(patchId: string): void { this.appliedPatches.update((prev) => { const next = new Set(prev) if (next.has(patchId)) next.delete(patchId) else next.add(patchId) return next }) } applyAllPatches(): void { if (this.isApplyingAll()) return this.isApplyingAll.set(true) this.later(() => { this.appliedPatches.set(new Set(['patch-1', 'patch-2'])) this.isApplyingAll.set(false) }, 600) } resetPatches(): void { this.appliedPatches.set(new Set()) } openChatWithTopic(topicText?: string): void { this.isChatOpen.set(true) if (!topicText) return const userMsg: ChatMessage = { id: `user-${Date.now()}`, sender: 'user', author: 'Elena Rostova', timestamp: 'Just now', text: topicText, } this.chatMessages.update((prev) => [...prev, userMsg]) this.later(() => { const aiMsg: ChatMessage = { id: `ai-${Date.now()}`, sender: 'ai', author: 'Glia AI Code Reviewer', timestamp: 'Just now', text: `Regarding "${topicText}": Our AST lint engine verified that OKLCH relative color syntax produces 7:1 contrast on dark surfaces with zero gamut clipping on Apple Display P3 panels.`, } this.chatMessages.update((prev) => [...prev, aiMsg]) }, 450) } onChatInput(e: Event): void { this.chatInput.set((e.target as HTMLInputElement).value) } handleSendMessage(e: Event): void { e.preventDefault() const text = this.chatInput().trim() if (!text) return const userMsg: ChatMessage = { id: `msg-${Date.now()}`, sender: 'user', author: 'Elena Rostova', timestamp: 'Just now', text, } this.chatMessages.update((prev) => [...prev, userMsg]) this.chatInput.set('') this.later(() => { const aiMsg: ChatMessage = { id: `ai-${Date.now()}`, sender: 'ai', author: 'Glia AI Code Reviewer', timestamp: 'Just now', text: 'Validated against UIPKGE craft standards. The suggested AST refactor is fully backward-compatible with PostCSS fallback polyfills.', } this.chatMessages.update((prev) => [...prev, aiMsg]) }, 500) } copyRawDiff(): void { const rawText = this.diffLines() .map((l) => `${l.text}`) .join('\n') void navigator.clipboard?.writeText(rawText) this.copiedDiff.set(true) this.later(() => this.copiedDiff.set(false), 2000) } copyPatchCode(id: string, code: string): void { void navigator.clipboard?.writeText(code) this.copiedPatchId.set(id) this.later(() => this.copiedPatchId.set(null), 2000) } private later(fn: () => void, ms: number): void { const t = setTimeout(() => { this.timeouts.delete(t) fn() }, ms) this.timeouts.add(t) } }src/app/components/blocks/ai-code-review-assistant/index.ts0.1 kB
export { UiAiCodeReviewAssistantComponent, type DiffLine, type ChatMessage } from './ai-code-review-assistant.component'
Raw manifest:https://uipkge.dev/r/angular/ai-code-review-assistant.json