Skip to content

Commit b8c7f86

Browse files
committed
feat: Add user feedback mechanism for translation quality
- Add thumbs up/down buttons next to Copy button in UI - Implement feedback modal for negative feedback with optional comment - Create /v1/feedback backend endpoint to store feedback - Store feedback in Cloudflare KV with metadata - Support both light and dark themes - Pass original code through translation pipeline for feedback context - Add comprehensive documentation in FEEDBACK_FEATURE.md This enables users to report translation quality issues directly, creating a feedback loop to improve AI prompts and translation quality.
1 parent 013555e commit b8c7f86

6 files changed

Lines changed: 546 additions & 12 deletions

File tree

FEEDBACK_FEATURE.md

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# User Feedback Mechanism Feature
2+
3+
## Overview
4+
This feature adds a user feedback mechanism to collect quality ratings and comments about AI-generated code translations. Users can now provide feedback directly in the extension UI using thumbs up/down buttons.
5+
6+
## Features Implemented
7+
8+
### 1. **Feedback Buttons in UI**
9+
- Added 👍 (Good) and 👎 (Bad) buttons next to the Copy button
10+
- Buttons appear for each translated code snippet
11+
- Visual feedback when clicked (button highlights briefly)
12+
13+
### 2. **Feedback Modal for Negative Feedback**
14+
- Clicking 👎 opens a modal dialog
15+
- Users can optionally describe what was wrong with the translation
16+
- Textarea with 1000 character limit
17+
- Cancel and Submit buttons
18+
- Works in both light and dark themes
19+
20+
### 3. **Backend Endpoint**
21+
- New `/v1/feedback` endpoint to receive feedback
22+
- Stores feedback in Cloudflare KV storage
23+
- Rate-limited along with other endpoints
24+
- CORS-enabled for extension requests
25+
26+
### 4. **Data Collection**
27+
Feedback includes:
28+
- `isPositive`: boolean (true for 👍, false for 👎)
29+
- `targetLanguage`: The language the code was translated to
30+
- `originalCode`: The original code snippet
31+
- `translatedCode`: The AI-generated translation
32+
- `comment`: Optional user comment (required for negative feedback)
33+
- `timestamp`: ISO 8601 timestamp of when feedback was submitted
34+
35+
## Files Modified
36+
37+
### Frontend
38+
1. **`frontend/scripts/ui.js`**
39+
- Added feedback buttons UI
40+
- Created feedback modal component
41+
- Added `sendFeedback()` and `showFeedbackModal()` functions
42+
- Updated styles for dark mode support
43+
- Modified function signature to accept `originalCode` parameter
44+
45+
2. **`frontend/scripts/content.js`**
46+
- Updated calls to `injectOrUpdateTranslations()` to pass original code
47+
48+
3. **`frontend/background.js`**
49+
- Added message handler for `SUBMIT_FEEDBACK` type
50+
- Sends feedback to backend `/v1/feedback` endpoint
51+
52+
### Backend
53+
4. **`backend/src/index.ts`**
54+
- Added `FEEDBACK_STORE` KV namespace to Env interface
55+
- Created `handleFeedback()` function
56+
- Added `/v1/feedback` route handler
57+
- Generates unique feedback IDs
58+
- Stores feedback with metadata for easy querying
59+
60+
5. **`backend/wrangler.jsonc`**
61+
- Added `FEEDBACK_STORE` KV namespace binding
62+
63+
## Setup Instructions
64+
65+
### 1. Create KV Namespace for Feedback Storage
66+
67+
```bash
68+
# Navigate to backend directory
69+
cd backend
70+
71+
# Create production KV namespace
72+
wrangler kv:namespace create "FEEDBACK_STORE"
73+
74+
# Create preview KV namespace for development
75+
wrangler kv:namespace create "FEEDBACK_STORE" --preview
76+
```
77+
78+
### 2. Update wrangler.jsonc
79+
80+
Replace the placeholder IDs in `wrangler.jsonc`:
81+
82+
```jsonc
83+
{
84+
"binding": "FEEDBACK_STORE",
85+
"id": "<your_feedback_kv_id>", // Use the ID from production namespace
86+
"preview_id": "<your_feedback_preview_kv_id>" // Use the ID from preview namespace
87+
}
88+
```
89+
90+
### 3. Install Dependencies & Build
91+
92+
```bash
93+
# Install backend dependencies
94+
cd backend
95+
npm install
96+
97+
# Install frontend dependencies
98+
cd ../frontend
99+
npm install
100+
101+
# Build the extension
102+
node build.js
103+
```
104+
105+
### 4. Deploy Backend
106+
107+
```bash
108+
cd backend
109+
npm run deploy
110+
```
111+
112+
## Usage
113+
114+
1. **User selects code** on a webpage using the extension
115+
2. **Translation appears** with Copy, 👍, and 👎 buttons
116+
3. **For good translations**: Click 👍 - feedback is submitted instantly
117+
4. **For bad translations**:
118+
- Click 👎
119+
- Modal appears asking "What was wrong with this translation?"
120+
- User can optionally provide details
121+
- Click "Submit Feedback"
122+
5. **Visual confirmation**: Button highlights briefly to confirm submission
123+
124+
## Accessing Feedback Data
125+
126+
### Via Wrangler CLI
127+
128+
```bash
129+
# List all feedback entries
130+
wrangler kv:key list --binding FEEDBACK_STORE
131+
132+
# Get specific feedback
133+
wrangler kv:key get "feedback_<id>" --binding FEEDBACK_STORE
134+
135+
# Get feedback with metadata
136+
wrangler kv:key get "feedback_<id>" --binding FEEDBACK_STORE --preview false
137+
```
138+
139+
### Via Cloudflare Dashboard
140+
141+
1. Go to Workers & Pages > KV
142+
2. Select the FEEDBACK_STORE namespace
143+
3. Browse or search feedback entries
144+
145+
### Programmatic Access
146+
147+
Add an admin endpoint to query feedback (optional future enhancement):
148+
149+
```typescript
150+
// Example: Get negative feedback
151+
async function getNegativeFeedback(env: Env, limit = 100) {
152+
const list = await env.FEEDBACK_STORE.list({ limit });
153+
const feedback = [];
154+
155+
for (const key of list.keys) {
156+
if (key.metadata?.isPositive === false) {
157+
const data = await env.FEEDBACK_STORE.get(key.name);
158+
feedback.push(JSON.parse(data));
159+
}
160+
}
161+
162+
return feedback;
163+
}
164+
```
165+
166+
## Data Schema
167+
168+
```typescript
169+
interface Feedback {
170+
isPositive: boolean; // true for 👍, false for 👎
171+
targetLanguage: string; // e.g., "Python", "JavaScript"
172+
originalCode: string; // The source code
173+
translatedCode: string; // The AI translation
174+
comment?: string; // Optional user comment
175+
timestamp: string; // ISO 8601 timestamp
176+
}
177+
```
178+
179+
## Future Enhancements
180+
181+
1. **Analytics Dashboard**: Create a dashboard to visualize feedback trends
182+
2. **Export Functionality**: Bulk export feedback to CSV/JSON for analysis
183+
3. **AI Model Training**: Use negative feedback to fine-tune prompts
184+
4. **User Identification**: Add optional user ID for tracking repeat issues
185+
5. **Feedback Categories**: Add predefined categories (syntax error, incorrect logic, etc.)
186+
6. **Thank You Message**: Show appreciation message after feedback submission
187+
7. **Feedback Statistics**: Show translation success rate per language
188+
189+
## Privacy & Data Handling
190+
191+
- No personally identifiable information is collected
192+
- Original and translated code is stored for quality improvement only
193+
- Consider adding a data retention policy (e.g., auto-delete after 90 days)
194+
- Add a privacy notice in the extension popup
195+
196+
## Testing
197+
198+
1. **Manual Testing**:
199+
- Test thumbs up feedback
200+
- Test thumbs down with and without comments
201+
- Test modal cancel functionality
202+
- Verify dark mode styling
203+
- Check rate limiting
204+
205+
2. **Backend Testing**:
206+
```bash
207+
cd backend
208+
npm test
209+
```
210+
211+
## Contributing
212+
213+
When working on this feature:
214+
- Test both light and dark themes
215+
- Ensure modal is accessible (keyboard navigation)
216+
- Test with long comments (1000 chars)
217+
- Verify feedback submission on slow networks
218+
- Check error handling for network failures
219+
220+
## License
221+
222+
Same as the main CodeTranslateAI project.

backend/src/index.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { GoogleGenerativeAI } from '@google/generative-ai';
33
export interface Env {
44
RATE_LIMIT: KVNamespace;
55
GEMINI_API_KEY: string;
6+
FEEDBACK_STORE: KVNamespace;
67
}
78

89
const corsHeaders = {
@@ -94,6 +95,53 @@ ${code}`;
9495
});
9596
}
9697

98+
async function handleFeedback(request: Request, env: Env) {
99+
const feedback = await request.json<{
100+
isPositive: boolean;
101+
targetLanguage: string;
102+
originalCode: string;
103+
translatedCode: string;
104+
comment?: string;
105+
timestamp: string;
106+
}>();
107+
108+
if (!feedback.targetLanguage || !feedback.originalCode || !feedback.translatedCode) {
109+
return new Response(JSON.stringify({ error: "Missing required feedback fields." }), {
110+
status: 400,
111+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
112+
});
113+
}
114+
115+
// Generate a unique ID for the feedback
116+
const feedbackId = `feedback_${Date.now()}_${Math.random().toString(36).substring(7)}`;
117+
118+
// Store feedback in KV
119+
try {
120+
await env.FEEDBACK_STORE.put(feedbackId, JSON.stringify(feedback), {
121+
metadata: {
122+
isPositive: feedback.isPositive,
123+
targetLanguage: feedback.targetLanguage,
124+
timestamp: feedback.timestamp
125+
}
126+
});
127+
128+
return new Response(JSON.stringify({
129+
success: true,
130+
message: "Feedback submitted successfully",
131+
feedbackId
132+
}), {
133+
status: 200,
134+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
135+
});
136+
} catch (error) {
137+
console.error('Error storing feedback:', error);
138+
return new Response(JSON.stringify({ error: 'Failed to store feedback.' }), {
139+
status: 500,
140+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
141+
});
142+
}
143+
}
144+
97145
export default {
98146
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
99147
if (request.method === 'OPTIONS') {
@@ -125,6 +173,10 @@ export default {
125173
return await handleExplain(request, model);
126174
}
127175

176+
if (path === '/v1/feedback') {
177+
return await handleFeedback(request, env);
178+
}
179+
128180
return new Response(JSON.stringify({ error: 'Route not found.' }), {
129181
status: 404,
130182
headers: { ...corsHeaders, 'Content-Type': 'application/json' },

backend/wrangler.jsonc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
{
1515
"binding": "RATE_LIMIT",
1616
"id": "<your_kv_id>"
17+
},
18+
{
19+
"binding": "FEEDBACK_STORE",
20+
"id": "<your_feedback_kv_id>",
21+
"preview_id": "<your_feedback_preview_kv_id>"
1722
}
1823
]
1924

frontend/background.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,39 @@ chrome.runtime.onMessage.addListener((request, _, sendResponse) => {
3939

4040
return true;
4141
}
42+
43+
if (request.type === "SUBMIT_FEEDBACK") {
44+
const BACKEND_URL = process.env.BACKEND_URL;
45+
const feedbackEndpoint = `${BACKEND_URL}/v1/feedback`;
46+
47+
fetch(feedbackEndpoint, {
48+
method: "POST",
49+
headers: {
50+
"Content-Type": "application/json",
51+
},
52+
body: JSON.stringify(request.feedback),
53+
})
54+
.then((response) => {
55+
if (!response.ok) {
56+
throw new Error(
57+
`Network response was not ok: ${response.statusText}`
58+
);
59+
}
60+
return response.json();
61+
})
62+
.then((data) => {
63+
sendResponse({ success: true, data });
64+
})
65+
.catch((error) => {
66+
console.error("Error submitting feedback:", error);
67+
sendResponse({
68+
success: false,
69+
error: `Failed to submit feedback: ${error.message}`,
70+
});
71+
});
72+
73+
return true;
74+
}
4275
});
4376
//Default commmand = Alt+T , Mac = Option+T
4477
chrome.commands.onCommand.addListener((command) => {

frontend/scripts/content.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ async function handleElementClick(e) {
2525
const cachedData = await getFromCache(cacheKey);
2626

2727
if (cachedData && cachedData[lang]) {
28-
injectOrUpdateTranslations(cachedData, clickedElement, originalWidth,theme);
28+
injectOrUpdateTranslations(cachedData, clickedElement, originalWidth, theme, selectedCode);
2929
return;
3030
}
3131

@@ -58,7 +58,7 @@ async function handleElementClick(e) {
5858
const newData = cachedData || {};
5959
newData[lang] = cleaned;
6060
await saveToCache(cacheKey, newData, 10);
61-
injectOrUpdateTranslations(newData, clickedElement, originalWidth,theme);
61+
injectOrUpdateTranslations(newData, clickedElement, originalWidth, theme, selectedCode);
6262
}
6363
}
6464
);

0 commit comments

Comments
 (0)