3v324v23 commited on
Commit
55261e4
·
1 Parent(s): b0144a1

FEAT: Replace Streamlit with Next.js dashboard — 8 components, dark diff view, proper API routing

Browse files
Dockerfile CHANGED
@@ -2,25 +2,36 @@ FROM python:3.11-slim
2
 
3
  WORKDIR /app
4
 
5
- # Install system dependencies
6
  RUN apt-get update && apt-get install -y \
7
  bash \
8
  nginx \
 
 
 
9
  && rm -rf /var/lib/apt/lists/*
10
 
11
  # Copy Nginx configuration
12
  COPY nginx.conf /etc/nginx/nginx.conf
13
 
 
14
  COPY requirements.txt .
15
  RUN pip install --no-cache-dir -r requirements.txt
16
 
 
 
 
 
 
17
  COPY . .
18
 
 
 
 
19
  # Ensure start.sh is executable
20
  RUN chmod +x start.sh
21
 
22
  ENV PYTHONPATH=/app
23
  EXPOSE 7860
24
 
25
- # We use start.sh to launch FastAPI, Streamlit, and Nginx
26
  CMD ["./start.sh"]
 
2
 
3
  WORKDIR /app
4
 
5
+ # Install system dependencies + Node.js 18
6
  RUN apt-get update && apt-get install -y \
7
  bash \
8
  nginx \
9
+ curl \
10
+ && curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
11
+ && apt-get install -y nodejs \
12
  && rm -rf /var/lib/apt/lists/*
13
 
14
  # Copy Nginx configuration
15
  COPY nginx.conf /etc/nginx/nginx.conf
16
 
17
+ # Install Python dependencies
18
  COPY requirements.txt .
19
  RUN pip install --no-cache-dir -r requirements.txt
20
 
21
+ # Install Next.js dependencies and build
22
+ COPY pr_review_dashboard/package.json pr_review_dashboard/package-lock.json ./pr_review_dashboard/
23
+ RUN cd pr_review_dashboard && npm ci --production=false
24
+
25
+ # Copy everything
26
  COPY . .
27
 
28
+ # Build the Next.js frontend
29
+ RUN cd pr_review_dashboard && npm run build
30
+
31
  # Ensure start.sh is executable
32
  RUN chmod +x start.sh
33
 
34
  ENV PYTHONPATH=/app
35
  EXPOSE 7860
36
 
 
37
  CMD ["./start.sh"]
nginx.conf CHANGED
@@ -19,7 +19,7 @@ http {
19
  listen 7860;
20
  server_name localhost;
21
 
22
- # 1. OpenEnv API Endpoints (FastAPI on Port 8000)
23
  location /reset {
24
  proxy_pass http://localhost:8000/reset;
25
  proxy_set_header Host $host;
@@ -38,26 +38,25 @@ http {
38
  proxy_set_header X-Real-IP $remote_addr;
39
  }
40
 
41
- # 2. Streamlit WebSocket (Port 8501)
42
- location /_stcore/stream {
43
- proxy_pass http://localhost:8501/_stcore/stream;
44
- proxy_http_version 1.1;
45
- proxy_set_header Upgrade $http_upgrade;
46
- proxy_set_header Connection "upgrade";
 
 
47
  proxy_set_header Host $host;
48
  proxy_set_header X-Real-IP $remote_addr;
49
- proxy_read_timeout 86400;
50
  }
51
 
52
- # 3. Streamlit UI (Port 8501)
53
  location / {
54
- proxy_pass http://localhost:8501;
55
  proxy_set_header Host $host;
56
  proxy_set_header X-Real-IP $remote_addr;
57
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
58
  proxy_set_header X-Forwarded-Proto $scheme;
59
-
60
- # Additional Streamlit settings
61
  proxy_http_version 1.1;
62
  proxy_set_header Upgrade $http_upgrade;
63
  proxy_set_header Connection $connection_upgrade;
 
19
  listen 7860;
20
  server_name localhost;
21
 
22
+ # 1. FastAPI Backend API routes
23
  location /reset {
24
  proxy_pass http://localhost:8000/reset;
25
  proxy_set_header Host $host;
 
38
  proxy_set_header X-Real-IP $remote_addr;
39
  }
40
 
41
+ location /state {
42
+ proxy_pass http://localhost:8000/state;
43
+ proxy_set_header Host $host;
44
+ proxy_set_header X-Real-IP $remote_addr;
45
+ }
46
+
47
+ location /config/custom {
48
+ proxy_pass http://localhost:8000/config/custom;
49
  proxy_set_header Host $host;
50
  proxy_set_header X-Real-IP $remote_addr;
 
51
  }
52
 
53
+ # 2. Next.js Frontend (port 3000)
54
  location / {
55
+ proxy_pass http://localhost:3000;
56
  proxy_set_header Host $host;
57
  proxy_set_header X-Real-IP $remote_addr;
58
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
59
  proxy_set_header X-Forwarded-Proto $scheme;
 
 
60
  proxy_http_version 1.1;
61
  proxy_set_header Upgrade $http_upgrade;
62
  proxy_set_header Connection $connection_upgrade;
pr_review_dashboard/.gitignore ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # env files (can opt-in for committing if needed)
34
+ .env*
35
+
36
+ # vercel
37
+ .vercel
38
+
39
+ # typescript
40
+ *.tsbuildinfo
41
+ next-env.d.ts
pr_review_dashboard/AGENTS.md ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ <!-- BEGIN:nextjs-agent-rules -->
2
+ # This is NOT the Next.js you know
3
+
4
+ This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
5
+ <!-- END:nextjs-agent-rules -->
pr_review_dashboard/CLAUDE.md ADDED
@@ -0,0 +1 @@
 
 
1
+ @AGENTS.md
pr_review_dashboard/README.md ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
pr_review_dashboard/app/api/agent/route.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import OpenAI from "openai";
2
+
3
+ export async function POST(request) {
4
+ try {
5
+ const { observation, modelId, apiUrl, apiKey } = await request.json();
6
+
7
+ if (!apiKey || !modelId || !apiUrl) {
8
+ return Response.json({ decision: "error", comment: "Missing API credentials." }, { status: 400 });
9
+ }
10
+
11
+ const client = new OpenAI({ baseURL: apiUrl, apiKey: apiKey });
12
+
13
+ const systemPrompt =
14
+ 'You are a senior software engineer performing a pull request code review.\n' +
15
+ 'Respond with ONLY this JSON (no markdown, no extra text):\n' +
16
+ '{"decision": "approve|request_changes|escalate", "comment": "your review"}';
17
+
18
+ const historyLines = (observation.review_history || [])
19
+ .map(h => `${h.role.toUpperCase()}: ${h.content}`)
20
+ .join("\n") || "None yet.";
21
+
22
+ const userPrompt =
23
+ `PR Title: ${observation.pr_title || "N/A"}\n` +
24
+ `PR Description: ${observation.pr_description || "N/A"}\n\n` +
25
+ `Diff:\n${observation.diff || "No diff"}\n\n` +
26
+ `Review History:\n${historyLines}\n\n` +
27
+ `Author's latest response: ${observation.author_response || "N/A"}\n\n` +
28
+ `Submit your review decision as JSON:`;
29
+
30
+ const resp = await client.chat.completions.create({
31
+ model: modelId,
32
+ messages: [
33
+ { role: "system", content: systemPrompt },
34
+ { role: "user", content: userPrompt },
35
+ ],
36
+ max_tokens: 500,
37
+ temperature: 0.1,
38
+ });
39
+
40
+ let raw = resp.choices[0].message.content.trim();
41
+ if (raw.includes("```json")) raw = raw.split("```json")[1].split("```")[0];
42
+ else if (raw.includes("```")) raw = raw.split("```")[1].split("```")[0];
43
+ raw = raw.trim();
44
+
45
+ const parsed = JSON.parse(raw);
46
+ if (!parsed.decision || !parsed.comment) {
47
+ return Response.json({ decision: "error", comment: "Model returned invalid format." }, { status: 200 });
48
+ }
49
+
50
+ return Response.json(parsed);
51
+ } catch (e) {
52
+ return Response.json({ decision: "error", comment: `API Error: ${e.message}` }, { status: 200 });
53
+ }
54
+ }
pr_review_dashboard/app/favicon.ico ADDED
pr_review_dashboard/app/globals.css ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ═══════════════════════════════════════════════════════════
2
+ PR Review Dashboard — Global Styles
3
+ Ported from user's custom HTML design
4
+ ═══════════════════════════════════════════════════════════ */
5
+
6
+ :root {
7
+ --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
8
+ --font-mono: 'Fira Code', 'SF Mono', 'Cascadia Code', monospace;
9
+ --color-background-primary: #ffffff;
10
+ --color-background-secondary: #f6f8fa;
11
+ --color-background-tertiary: #eaeef2;
12
+ --color-text-primary: #1f2328;
13
+ --color-text-secondary: #656d76;
14
+ --color-text-tertiary: #8b949e;
15
+ --color-border-secondary: #d0d7de;
16
+ --color-border-tertiary: #d8dee4;
17
+ --border-radius-md: 6px;
18
+ --border-radius-lg: 10px;
19
+ }
20
+
21
+ * { box-sizing: border-box; margin: 0; padding: 0; }
22
+ body { font-family: var(--font-sans); background: var(--color-background-secondary); color: var(--color-text-primary); }
23
+
24
+ /* ── Dashboard Grid ── */
25
+ .dash {
26
+ display: grid;
27
+ grid-template-columns: 240px 1fr;
28
+ min-height: 100vh;
29
+ gap: 0;
30
+ background: var(--color-background-primary);
31
+ }
32
+
33
+ /* ── Sidebar ── */
34
+ .sidebar {
35
+ background: var(--color-background-secondary);
36
+ border-right: 0.5px solid var(--color-border-tertiary);
37
+ padding: 16px;
38
+ display: flex;
39
+ flex-direction: column;
40
+ gap: 14px;
41
+ overflow-y: auto;
42
+ }
43
+ .sidebar-label {
44
+ font-size: 11px;
45
+ font-weight: 500;
46
+ color: var(--color-text-tertiary);
47
+ text-transform: uppercase;
48
+ letter-spacing: .06em;
49
+ margin-bottom: 4px;
50
+ }
51
+ .sidebar select, .sidebar input, .sidebar textarea {
52
+ width: 100%;
53
+ font-size: 13px;
54
+ padding: 7px 10px;
55
+ background: var(--color-background-primary);
56
+ border: 0.5px solid var(--color-border-secondary);
57
+ border-radius: var(--border-radius-md);
58
+ color: var(--color-text-primary);
59
+ font-family: var(--font-sans);
60
+ }
61
+ .sidebar textarea { resize: vertical; min-height: 60px; }
62
+ .init-btn {
63
+ width: 100%;
64
+ padding: 9px;
65
+ font-size: 13px;
66
+ font-weight: 500;
67
+ background: var(--color-background-primary);
68
+ border: 0.5px solid var(--color-border-secondary);
69
+ border-radius: var(--border-radius-md);
70
+ color: var(--color-text-primary);
71
+ cursor: pointer;
72
+ transition: background .15s;
73
+ font-family: var(--font-sans);
74
+ }
75
+ .init-btn:hover { background: var(--color-background-tertiary); }
76
+ .init-btn.active { background: #1a7f3c; color: #fff; border-color: #1a7f3c; }
77
+ .init-btn:disabled { opacity: 0.5; cursor: not-allowed; }
78
+
79
+ /* ── Main Area ── */
80
+ .main {
81
+ display: flex;
82
+ flex-direction: column;
83
+ gap: 0;
84
+ overflow: hidden;
85
+ }
86
+
87
+ /* ── Top Bar ── */
88
+ .topbar {
89
+ padding: 14px 18px;
90
+ border-bottom: 0.5px solid var(--color-border-tertiary);
91
+ display: flex;
92
+ align-items: center;
93
+ justify-content: space-between;
94
+ }
95
+ .pr-title { font-size: 15px; font-weight: 500; color: var(--color-text-primary); }
96
+ .pr-sub { font-size: 12px; color: var(--color-text-secondary); margin-top: 2px; }
97
+
98
+ /* ── Badges ── */
99
+ .badge {
100
+ font-size: 11px;
101
+ font-weight: 500;
102
+ padding: 4px 10px;
103
+ border-radius: 20px;
104
+ letter-spacing: .02em;
105
+ white-space: nowrap;
106
+ }
107
+ .badge.approve { background: #d4edda; color: #1a7f3c; }
108
+ .badge.request { background: #fde8e8; color: #c0392b; }
109
+ .badge.escalate { background: #fef3cd; color: #856404; }
110
+ .badge.running { background: var(--color-background-secondary); color: var(--color-text-secondary); }
111
+ .badge.idle { background: var(--color-background-secondary); color: var(--color-text-tertiary); }
112
+
113
+ /* ── Metric Cards ── */
114
+ .metrics {
115
+ display: grid;
116
+ grid-template-columns: repeat(3, 1fr);
117
+ gap: 10px;
118
+ padding: 14px 18px;
119
+ border-bottom: 0.5px solid var(--color-border-tertiary);
120
+ }
121
+ .metric-card {
122
+ background: var(--color-background-secondary);
123
+ border-radius: var(--border-radius-md);
124
+ padding: 10px 14px;
125
+ }
126
+ .metric-card .m-label { font-size: 11px; color: var(--color-text-tertiary); margin-bottom: 4px; }
127
+ .metric-card .m-val { font-size: 22px; font-weight: 500; color: var(--color-text-primary); line-height: 1; }
128
+ .metric-card .m-sub { font-size: 11px; color: var(--color-text-secondary); margin-top: 3px; }
129
+ .progress-bar { height: 4px; background: var(--color-border-tertiary); border-radius: 2px; overflow: hidden; margin-top: 6px; }
130
+ .progress-fill { height: 100%; border-radius: 2px; transition: width .5s ease; background: #1a7f3c; }
131
+
132
+ /* ── Tabs ── */
133
+ .tabs {
134
+ display: flex;
135
+ gap: 0;
136
+ padding: 0 18px;
137
+ border-bottom: 0.5px solid var(--color-border-tertiary);
138
+ }
139
+ .tab {
140
+ font-size: 12px;
141
+ font-weight: 500;
142
+ padding: 9px 14px;
143
+ cursor: pointer;
144
+ color: var(--color-text-secondary);
145
+ border-bottom: 2px solid transparent;
146
+ transition: color .15s;
147
+ user-select: none;
148
+ }
149
+ .tab:hover { color: var(--color-text-primary); }
150
+ .tab.active { color: var(--color-text-primary); border-bottom-color: var(--color-text-primary); }
151
+
152
+ /* ── Content ── */
153
+ .content {
154
+ flex: 1;
155
+ overflow: auto;
156
+ padding: 16px 18px;
157
+ display: flex;
158
+ flex-direction: column;
159
+ gap: 14px;
160
+ }
161
+
162
+ /* ── Diff View (Dark theme) ── */
163
+ .diff-box {
164
+ background: #0d1117;
165
+ border-radius: var(--border-radius-md);
166
+ overflow: hidden;
167
+ border: 0.5px solid #30363d;
168
+ }
169
+ .diff-header {
170
+ background: #161b22;
171
+ padding: 8px 14px;
172
+ font-size: 11px;
173
+ color: #8b949e;
174
+ font-family: var(--font-mono);
175
+ border-bottom: 0.5px solid #30363d;
176
+ display: flex;
177
+ justify-content: space-between;
178
+ }
179
+ .diff-body {
180
+ padding: 4px 0;
181
+ font-family: var(--font-mono);
182
+ font-size: 12px;
183
+ line-height: 1.7;
184
+ }
185
+ .diff-line {
186
+ padding: 1px 14px;
187
+ display: flex;
188
+ gap: 10px;
189
+ color: #e6edf3;
190
+ }
191
+ .diff-line .ln {
192
+ color: #484f58;
193
+ min-width: 28px;
194
+ text-align: right;
195
+ user-select: none;
196
+ }
197
+ .diff-line.add { background: rgba(46,160,67,.15); color: #aff5b4; }
198
+ .diff-line.add .ln { color: rgba(46,160,67,.5); }
199
+ .diff-line.del { background: rgba(248,81,73,.15); color: #ffa198; }
200
+ .diff-line.del .ln { color: rgba(248,81,73,.5); }
201
+ .diff-line.meta { color: #8b949e; background: #161b22; }
202
+
203
+ /* ── Chat Timeline ── */
204
+ .chat-thread { display: flex; flex-direction: column; gap: 12px; }
205
+ .chat-msg { display: flex; gap: 10px; align-items: flex-start; }
206
+ .chat-msg.author { flex-direction: row-reverse; }
207
+ .avatar {
208
+ width: 30px; height: 30px; border-radius: 50%;
209
+ display: flex; align-items: center; justify-content: center;
210
+ font-size: 11px; font-weight: 500; flex-shrink: 0;
211
+ }
212
+ .avatar.reviewer { background: #E6F1FB; color: #185FA5; }
213
+ .avatar.author { background: #EAF3DE; color: #3B6D11; }
214
+ .bubble {
215
+ background: var(--color-background-secondary);
216
+ border: 0.5px solid var(--color-border-tertiary);
217
+ border-radius: var(--border-radius-lg);
218
+ padding: 10px 14px;
219
+ font-size: 13px;
220
+ color: var(--color-text-primary);
221
+ max-width: 80%;
222
+ line-height: 1.55;
223
+ }
224
+ .bubble.reviewer { border-radius: 4px var(--border-radius-lg) var(--border-radius-lg) var(--border-radius-lg); }
225
+ .bubble.author { background: #F0F7FF; border-radius: var(--border-radius-lg) 4px var(--border-radius-lg) var(--border-radius-lg); }
226
+ .chat-meta { font-size: 10px; color: var(--color-text-tertiary); margin-top: 3px; }
227
+
228
+ /* ── Manual Override ── */
229
+ .manual-textarea {
230
+ width: 100%;
231
+ min-height: 80px;
232
+ font-size: 13px;
233
+ padding: 10px;
234
+ background: var(--color-background-secondary);
235
+ border: 0.5px solid var(--color-border-secondary);
236
+ border-radius: var(--border-radius-md);
237
+ color: var(--color-text-primary);
238
+ resize: vertical;
239
+ font-family: var(--font-sans);
240
+ }
241
+ .manual-actions { display: flex; gap: 8px; margin-top: 8px; }
242
+
243
+ /* ── Log Box ── */
244
+ .log-box {
245
+ background: var(--color-background-secondary);
246
+ border: 0.5px solid var(--color-border-tertiary);
247
+ border-radius: var(--border-radius-md);
248
+ overflow: hidden;
249
+ }
250
+ .log-toggle {
251
+ padding: 10px 14px;
252
+ font-size: 12px;
253
+ cursor: pointer;
254
+ color: var(--color-text-secondary);
255
+ display: flex;
256
+ justify-content: space-between;
257
+ align-items: center;
258
+ user-select: none;
259
+ }
260
+ .log-toggle:hover { background: var(--color-background-tertiary); }
261
+ .log-content {
262
+ padding: 12px 14px;
263
+ font-family: var(--font-mono);
264
+ font-size: 11px;
265
+ color: var(--color-text-secondary);
266
+ border-top: 0.5px solid var(--color-border-tertiary);
267
+ line-height: 1.8;
268
+ white-space: pre-wrap;
269
+ }
270
+
271
+ /* ── Reward Chart ── */
272
+ .reward-chart { padding: 4px 0 8px; }
273
+ .chart-row { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
274
+ .chart-label { font-size: 11px; color: var(--color-text-tertiary); min-width: 40px; text-align: right; }
275
+ .chart-bar-wrap { flex: 1; height: 12px; background: var(--color-background-secondary); border-radius: 2px; overflow: hidden; }
276
+ .chart-bar { height: 100%; border-radius: 2px; transition: width .6s ease; }
277
+ .chart-val { font-size: 11px; color: var(--color-text-secondary); min-width: 28px; }
278
+
279
+ /* ── Utilities ── */
280
+ .section-head {
281
+ font-size: 11px; font-weight: 500; color: var(--color-text-tertiary);
282
+ text-transform: uppercase; letter-spacing: .06em;
283
+ }
284
+ .sep { height: 0.5px; background: var(--color-border-tertiary); }
285
+ .pulse { animation: pulse 1.8s ease-in-out infinite; }
286
+ @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .45; } }
287
+ .thinking { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--color-text-secondary); padding: 4px 0; }
288
+ .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--color-text-tertiary); }
289
+ .dot:nth-child(1) { animation: blink 1.2s .0s infinite; }
290
+ .dot:nth-child(2) { animation: blink 1.2s .2s infinite; }
291
+ .dot:nth-child(3) { animation: blink 1.2s .4s infinite; }
292
+ @keyframes blink { 0%,80%,100% { opacity: .2; } 40% { opacity: 1; } }
293
+
294
+ /* ── Error/Status messages ── */
295
+ .status-msg {
296
+ padding: 8px 12px;
297
+ border-radius: var(--border-radius-md);
298
+ font-size: 12px;
299
+ font-weight: 500;
300
+ }
301
+ .status-msg.error { background: #fde8e8; color: #c0392b; }
302
+ .status-msg.success { background: #d4edda; color: #1a7f3c; }
303
+ .status-msg.info { background: #E6F1FB; color: #185FA5; }
pr_review_dashboard/app/layout.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import "./globals.css";
2
+
3
+ export const metadata = {
4
+ title: "PR Review Command Center",
5
+ description: "AI-powered code review negotiation environment",
6
+ };
7
+
8
+ export default function RootLayout({ children }) {
9
+ return (
10
+ <html lang="en">
11
+ <head>
12
+ <link
13
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Fira+Code:wght@400;500&display=swap"
14
+ rel="stylesheet"
15
+ />
16
+ </head>
17
+ <body>{children}</body>
18
+ </html>
19
+ );
20
+ }
pr_review_dashboard/app/page.js ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { useState, useCallback } from "react";
3
+ import Sidebar from "@/components/Sidebar";
4
+ import TopBar from "@/components/TopBar";
5
+ import MetricCards from "@/components/MetricCards";
6
+ import TabBar from "@/components/TabBar";
7
+ import DiffView from "@/components/DiffView";
8
+ import Timeline from "@/components/Timeline";
9
+ import ManualOverride from "@/components/ManualOverride";
10
+ import LogBox from "@/components/LogBox";
11
+ import { resetEnv, stepEnv, callAgent } from "@/lib/api";
12
+
13
+ export default function Dashboard() {
14
+ // ── Config state ──
15
+ const [taskName, setTaskName] = useState("single-pass-review");
16
+ const [apiUrl, setApiUrl] = useState("https://integrate.api.nvidia.com/v1");
17
+ const [modelId, setModelId] = useState("google/gemma-4-31b-it");
18
+ const [apiKey, setApiKey] = useState("");
19
+ const [isInternal] = useState(false);
20
+
21
+ // ── Environment state ──
22
+ const [initialized, setInitialized] = useState(false);
23
+ const [initStatus, setInitStatus] = useState("idle"); // idle | loading | ready
24
+ const [observation, setObservation] = useState({});
25
+ const [score, setScore] = useState(0);
26
+ const [turn, setTurn] = useState(0);
27
+ const [maxTurns, setMaxTurns] = useState(3);
28
+ const [done, setDone] = useState(false);
29
+ const [decision, setDecision] = useState("IDLE");
30
+ const [rewards, setRewards] = useState([]);
31
+ const [logs, setLogs] = useState([]);
32
+ const [isThinking, setIsThinking] = useState(false);
33
+ const [error, setError] = useState(null);
34
+
35
+ // ── Tab state ──
36
+ const [activeTab, setActiveTab] = useState("diff");
37
+
38
+ const addLog = useCallback((msg) => {
39
+ setLogs(prev => [...prev, `[${new Date().toLocaleTimeString()}] ${msg}`]);
40
+ }, []);
41
+
42
+ // ── Initialize ──
43
+ const handleInit = useCallback(async () => {
44
+ setError(null);
45
+ setInitStatus("loading");
46
+ addLog(`Resetting environment: ${taskName}`);
47
+ try {
48
+ const obs = await resetEnv(taskName);
49
+ setObservation(obs);
50
+ setInitialized(true);
51
+ setScore(0);
52
+ setTurn(1);
53
+ setMaxTurns(3);
54
+ setDone(false);
55
+ setDecision("IDLE");
56
+ setRewards([]);
57
+ setInitStatus("ready");
58
+ addLog(`Environment ready. PR: ${obs.pr_title}`);
59
+ } catch (e) {
60
+ setError(e.message);
61
+ setInitStatus("idle");
62
+ addLog(`ERROR: ${e.message}`);
63
+ }
64
+ }, [taskName, addLog]);
65
+
66
+ // ── Execute AI Round ──
67
+ const handleExecute = useCallback(async () => {
68
+ if (done || isThinking) return;
69
+ setError(null);
70
+ setIsThinking(true);
71
+ addLog(`Calling AI agent: ${modelId}`);
72
+
73
+ try {
74
+ const action = await callAgent({ observation, modelId, apiUrl, apiKey });
75
+
76
+ if (action.decision === "error") {
77
+ setError(action.comment);
78
+ addLog(`AI ERROR: ${action.comment}`);
79
+ setIsThinking(false);
80
+ return;
81
+ }
82
+
83
+ addLog(`AI decision: ${action.decision}`);
84
+ const result = await stepEnv(action);
85
+ const newReward = result.reward;
86
+
87
+ setObservation(result.observation);
88
+ setScore(prev => prev + newReward);
89
+ setRewards(prev => [...prev, newReward]);
90
+ setDone(result.done);
91
+ setDecision(action.decision.toUpperCase());
92
+ if (!result.done) setTurn(prev => prev + 1);
93
+
94
+ addLog(`Step complete: reward=${newReward.toFixed(2)} done=${result.done}`);
95
+ } catch (e) {
96
+ setError(e.message);
97
+ addLog(`STEP ERROR: ${e.message}`);
98
+ } finally {
99
+ setIsThinking(false);
100
+ }
101
+ }, [done, isThinking, observation, modelId, apiUrl, apiKey, addLog]);
102
+
103
+ // ── Manual Override ──
104
+ const handleManual = useCallback(async ({ decision: dec, comment }) => {
105
+ if (done) return null;
106
+ setError(null);
107
+ addLog(`Manual action: ${dec}`);
108
+ try {
109
+ const result = await stepEnv({ decision: dec, comment: comment || "Manual review." });
110
+ setObservation(result.observation);
111
+ setScore(prev => prev + result.reward);
112
+ setRewards(prev => [...prev, result.reward]);
113
+ setDone(result.done);
114
+ setDecision(dec.toUpperCase());
115
+ if (!result.done) setTurn(prev => prev + 1);
116
+ addLog(`Manual step: reward=${result.reward.toFixed(2)} done=${result.done}`);
117
+ return result;
118
+ } catch (e) {
119
+ setError(e.message);
120
+ addLog(`MANUAL ERROR: ${e.message}`);
121
+ return null;
122
+ }
123
+ }, [done, addLog]);
124
+
125
+ // ── Derived Data ──
126
+ const epStatus = !initialized ? "Waiting" : done ? "Complete" : "Running";
127
+ const epSub = !initialized ? "No episode" : done ? "Episode finished" : isThinking ? "AI turn active" : "Ready for input";
128
+ const prSub = initialized
129
+ ? `${taskName} · Turn ${turn}/${maxTurns}`
130
+ : "Select a scenario and initialize";
131
+
132
+ return (
133
+ <div className="dash">
134
+ <Sidebar
135
+ taskName={taskName} setTaskName={setTaskName}
136
+ apiUrl={apiUrl} setApiUrl={setApiUrl}
137
+ modelId={modelId} setModelId={setModelId}
138
+ apiKey={apiKey} setApiKey={setApiKey}
139
+ onInit={handleInit}
140
+ initStatus={initStatus}
141
+ rewards={rewards}
142
+ isInternal={isInternal}
143
+ />
144
+ <div className="main">
145
+ <TopBar
146
+ title={observation.pr_title || "PR Review Command Center"}
147
+ subtitle={prSub}
148
+ decision={decision}
149
+ />
150
+ <MetricCards
151
+ score={score}
152
+ turn={turn}
153
+ maxTurns={maxTurns}
154
+ status={epStatus}
155
+ statusSub={epSub}
156
+ />
157
+ <TabBar activeTab={activeTab} setActiveTab={setActiveTab} />
158
+
159
+ <div className="content">
160
+ {error && <div className="status-msg error">{error}</div>}
161
+
162
+ {activeTab === "diff" && <DiffView diff={observation.diff} />}
163
+ {activeTab === "timeline" && (
164
+ <Timeline
165
+ history={observation.review_history || []}
166
+ isThinking={isThinking}
167
+ onExecute={handleExecute}
168
+ done={done}
169
+ />
170
+ )}
171
+ {activeTab === "manual" && (
172
+ <ManualOverride onSubmit={handleManual} disabled={done || !initialized} />
173
+ )}
174
+
175
+ <LogBox logs={logs} />
176
+ </div>
177
+ </div>
178
+ </div>
179
+ );
180
+ }
pr_review_dashboard/components/DiffView.js ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ export default function DiffView({ diff }) {
4
+ if (!diff || !diff.trim()) {
5
+ return (
6
+ <div className="diff-box">
7
+ <div className="diff-header"><span>No diff loaded</span></div>
8
+ </div>
9
+ );
10
+ }
11
+
12
+ const lines = diff.split("\n");
13
+
14
+ // Extract filename
15
+ let filename = "unknown_file";
16
+ for (const line of lines) {
17
+ if (line.startsWith("+++ b/")) {
18
+ filename = line.slice(6);
19
+ break;
20
+ }
21
+ }
22
+
23
+ // Count additions/deletions
24
+ let adds = 0, dels = 0;
25
+ lines.forEach(l => {
26
+ if (l.startsWith("+") && !l.startsWith("+++")) adds++;
27
+ if (l.startsWith("-") && !l.startsWith("---")) dels++;
28
+ });
29
+
30
+ // Build line numbers
31
+ let lineNum = 0;
32
+ const parsedLines = lines.map((line) => {
33
+ let type = "context";
34
+ if (line.startsWith("@@")) {
35
+ type = "meta";
36
+ const match = line.match(/@@ -\d+,?\d* \+(\d+)/);
37
+ if (match) lineNum = parseInt(match[1]) - 1;
38
+ } else if (line.startsWith("+") && !line.startsWith("+++")) {
39
+ type = "add";
40
+ lineNum++;
41
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
42
+ type = "del";
43
+ } else if (!line.startsWith("---") && !line.startsWith("+++")) {
44
+ lineNum++;
45
+ }
46
+ return { text: line, type, num: type === "meta" ? "···" : type === "del" ? "" : lineNum || "" };
47
+ });
48
+
49
+ return (
50
+ <div className="diff-box">
51
+ <div className="diff-header">
52
+ <span>{filename}</span>
53
+ <span>+{adds} −{dels} lines</span>
54
+ </div>
55
+ <div className="diff-body">
56
+ {parsedLines.map((line, i) => (
57
+ <div key={i} className={`diff-line ${line.type}`}>
58
+ <span className="ln">{line.num}</span>
59
+ <span>{line.text}</span>
60
+ </div>
61
+ ))}
62
+ </div>
63
+ </div>
64
+ );
65
+ }
pr_review_dashboard/components/LogBox.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { useState } from "react";
3
+
4
+ export default function LogBox({ logs }) {
5
+ const [open, setOpen] = useState(false);
6
+
7
+ return (
8
+ <div className="log-box">
9
+ <div className="log-toggle" onClick={() => setOpen(!open)}>
10
+ <span>Behind the scenes — raw logs</span>
11
+ <span>{open ? "▼" : "▶"}</span>
12
+ </div>
13
+ {open && (
14
+ <div className="log-content">
15
+ {logs.length === 0
16
+ ? "No logs yet. Initialize an environment to begin."
17
+ : logs.join("\n")}
18
+ </div>
19
+ )}
20
+ </div>
21
+ );
22
+ }
pr_review_dashboard/components/ManualOverride.js ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { useState } from "react";
3
+
4
+ export default function ManualOverride({ onSubmit, disabled }) {
5
+ const [comment, setComment] = useState("");
6
+ const [response, setResponse] = useState(null);
7
+
8
+ const handleSubmit = async (decision) => {
9
+ if (disabled) return;
10
+ const result = await onSubmit({ decision, comment });
11
+ if (result) setResponse(result);
12
+ };
13
+
14
+ return (
15
+ <div>
16
+ <div style={{ fontSize: 13, color: "var(--color-text-secondary)", marginBottom: 12 }}>
17
+ Act as the reviewer. Submit feedback to see how the environment responds.
18
+ </div>
19
+ <textarea
20
+ className="manual-textarea"
21
+ value={comment}
22
+ onChange={e => setComment(e.target.value)}
23
+ placeholder="e.g. I found a potential null pointer issue in line 47…"
24
+ />
25
+ <div className="manual-actions">
26
+ <button className="init-btn" onClick={() => handleSubmit("request_changes")} disabled={disabled}>
27
+ Request changes
28
+ </button>
29
+ <button className="init-btn active" onClick={() => handleSubmit("approve")} disabled={disabled}>
30
+ Approve
31
+ </button>
32
+ <button
33
+ className="init-btn"
34
+ style={{ borderColor: "#856404", color: "#856404" }}
35
+ onClick={() => handleSubmit("escalate")}
36
+ disabled={disabled}
37
+ >
38
+ Escalate
39
+ </button>
40
+ </div>
41
+
42
+ {response && (
43
+ <div style={{ marginTop: 14 }}>
44
+ <div className="section-head" style={{ marginBottom: 8 }}>Environment response</div>
45
+ <div className="bubble reviewer" style={{ maxWidth: "100%" }}>
46
+ Reward: {response.reward?.toFixed(2)} | Done: {response.done ? "Yes" : "No"}
47
+ </div>
48
+ </div>
49
+ )}
50
+ </div>
51
+ );
52
+ }
pr_review_dashboard/components/MetricCards.js ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ export default function MetricCards({ score, turn, maxTurns, status, statusSub }) {
4
+ const pct = Math.min((score / (maxTurns * 0.8)) * 100, 100);
5
+
6
+ return (
7
+ <div className="metrics">
8
+ <div className="metric-card">
9
+ <div className="m-label">Cumulative reward</div>
10
+ <div className="m-val">{score.toFixed(2)}</div>
11
+ <div className="progress-bar">
12
+ <div className="progress-fill" style={{ width: `${pct}%` }} />
13
+ </div>
14
+ </div>
15
+ <div className="metric-card">
16
+ <div className="m-label">Turn</div>
17
+ <div className="m-val">
18
+ {turn} <span style={{ fontSize: 14, color: "var(--color-text-tertiary)" }}>/ {maxTurns}</span>
19
+ </div>
20
+ <div className="m-sub">
21
+ {status === "Running" ? "Reviewer processing…" : status === "Complete" ? "Episode finished" : "Waiting…"}
22
+ </div>
23
+ </div>
24
+ <div className="metric-card">
25
+ <div className="m-label">Episode status</div>
26
+ <div className="m-val" style={{ fontSize: 14, fontWeight: 500, paddingTop: 4 }}>{status}</div>
27
+ <div className="m-sub">{statusSub}</div>
28
+ </div>
29
+ </div>
30
+ );
31
+ }
pr_review_dashboard/components/Sidebar.js ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ export default function Sidebar({
4
+ taskName, setTaskName,
5
+ apiUrl, setApiUrl,
6
+ modelId, setModelId,
7
+ apiKey, setApiKey,
8
+ onInit, initStatus,
9
+ rewards,
10
+ isInternal,
11
+ }) {
12
+ const TASKS = [
13
+ { value: "single-pass-review", label: "Easy — single pass review" },
14
+ { value: "iterative-negotiation", label: "Medium — iterative negotiation" },
15
+ { value: "escalation-judgment", label: "Hard — escalation judgment" },
16
+ { value: "custom-review", label: "Custom — your own code" },
17
+ ];
18
+
19
+ return (
20
+ <div className="sidebar">
21
+ <div>
22
+ <div className="sidebar-label">Scenario</div>
23
+ <select value={taskName} onChange={e => setTaskName(e.target.value)}>
24
+ {TASKS.map(t => <option key={t.value} value={t.value}>{t.label}</option>)}
25
+ </select>
26
+ </div>
27
+
28
+ <div>
29
+ <div className="sidebar-label">API base URL</div>
30
+ <input
31
+ type="text"
32
+ value={apiUrl}
33
+ onChange={e => setApiUrl(e.target.value)}
34
+ disabled={isInternal}
35
+ />
36
+ </div>
37
+
38
+ <div>
39
+ <div className="sidebar-label">Model</div>
40
+ <input
41
+ type="text"
42
+ value={modelId}
43
+ onChange={e => setModelId(e.target.value)}
44
+ />
45
+ </div>
46
+
47
+ {!isInternal && (
48
+ <div>
49
+ <div className="sidebar-label">API Key</div>
50
+ <input
51
+ type="password"
52
+ value={apiKey}
53
+ onChange={e => setApiKey(e.target.value)}
54
+ placeholder="sk-..."
55
+ />
56
+ </div>
57
+ )}
58
+ {isInternal && (
59
+ <div className="status-msg info">🔒 Secure internal key active</div>
60
+ )}
61
+
62
+ <div className="sep" />
63
+
64
+ <button
65
+ className={`init-btn ${initStatus === "ready" ? "active" : ""}`}
66
+ onClick={onInit}
67
+ disabled={initStatus === "loading"}
68
+ >
69
+ {initStatus === "loading" ? "Initializing…" : initStatus === "ready" ? "Environment ready" : "Initialize environment"}
70
+ </button>
71
+
72
+ <div style={{ marginTop: "auto" }}>
73
+ <div className="sidebar-label" style={{ marginBottom: 8 }}>Reward history</div>
74
+ <RewardChart rewards={rewards} />
75
+ </div>
76
+ </div>
77
+ );
78
+ }
79
+
80
+ function RewardChart({ rewards }) {
81
+ if (!rewards || rewards.length === 0) {
82
+ return <div style={{ fontSize: 11, color: "var(--color-text-tertiary)" }}>No data yet</div>;
83
+ }
84
+
85
+ const getColor = (val) => {
86
+ if (val >= 0.7) return "#1a7f3c";
87
+ if (val >= 0.4) return "#ef9f27";
88
+ return "#e24b4a";
89
+ };
90
+
91
+ return (
92
+ <div className="reward-chart">
93
+ {rewards.map((r, i) => (
94
+ <div className="chart-row" key={i}>
95
+ <span className="chart-label">T{i + 1}</span>
96
+ <div className="chart-bar-wrap">
97
+ <div className="chart-bar" style={{ width: `${Math.min(r * 100, 100)}%`, background: getColor(r) }} />
98
+ </div>
99
+ <span className="chart-val">{r.toFixed(2)}</span>
100
+ </div>
101
+ ))}
102
+ </div>
103
+ );
104
+ }
pr_review_dashboard/components/TabBar.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ export default function TabBar({ activeTab, setActiveTab }) {
4
+ const tabs = [
5
+ { id: "diff", label: "Diff view" },
6
+ { id: "timeline", label: "Negotiation timeline" },
7
+ { id: "manual", label: "Manual override" },
8
+ ];
9
+
10
+ return (
11
+ <div className="tabs">
12
+ {tabs.map(t => (
13
+ <div
14
+ key={t.id}
15
+ className={`tab ${activeTab === t.id ? "active" : ""}`}
16
+ onClick={() => setActiveTab(t.id)}
17
+ >
18
+ {t.label}
19
+ </div>
20
+ ))}
21
+ </div>
22
+ );
23
+ }
pr_review_dashboard/components/Timeline.js ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ export default function Timeline({ history, isThinking, onExecute, done }) {
4
+ return (
5
+ <div className="chat-thread">
6
+ {(!history || history.length === 0) && !isThinking && (
7
+ <div style={{ fontSize: 13, color: "var(--color-text-secondary)" }}>
8
+ No review activity yet. Click below to start.
9
+ </div>
10
+ )}
11
+
12
+ {history.map((item, i) => {
13
+ const isReviewer = item.role === "reviewer";
14
+ return (
15
+ <div key={i} className={`chat-msg ${isReviewer ? "" : "author"}`}>
16
+ <div className={`avatar ${isReviewer ? "reviewer" : "author"}`}>
17
+ {isReviewer ? "AI" : "Env"}
18
+ </div>
19
+ <div>
20
+ <div className={`bubble ${isReviewer ? "reviewer" : "author"}`}>
21
+ {item.content}
22
+ </div>
23
+ <div className="chat-meta" style={isReviewer ? {} : { textAlign: "right" }}>
24
+ {isReviewer ? "Reviewer" : "Author"} · Turn {Math.ceil((i + 1) / 2)}
25
+ </div>
26
+ </div>
27
+ </div>
28
+ );
29
+ })}
30
+
31
+ {isThinking && (
32
+ <div className="chat-msg">
33
+ <div className="avatar reviewer pulse">AI</div>
34
+ <div>
35
+ <div className="thinking">
36
+ <div className="dot" />
37
+ <div className="dot" />
38
+ <div className="dot" />
39
+ <span style={{ marginLeft: 2 }}>Reviewer is thinking…</span>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ )}
44
+
45
+ {!done && !isThinking && (
46
+ <button className="init-btn" onClick={onExecute} style={{ marginTop: 8 }}>
47
+ ▶ Execute next round
48
+ </button>
49
+ )}
50
+
51
+ {done && (
52
+ <div className="status-msg success" style={{ marginTop: 8 }}>
53
+ ✓ Episode complete
54
+ </div>
55
+ )}
56
+ </div>
57
+ );
58
+ }
pr_review_dashboard/components/TopBar.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ export default function TopBar({ title, subtitle, decision }) {
4
+ const badgeMap = {
5
+ APPROVE: "approve",
6
+ REQUEST_CHANGES: "request",
7
+ ESCALATE: "escalate",
8
+ RUNNING: "running",
9
+ IDLE: "idle",
10
+ };
11
+ const cls = badgeMap[decision] || "running";
12
+
13
+ return (
14
+ <div className="topbar">
15
+ <div>
16
+ <div className="pr-title">{title || "No PR loaded"}</div>
17
+ <div className="pr-sub">{subtitle || "Initialize a scenario to begin"}</div>
18
+ </div>
19
+ <span className={`badge ${cls}`}>{decision}</span>
20
+ </div>
21
+ );
22
+ }
pr_review_dashboard/jsconfig.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "paths": {
4
+ "@/*": ["./*"]
5
+ }
6
+ }
7
+ }
pr_review_dashboard/next.config.mjs ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ async rewrites() {
4
+ return [
5
+ // Proxy all /api/env/* requests to the FastAPI backend on port 8000
6
+ { source: "/api/env/:path*", destination: "http://localhost:8000/:path*" },
7
+ ];
8
+ },
9
+ };
10
+
11
+ export default nextConfig;
pr_review_dashboard/package-lock.json ADDED
@@ -0,0 +1,936 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "pr_review_dashboard",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "pr_review_dashboard",
9
+ "version": "0.1.0",
10
+ "dependencies": {
11
+ "next": "16.2.3",
12
+ "openai": "^6.34.0",
13
+ "react": "19.2.4",
14
+ "react-dom": "19.2.4"
15
+ }
16
+ },
17
+ "node_modules/@emnapi/runtime": {
18
+ "version": "1.9.2",
19
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
20
+ "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
21
+ "license": "MIT",
22
+ "optional": true,
23
+ "dependencies": {
24
+ "tslib": "^2.4.0"
25
+ }
26
+ },
27
+ "node_modules/@img/colour": {
28
+ "version": "1.1.0",
29
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
30
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
31
+ "license": "MIT",
32
+ "optional": true,
33
+ "engines": {
34
+ "node": ">=18"
35
+ }
36
+ },
37
+ "node_modules/@img/sharp-darwin-arm64": {
38
+ "version": "0.34.5",
39
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
40
+ "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
41
+ "cpu": [
42
+ "arm64"
43
+ ],
44
+ "license": "Apache-2.0",
45
+ "optional": true,
46
+ "os": [
47
+ "darwin"
48
+ ],
49
+ "engines": {
50
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
51
+ },
52
+ "funding": {
53
+ "url": "https://opencollective.com/libvips"
54
+ },
55
+ "optionalDependencies": {
56
+ "@img/sharp-libvips-darwin-arm64": "1.2.4"
57
+ }
58
+ },
59
+ "node_modules/@img/sharp-darwin-x64": {
60
+ "version": "0.34.5",
61
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
62
+ "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
63
+ "cpu": [
64
+ "x64"
65
+ ],
66
+ "license": "Apache-2.0",
67
+ "optional": true,
68
+ "os": [
69
+ "darwin"
70
+ ],
71
+ "engines": {
72
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
73
+ },
74
+ "funding": {
75
+ "url": "https://opencollective.com/libvips"
76
+ },
77
+ "optionalDependencies": {
78
+ "@img/sharp-libvips-darwin-x64": "1.2.4"
79
+ }
80
+ },
81
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
82
+ "version": "1.2.4",
83
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
84
+ "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
85
+ "cpu": [
86
+ "arm64"
87
+ ],
88
+ "license": "LGPL-3.0-or-later",
89
+ "optional": true,
90
+ "os": [
91
+ "darwin"
92
+ ],
93
+ "funding": {
94
+ "url": "https://opencollective.com/libvips"
95
+ }
96
+ },
97
+ "node_modules/@img/sharp-libvips-darwin-x64": {
98
+ "version": "1.2.4",
99
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
100
+ "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
101
+ "cpu": [
102
+ "x64"
103
+ ],
104
+ "license": "LGPL-3.0-or-later",
105
+ "optional": true,
106
+ "os": [
107
+ "darwin"
108
+ ],
109
+ "funding": {
110
+ "url": "https://opencollective.com/libvips"
111
+ }
112
+ },
113
+ "node_modules/@img/sharp-libvips-linux-arm": {
114
+ "version": "1.2.4",
115
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
116
+ "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
117
+ "cpu": [
118
+ "arm"
119
+ ],
120
+ "license": "LGPL-3.0-or-later",
121
+ "optional": true,
122
+ "os": [
123
+ "linux"
124
+ ],
125
+ "funding": {
126
+ "url": "https://opencollective.com/libvips"
127
+ }
128
+ },
129
+ "node_modules/@img/sharp-libvips-linux-arm64": {
130
+ "version": "1.2.4",
131
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
132
+ "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
133
+ "cpu": [
134
+ "arm64"
135
+ ],
136
+ "license": "LGPL-3.0-or-later",
137
+ "optional": true,
138
+ "os": [
139
+ "linux"
140
+ ],
141
+ "funding": {
142
+ "url": "https://opencollective.com/libvips"
143
+ }
144
+ },
145
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
146
+ "version": "1.2.4",
147
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
148
+ "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
149
+ "cpu": [
150
+ "ppc64"
151
+ ],
152
+ "license": "LGPL-3.0-or-later",
153
+ "optional": true,
154
+ "os": [
155
+ "linux"
156
+ ],
157
+ "funding": {
158
+ "url": "https://opencollective.com/libvips"
159
+ }
160
+ },
161
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
162
+ "version": "1.2.4",
163
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
164
+ "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
165
+ "cpu": [
166
+ "riscv64"
167
+ ],
168
+ "license": "LGPL-3.0-or-later",
169
+ "optional": true,
170
+ "os": [
171
+ "linux"
172
+ ],
173
+ "funding": {
174
+ "url": "https://opencollective.com/libvips"
175
+ }
176
+ },
177
+ "node_modules/@img/sharp-libvips-linux-s390x": {
178
+ "version": "1.2.4",
179
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
180
+ "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
181
+ "cpu": [
182
+ "s390x"
183
+ ],
184
+ "license": "LGPL-3.0-or-later",
185
+ "optional": true,
186
+ "os": [
187
+ "linux"
188
+ ],
189
+ "funding": {
190
+ "url": "https://opencollective.com/libvips"
191
+ }
192
+ },
193
+ "node_modules/@img/sharp-libvips-linux-x64": {
194
+ "version": "1.2.4",
195
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
196
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
197
+ "cpu": [
198
+ "x64"
199
+ ],
200
+ "license": "LGPL-3.0-or-later",
201
+ "optional": true,
202
+ "os": [
203
+ "linux"
204
+ ],
205
+ "funding": {
206
+ "url": "https://opencollective.com/libvips"
207
+ }
208
+ },
209
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
210
+ "version": "1.2.4",
211
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
212
+ "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
213
+ "cpu": [
214
+ "arm64"
215
+ ],
216
+ "license": "LGPL-3.0-or-later",
217
+ "optional": true,
218
+ "os": [
219
+ "linux"
220
+ ],
221
+ "funding": {
222
+ "url": "https://opencollective.com/libvips"
223
+ }
224
+ },
225
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
226
+ "version": "1.2.4",
227
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
228
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
229
+ "cpu": [
230
+ "x64"
231
+ ],
232
+ "license": "LGPL-3.0-or-later",
233
+ "optional": true,
234
+ "os": [
235
+ "linux"
236
+ ],
237
+ "funding": {
238
+ "url": "https://opencollective.com/libvips"
239
+ }
240
+ },
241
+ "node_modules/@img/sharp-linux-arm": {
242
+ "version": "0.34.5",
243
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
244
+ "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
245
+ "cpu": [
246
+ "arm"
247
+ ],
248
+ "license": "Apache-2.0",
249
+ "optional": true,
250
+ "os": [
251
+ "linux"
252
+ ],
253
+ "engines": {
254
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
255
+ },
256
+ "funding": {
257
+ "url": "https://opencollective.com/libvips"
258
+ },
259
+ "optionalDependencies": {
260
+ "@img/sharp-libvips-linux-arm": "1.2.4"
261
+ }
262
+ },
263
+ "node_modules/@img/sharp-linux-arm64": {
264
+ "version": "0.34.5",
265
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
266
+ "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
267
+ "cpu": [
268
+ "arm64"
269
+ ],
270
+ "license": "Apache-2.0",
271
+ "optional": true,
272
+ "os": [
273
+ "linux"
274
+ ],
275
+ "engines": {
276
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
277
+ },
278
+ "funding": {
279
+ "url": "https://opencollective.com/libvips"
280
+ },
281
+ "optionalDependencies": {
282
+ "@img/sharp-libvips-linux-arm64": "1.2.4"
283
+ }
284
+ },
285
+ "node_modules/@img/sharp-linux-ppc64": {
286
+ "version": "0.34.5",
287
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
288
+ "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
289
+ "cpu": [
290
+ "ppc64"
291
+ ],
292
+ "license": "Apache-2.0",
293
+ "optional": true,
294
+ "os": [
295
+ "linux"
296
+ ],
297
+ "engines": {
298
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
299
+ },
300
+ "funding": {
301
+ "url": "https://opencollective.com/libvips"
302
+ },
303
+ "optionalDependencies": {
304
+ "@img/sharp-libvips-linux-ppc64": "1.2.4"
305
+ }
306
+ },
307
+ "node_modules/@img/sharp-linux-riscv64": {
308
+ "version": "0.34.5",
309
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
310
+ "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
311
+ "cpu": [
312
+ "riscv64"
313
+ ],
314
+ "license": "Apache-2.0",
315
+ "optional": true,
316
+ "os": [
317
+ "linux"
318
+ ],
319
+ "engines": {
320
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
321
+ },
322
+ "funding": {
323
+ "url": "https://opencollective.com/libvips"
324
+ },
325
+ "optionalDependencies": {
326
+ "@img/sharp-libvips-linux-riscv64": "1.2.4"
327
+ }
328
+ },
329
+ "node_modules/@img/sharp-linux-s390x": {
330
+ "version": "0.34.5",
331
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
332
+ "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
333
+ "cpu": [
334
+ "s390x"
335
+ ],
336
+ "license": "Apache-2.0",
337
+ "optional": true,
338
+ "os": [
339
+ "linux"
340
+ ],
341
+ "engines": {
342
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
343
+ },
344
+ "funding": {
345
+ "url": "https://opencollective.com/libvips"
346
+ },
347
+ "optionalDependencies": {
348
+ "@img/sharp-libvips-linux-s390x": "1.2.4"
349
+ }
350
+ },
351
+ "node_modules/@img/sharp-linux-x64": {
352
+ "version": "0.34.5",
353
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
354
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
355
+ "cpu": [
356
+ "x64"
357
+ ],
358
+ "license": "Apache-2.0",
359
+ "optional": true,
360
+ "os": [
361
+ "linux"
362
+ ],
363
+ "engines": {
364
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
365
+ },
366
+ "funding": {
367
+ "url": "https://opencollective.com/libvips"
368
+ },
369
+ "optionalDependencies": {
370
+ "@img/sharp-libvips-linux-x64": "1.2.4"
371
+ }
372
+ },
373
+ "node_modules/@img/sharp-linuxmusl-arm64": {
374
+ "version": "0.34.5",
375
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
376
+ "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
377
+ "cpu": [
378
+ "arm64"
379
+ ],
380
+ "license": "Apache-2.0",
381
+ "optional": true,
382
+ "os": [
383
+ "linux"
384
+ ],
385
+ "engines": {
386
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
387
+ },
388
+ "funding": {
389
+ "url": "https://opencollective.com/libvips"
390
+ },
391
+ "optionalDependencies": {
392
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
393
+ }
394
+ },
395
+ "node_modules/@img/sharp-linuxmusl-x64": {
396
+ "version": "0.34.5",
397
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
398
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
399
+ "cpu": [
400
+ "x64"
401
+ ],
402
+ "license": "Apache-2.0",
403
+ "optional": true,
404
+ "os": [
405
+ "linux"
406
+ ],
407
+ "engines": {
408
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
409
+ },
410
+ "funding": {
411
+ "url": "https://opencollective.com/libvips"
412
+ },
413
+ "optionalDependencies": {
414
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
415
+ }
416
+ },
417
+ "node_modules/@img/sharp-wasm32": {
418
+ "version": "0.34.5",
419
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
420
+ "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
421
+ "cpu": [
422
+ "wasm32"
423
+ ],
424
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
425
+ "optional": true,
426
+ "dependencies": {
427
+ "@emnapi/runtime": "^1.7.0"
428
+ },
429
+ "engines": {
430
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
431
+ },
432
+ "funding": {
433
+ "url": "https://opencollective.com/libvips"
434
+ }
435
+ },
436
+ "node_modules/@img/sharp-win32-arm64": {
437
+ "version": "0.34.5",
438
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
439
+ "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
440
+ "cpu": [
441
+ "arm64"
442
+ ],
443
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
444
+ "optional": true,
445
+ "os": [
446
+ "win32"
447
+ ],
448
+ "engines": {
449
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
450
+ },
451
+ "funding": {
452
+ "url": "https://opencollective.com/libvips"
453
+ }
454
+ },
455
+ "node_modules/@img/sharp-win32-ia32": {
456
+ "version": "0.34.5",
457
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
458
+ "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
459
+ "cpu": [
460
+ "ia32"
461
+ ],
462
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
463
+ "optional": true,
464
+ "os": [
465
+ "win32"
466
+ ],
467
+ "engines": {
468
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
469
+ },
470
+ "funding": {
471
+ "url": "https://opencollective.com/libvips"
472
+ }
473
+ },
474
+ "node_modules/@img/sharp-win32-x64": {
475
+ "version": "0.34.5",
476
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
477
+ "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
478
+ "cpu": [
479
+ "x64"
480
+ ],
481
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
482
+ "optional": true,
483
+ "os": [
484
+ "win32"
485
+ ],
486
+ "engines": {
487
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
488
+ },
489
+ "funding": {
490
+ "url": "https://opencollective.com/libvips"
491
+ }
492
+ },
493
+ "node_modules/@next/env": {
494
+ "version": "16.2.3",
495
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.3.tgz",
496
+ "integrity": "sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA==",
497
+ "license": "MIT"
498
+ },
499
+ "node_modules/@next/swc-darwin-arm64": {
500
+ "version": "16.2.3",
501
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.3.tgz",
502
+ "integrity": "sha512-u37KDKTKQ+OQLvY+z7SNXixwo4Q2/IAJFDzU1fYe66IbCE51aDSAzkNDkWmLN0yjTUh4BKBd+hb69jYn6qqqSg==",
503
+ "cpu": [
504
+ "arm64"
505
+ ],
506
+ "license": "MIT",
507
+ "optional": true,
508
+ "os": [
509
+ "darwin"
510
+ ],
511
+ "engines": {
512
+ "node": ">= 10"
513
+ }
514
+ },
515
+ "node_modules/@next/swc-darwin-x64": {
516
+ "version": "16.2.3",
517
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.3.tgz",
518
+ "integrity": "sha512-gHjL/qy6Q6CG3176FWbAKyKh9IfntKZTB3RY/YOJdDFpHGsUDXVH38U4mMNpHVGXmeYW4wj22dMp1lTfmu/bTQ==",
519
+ "cpu": [
520
+ "x64"
521
+ ],
522
+ "license": "MIT",
523
+ "optional": true,
524
+ "os": [
525
+ "darwin"
526
+ ],
527
+ "engines": {
528
+ "node": ">= 10"
529
+ }
530
+ },
531
+ "node_modules/@next/swc-linux-arm64-gnu": {
532
+ "version": "16.2.3",
533
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.3.tgz",
534
+ "integrity": "sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q==",
535
+ "cpu": [
536
+ "arm64"
537
+ ],
538
+ "license": "MIT",
539
+ "optional": true,
540
+ "os": [
541
+ "linux"
542
+ ],
543
+ "engines": {
544
+ "node": ">= 10"
545
+ }
546
+ },
547
+ "node_modules/@next/swc-linux-arm64-musl": {
548
+ "version": "16.2.3",
549
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.3.tgz",
550
+ "integrity": "sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==",
551
+ "cpu": [
552
+ "arm64"
553
+ ],
554
+ "license": "MIT",
555
+ "optional": true,
556
+ "os": [
557
+ "linux"
558
+ ],
559
+ "engines": {
560
+ "node": ">= 10"
561
+ }
562
+ },
563
+ "node_modules/@next/swc-linux-x64-gnu": {
564
+ "version": "16.2.3",
565
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.3.tgz",
566
+ "integrity": "sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==",
567
+ "cpu": [
568
+ "x64"
569
+ ],
570
+ "license": "MIT",
571
+ "optional": true,
572
+ "os": [
573
+ "linux"
574
+ ],
575
+ "engines": {
576
+ "node": ">= 10"
577
+ }
578
+ },
579
+ "node_modules/@next/swc-linux-x64-musl": {
580
+ "version": "16.2.3",
581
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.3.tgz",
582
+ "integrity": "sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==",
583
+ "cpu": [
584
+ "x64"
585
+ ],
586
+ "license": "MIT",
587
+ "optional": true,
588
+ "os": [
589
+ "linux"
590
+ ],
591
+ "engines": {
592
+ "node": ">= 10"
593
+ }
594
+ },
595
+ "node_modules/@next/swc-win32-arm64-msvc": {
596
+ "version": "16.2.3",
597
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.3.tgz",
598
+ "integrity": "sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw==",
599
+ "cpu": [
600
+ "arm64"
601
+ ],
602
+ "license": "MIT",
603
+ "optional": true,
604
+ "os": [
605
+ "win32"
606
+ ],
607
+ "engines": {
608
+ "node": ">= 10"
609
+ }
610
+ },
611
+ "node_modules/@next/swc-win32-x64-msvc": {
612
+ "version": "16.2.3",
613
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.3.tgz",
614
+ "integrity": "sha512-Ibm29/GgB/ab5n7XKqlStkm54qqZE8v2FnijUPBgrd67FWrac45o/RsNlaOWjme/B5UqeWt/8KM4aWBwA1D2Kw==",
615
+ "cpu": [
616
+ "x64"
617
+ ],
618
+ "license": "MIT",
619
+ "optional": true,
620
+ "os": [
621
+ "win32"
622
+ ],
623
+ "engines": {
624
+ "node": ">= 10"
625
+ }
626
+ },
627
+ "node_modules/@swc/helpers": {
628
+ "version": "0.5.15",
629
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
630
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
631
+ "license": "Apache-2.0",
632
+ "dependencies": {
633
+ "tslib": "^2.8.0"
634
+ }
635
+ },
636
+ "node_modules/baseline-browser-mapping": {
637
+ "version": "2.10.16",
638
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.16.tgz",
639
+ "integrity": "sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==",
640
+ "license": "Apache-2.0",
641
+ "bin": {
642
+ "baseline-browser-mapping": "dist/cli.cjs"
643
+ },
644
+ "engines": {
645
+ "node": ">=6.0.0"
646
+ }
647
+ },
648
+ "node_modules/caniuse-lite": {
649
+ "version": "1.0.30001787",
650
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz",
651
+ "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==",
652
+ "funding": [
653
+ {
654
+ "type": "opencollective",
655
+ "url": "https://opencollective.com/browserslist"
656
+ },
657
+ {
658
+ "type": "tidelift",
659
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
660
+ },
661
+ {
662
+ "type": "github",
663
+ "url": "https://github.com/sponsors/ai"
664
+ }
665
+ ],
666
+ "license": "CC-BY-4.0"
667
+ },
668
+ "node_modules/client-only": {
669
+ "version": "0.0.1",
670
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
671
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
672
+ "license": "MIT"
673
+ },
674
+ "node_modules/detect-libc": {
675
+ "version": "2.1.2",
676
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
677
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
678
+ "license": "Apache-2.0",
679
+ "optional": true,
680
+ "engines": {
681
+ "node": ">=8"
682
+ }
683
+ },
684
+ "node_modules/nanoid": {
685
+ "version": "3.3.11",
686
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
687
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
688
+ "funding": [
689
+ {
690
+ "type": "github",
691
+ "url": "https://github.com/sponsors/ai"
692
+ }
693
+ ],
694
+ "license": "MIT",
695
+ "bin": {
696
+ "nanoid": "bin/nanoid.cjs"
697
+ },
698
+ "engines": {
699
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
700
+ }
701
+ },
702
+ "node_modules/next": {
703
+ "version": "16.2.3",
704
+ "resolved": "https://registry.npmjs.org/next/-/next-16.2.3.tgz",
705
+ "integrity": "sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA==",
706
+ "license": "MIT",
707
+ "dependencies": {
708
+ "@next/env": "16.2.3",
709
+ "@swc/helpers": "0.5.15",
710
+ "baseline-browser-mapping": "^2.9.19",
711
+ "caniuse-lite": "^1.0.30001579",
712
+ "postcss": "8.4.31",
713
+ "styled-jsx": "5.1.6"
714
+ },
715
+ "bin": {
716
+ "next": "dist/bin/next"
717
+ },
718
+ "engines": {
719
+ "node": ">=20.9.0"
720
+ },
721
+ "optionalDependencies": {
722
+ "@next/swc-darwin-arm64": "16.2.3",
723
+ "@next/swc-darwin-x64": "16.2.3",
724
+ "@next/swc-linux-arm64-gnu": "16.2.3",
725
+ "@next/swc-linux-arm64-musl": "16.2.3",
726
+ "@next/swc-linux-x64-gnu": "16.2.3",
727
+ "@next/swc-linux-x64-musl": "16.2.3",
728
+ "@next/swc-win32-arm64-msvc": "16.2.3",
729
+ "@next/swc-win32-x64-msvc": "16.2.3",
730
+ "sharp": "^0.34.5"
731
+ },
732
+ "peerDependencies": {
733
+ "@opentelemetry/api": "^1.1.0",
734
+ "@playwright/test": "^1.51.1",
735
+ "babel-plugin-react-compiler": "*",
736
+ "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
737
+ "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
738
+ "sass": "^1.3.0"
739
+ },
740
+ "peerDependenciesMeta": {
741
+ "@opentelemetry/api": {
742
+ "optional": true
743
+ },
744
+ "@playwright/test": {
745
+ "optional": true
746
+ },
747
+ "babel-plugin-react-compiler": {
748
+ "optional": true
749
+ },
750
+ "sass": {
751
+ "optional": true
752
+ }
753
+ }
754
+ },
755
+ "node_modules/openai": {
756
+ "version": "6.34.0",
757
+ "resolved": "https://registry.npmjs.org/openai/-/openai-6.34.0.tgz",
758
+ "integrity": "sha512-yEr2jdGf4tVFYG6ohmr3pF6VJuveP0EA/sS8TBx+4Eq5NT10alu5zg2dmxMXMgqpihRDQlFGpRt2XwsGj+Fyxw==",
759
+ "license": "Apache-2.0",
760
+ "bin": {
761
+ "openai": "bin/cli"
762
+ },
763
+ "peerDependencies": {
764
+ "ws": "^8.18.0",
765
+ "zod": "^3.25 || ^4.0"
766
+ },
767
+ "peerDependenciesMeta": {
768
+ "ws": {
769
+ "optional": true
770
+ },
771
+ "zod": {
772
+ "optional": true
773
+ }
774
+ }
775
+ },
776
+ "node_modules/picocolors": {
777
+ "version": "1.1.1",
778
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
779
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
780
+ "license": "ISC"
781
+ },
782
+ "node_modules/postcss": {
783
+ "version": "8.4.31",
784
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
785
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
786
+ "funding": [
787
+ {
788
+ "type": "opencollective",
789
+ "url": "https://opencollective.com/postcss/"
790
+ },
791
+ {
792
+ "type": "tidelift",
793
+ "url": "https://tidelift.com/funding/github/npm/postcss"
794
+ },
795
+ {
796
+ "type": "github",
797
+ "url": "https://github.com/sponsors/ai"
798
+ }
799
+ ],
800
+ "license": "MIT",
801
+ "dependencies": {
802
+ "nanoid": "^3.3.6",
803
+ "picocolors": "^1.0.0",
804
+ "source-map-js": "^1.0.2"
805
+ },
806
+ "engines": {
807
+ "node": "^10 || ^12 || >=14"
808
+ }
809
+ },
810
+ "node_modules/react": {
811
+ "version": "19.2.4",
812
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
813
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
814
+ "license": "MIT",
815
+ "peer": true,
816
+ "engines": {
817
+ "node": ">=0.10.0"
818
+ }
819
+ },
820
+ "node_modules/react-dom": {
821
+ "version": "19.2.4",
822
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
823
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
824
+ "license": "MIT",
825
+ "peer": true,
826
+ "dependencies": {
827
+ "scheduler": "^0.27.0"
828
+ },
829
+ "peerDependencies": {
830
+ "react": "^19.2.4"
831
+ }
832
+ },
833
+ "node_modules/scheduler": {
834
+ "version": "0.27.0",
835
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
836
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
837
+ "license": "MIT"
838
+ },
839
+ "node_modules/semver": {
840
+ "version": "7.7.4",
841
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
842
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
843
+ "license": "ISC",
844
+ "optional": true,
845
+ "bin": {
846
+ "semver": "bin/semver.js"
847
+ },
848
+ "engines": {
849
+ "node": ">=10"
850
+ }
851
+ },
852
+ "node_modules/sharp": {
853
+ "version": "0.34.5",
854
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
855
+ "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
856
+ "hasInstallScript": true,
857
+ "license": "Apache-2.0",
858
+ "optional": true,
859
+ "dependencies": {
860
+ "@img/colour": "^1.0.0",
861
+ "detect-libc": "^2.1.2",
862
+ "semver": "^7.7.3"
863
+ },
864
+ "engines": {
865
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
866
+ },
867
+ "funding": {
868
+ "url": "https://opencollective.com/libvips"
869
+ },
870
+ "optionalDependencies": {
871
+ "@img/sharp-darwin-arm64": "0.34.5",
872
+ "@img/sharp-darwin-x64": "0.34.5",
873
+ "@img/sharp-libvips-darwin-arm64": "1.2.4",
874
+ "@img/sharp-libvips-darwin-x64": "1.2.4",
875
+ "@img/sharp-libvips-linux-arm": "1.2.4",
876
+ "@img/sharp-libvips-linux-arm64": "1.2.4",
877
+ "@img/sharp-libvips-linux-ppc64": "1.2.4",
878
+ "@img/sharp-libvips-linux-riscv64": "1.2.4",
879
+ "@img/sharp-libvips-linux-s390x": "1.2.4",
880
+ "@img/sharp-libvips-linux-x64": "1.2.4",
881
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
882
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
883
+ "@img/sharp-linux-arm": "0.34.5",
884
+ "@img/sharp-linux-arm64": "0.34.5",
885
+ "@img/sharp-linux-ppc64": "0.34.5",
886
+ "@img/sharp-linux-riscv64": "0.34.5",
887
+ "@img/sharp-linux-s390x": "0.34.5",
888
+ "@img/sharp-linux-x64": "0.34.5",
889
+ "@img/sharp-linuxmusl-arm64": "0.34.5",
890
+ "@img/sharp-linuxmusl-x64": "0.34.5",
891
+ "@img/sharp-wasm32": "0.34.5",
892
+ "@img/sharp-win32-arm64": "0.34.5",
893
+ "@img/sharp-win32-ia32": "0.34.5",
894
+ "@img/sharp-win32-x64": "0.34.5"
895
+ }
896
+ },
897
+ "node_modules/source-map-js": {
898
+ "version": "1.2.1",
899
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
900
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
901
+ "license": "BSD-3-Clause",
902
+ "engines": {
903
+ "node": ">=0.10.0"
904
+ }
905
+ },
906
+ "node_modules/styled-jsx": {
907
+ "version": "5.1.6",
908
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
909
+ "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
910
+ "license": "MIT",
911
+ "dependencies": {
912
+ "client-only": "0.0.1"
913
+ },
914
+ "engines": {
915
+ "node": ">= 12.0.0"
916
+ },
917
+ "peerDependencies": {
918
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
919
+ },
920
+ "peerDependenciesMeta": {
921
+ "@babel/core": {
922
+ "optional": true
923
+ },
924
+ "babel-plugin-macros": {
925
+ "optional": true
926
+ }
927
+ }
928
+ },
929
+ "node_modules/tslib": {
930
+ "version": "2.8.1",
931
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
932
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
933
+ "license": "0BSD"
934
+ }
935
+ }
936
+ }
pr_review_dashboard/package.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "pr_review_dashboard",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start"
9
+ },
10
+ "dependencies": {
11
+ "next": "16.2.3",
12
+ "openai": "^6.34.0",
13
+ "react": "19.2.4",
14
+ "react-dom": "19.2.4"
15
+ }
16
+ }
pr_review_dashboard/public/file.svg ADDED
pr_review_dashboard/public/globe.svg ADDED
pr_review_dashboard/public/next.svg ADDED
pr_review_dashboard/public/vercel.svg ADDED
pr_review_dashboard/public/window.svg ADDED
start.sh CHANGED
@@ -1,12 +1,18 @@
1
  #!/bin/bash
2
 
 
 
3
  # Start the FastAPI backend on port 8000
4
  echo "Starting FastAPI Backend on port 8000..."
5
  uvicorn server.app:app --host 0.0.0.0 --port 8000 &
6
 
7
- # Start the Streamlit dashboard internally on port 8501
8
- echo "Starting Streamlit Dashboard on port 8501..."
9
- streamlit run app.py --server.port 8501 --server.address 0.0.0.0 --server.headless true &
 
 
 
 
10
 
11
  # Start Nginx in the foreground to keep the container alive
12
  echo "Starting Nginx Proxy on port 7860..."
 
1
  #!/bin/bash
2
 
3
+ echo "===== Application Startup at $(date) ====="
4
+
5
  # Start the FastAPI backend on port 8000
6
  echo "Starting FastAPI Backend on port 8000..."
7
  uvicorn server.app:app --host 0.0.0.0 --port 8000 &
8
 
9
+ # Start the Next.js frontend on port 3000
10
+ echo "Starting Next.js Dashboard on port 3000..."
11
+ cd pr_review_dashboard && npm start -- -p 3000 &
12
+ cd /app
13
+
14
+ # Give services a moment to start
15
+ sleep 3
16
 
17
  # Start Nginx in the foreground to keep the container alive
18
  echo "Starting Nginx Proxy on port 7860..."