Ai and feedback#1
Conversation
…ommendations and rationales
…th scoring, and dynamic match weighting system
…engine, and update UI components with viewer session awareness.
There was a problem hiding this comment.
Pull request overview
Implements a feedback loop around AI matching by introducing an in-memory relationship/feedback store, profile “strength” scoring, and weight overrides derived from completed sessions; wires this into new API routes and UI components (AI match cards + dashboard).
Changes:
- Added relationship/feedback data model (
store.ts) plus strength and weight calculation utilities. - Added API routes for matches, relationships, feedback submission, and profile strength.
- Added UI: AI match cards, relationship dashboard/cards, feedback widget, and a hook to fetch AI matches; added Node test runner setup + tests for deterministic matching/exclusions.
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/match-engine.test.ts | Adds deterministic matcher + exclusion tests. |
| src/lib/store.ts | In-memory relationships/feedback store with seeded demo data. |
| src/lib/strength-calculator.ts | Computes profile strength score and label. |
| src/lib/weight-engine.ts | Computes dimension weight multipliers from historical feedback. |
| src/lib/match-engine.ts | Adds AI matching via Ollama with deterministic fallback + weighting/context support. |
| src/app/api/match/route.ts | Match endpoint that applies exclusion + weight overrides + prompt context note. |
| src/app/api/relationships/route.ts | Relationships GET/POST endpoints. |
| src/app/api/feedback/[id]/route.ts | Feedback submission endpoint for a relationship side. |
| src/app/api/profile-strength/[id]/route.ts | Profile strength endpoint. |
| src/hooks/use-ai-matches.ts | Client hook to fetch matches for a viewer. |
| src/components/audience-browse-page.tsx | Switches “AI Recommended” section to show AI picks when available. |
| src/components/ai-match-card.tsx | Displays an AI-picked profile + rationale tooltip and creates relationships. |
| src/components/rationale-card.tsx | Renders score breakdown + flags for a match. |
| src/components/relationship-dashboard.tsx | Fetches and displays strength + relationships list. |
| src/components/relationship-card.tsx | Renders a relationship row with optional feedback flow. |
| src/components/feedback-widget.tsx | Star rating + optional comment submission UI. |
| src/components/profile-strength-badge.tsx | Renders strength score bar + label. |
| src/app/dashboard/page.tsx | Adds /dashboard page to display relationship dashboard. |
| src/app/page.tsx | Adds a Dashboard link to the home header. |
| package.json | Adds a test script using Node’s test runner + jiti. |
| docs/superpowers/plans/2026-05-16-feedback-loop.md | Adds implementation plan doc for the feedback loop work. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| function applyWeights( | ||
| base: MatchBreakdown, | ||
| weights: WeightOverrides | ||
| ): MatchBreakdown { | ||
| return { | ||
| domain_fit: clamp(Math.round(base.domain_fit * weights.domain_fit), 0, DIMENSION_MAXES.domain_fit), | ||
| stage_fit: clamp(Math.round(base.stage_fit * weights.stage_fit), 0, DIMENSION_MAXES.stage_fit), | ||
| geography: clamp(Math.round(base.geography * weights.geography), 0, DIMENSION_MAXES.geography), | ||
| history: clamp(Math.round(base.history * weights.history), 0, DIMENSION_MAXES.history), | ||
| availability: clamp(Math.round(base.availability * weights.availability), 0, DIMENSION_MAXES.availability), | ||
| }; |
There was a problem hiding this comment.
Addressed in 68cbc6c. The deterministic matcher now clamps weighted dimensions against weighted maxes, so boosts above 1.0 can increase the breakdown, and the final returned score is normalized back to /100. I also added a regression test for the boosted-weight path.
| import type { Relationship } from "./store"; | ||
| import type { WeightOverrides } from "./weight-engine"; | ||
| import { DEFAULT_WEIGHTS } from "./weight-engine"; | ||
|
|
||
| export type MatchBreakdown = { | ||
| domain_fit: number; | ||
| stage_fit: number; | ||
| geography: number; | ||
| history: number; | ||
| availability: number; | ||
| }; |
| async function submit() { | ||
| if (selected === 0) return; | ||
| setSubmitting(true); | ||
|
|
||
| await fetch(`/api/feedback/${relationshipId}`, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ side, rating: selected, comment }), | ||
| }); | ||
|
|
||
| setSubmitting(false); | ||
| setDone(true); | ||
| onSubmitted?.(selected); | ||
| } |
| {[1, 2, 3, 4, 5].map((star) => ( | ||
| <button | ||
| key={star} | ||
| type="button" | ||
| className={`text-2xl transition-transform hover:scale-110 ${ | ||
| star <= (hovered || selected) ? "text-[var(--color-google-yellow)]" : "text-muted" | ||
| }`} | ||
| onMouseEnter={() => setHovered(star)} | ||
| onMouseLeave={() => setHovered(0)} | ||
| onClick={() => setSelected(star)} | ||
| > | ||
| ★ | ||
| </button> |
| export function ProfileStrengthBadge({ strength, size = "md" }: ProfileStrengthBadgeProps) { | ||
| const pct = strength.total; | ||
| const isLg = size === "lg"; | ||
|
|
||
| return ( | ||
| <div className={`space-y-${isLg ? "4" : "2"}`}> | ||
| <div className="flex items-center justify-between"> | ||
| <span className="text-xs font-semibold uppercase tracking-[0.2em] text-muted"> | ||
| Profile Strength | ||
| </span> | ||
| <span className={`font-bold tabular-nums ${labelColors[strength.label]} ${isLg ? "text-2xl" : "text-base"}`}> | ||
| {strength.total} |
| { params }: { params: Promise<{ id: string }> } | ||
| ) { | ||
| const { id } = await params; |
| { params }: { params: Promise<{ id: string }> } | ||
| ) { | ||
| const { id } = await params; |
| useEffect(() => { | ||
| let cancelled = false; | ||
|
|
||
| fetch("/api/match", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ viewerId }), | ||
| }) | ||
| .then((r) => { | ||
| if (!r.ok) throw new Error(`${r.status}`); | ||
| return r.json(); | ||
| }) | ||
| .then((data) => { | ||
| if (!cancelled) setMatches(data.matches ?? []); | ||
| }) | ||
| .catch((err: unknown) => { |
| const response = await fetch("/api/relationships", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| viewerId, | ||
| matchedId: match.id, | ||
| matchScore: match.score, | ||
| rationale: match.rationale, | ||
| breakdown: match.breakdown, | ||
| }), | ||
| }); | ||
| if (!response.ok) { | ||
| setConnecting(false); | ||
| return; | ||
| } | ||
| setConnecting(false); | ||
| setConnected(true); |
| const maxes: Record<keyof MatchBreakdown, number> = { | ||
| domain_fit: 30, stage_fit: 25, geography: 15, history: 20, availability: 10, | ||
| }; | ||
|
|
||
| const overrides = { ...DEFAULT_WEIGHTS }; | ||
|
|
||
| for (const key of keys) { | ||
| const avgAll = avg(completed.map((r) => (r.breakdown![key] / maxes[key]) * 100)); | ||
| const avgHigh = avg(highRated.map((r) => (r.breakdown![key] / maxes[key]) * 100)); |
Agent-Logs-Url: https://github.com/mewHacks/myhack26/sessions/6c6fc861-aa69-457c-beb0-4f4baa7a4f5a Co-authored-by: mewHacks <201765265+mewHacks@users.noreply.github.com>
|
Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
No description provided.