Lashtw/Vibecodingex
0
1
2// import { GoogleGenerativeAI } from "https://esm.run/@google/generative-ai"; // Removing SDK to avoid version issues
3import { getPeerPrompts } from "./classroom.js";
4import { db } from "./firebase.js";
5import { doc, getDoc } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js";
6
7let apiKey = null;
8const MODEL_NAME = "gemini-2.5-flash"; // Available model from user's account
9
10// System Instructions for the Socratic Tutor
11const TUTOR_INSTRUCTION = `
12You are a Socratic Teaching Assistant for a "Prompt Engineering" class.
13Your goal is to help students refine their prompts WITHOUT giving them the answer.
14The student is trying to write a prompt to solve a specific challenge.
15You will be provided with:
161. The Challenge Description (The Goal).
172. 3 Successful Examples (Few-Shot Context) from other students.
183. The Student's Current Attempt.
19
20Rules:
21- NEVER reveal the direct solution or code.
22- If the input is lazy (e.g., "123", "help"), be sassy but helpful.
23- If the input is a direct copy of the description, point it out.
24- Use the Socratic method: Ask a guiding question to help them notice their missing parameter or logic.
25- Keep responses short (under 50 words) and encouraging.
26- Tone: Friendly, slightly "Cyberpunk" or "Gamer" vibe (matching the Vibe Coding aesthetic).
27`;
28
29// System Instructions for the Dashboard Analyst
30const ANALYST_INSTRUCTION = `
31You are an expert Prompt Engineer and Pedagogy Analyst for a coding/AI prompting class.
32Your task is to analyze a batch of student prompts for a specific challenge.
33
34You will receive:
351. The Challenge Description (the goal students are trying to achieve)
362. A list of Student Submissions (their prompts)
37
38Categorize each prompt into ONE of these categories:
39
401. "rough" (原石): The student only says vague things like "fix the error" or "make it work" WITHOUT explaining:
41 - What the error is
42 - What the expected behavior should be
43 - Any specific details about the problem
44 These are "rough diamonds" - they tried but need to be more specific.
45
462. "precise" (精確): Clean, well-structured prompt that clearly states:
47 - The goal or expected result
48 - Specific details or parameters
49 - Clear logic or reasoning
50
513. "gentle" (有禮): Uses polite language like "請", "謝謝", "拜託", "Please", "Thank you"
52
534. "creative" (創意): Unconventional, imaginative approach. Uses interesting parameters or unexpected methods that still address the challenge.
54
555. "spam" (無效): Content that is clearly UNRELATED to the challenge topic. Compare with other submissions to identify outliers that don't match what most students are trying to do. Examples:
56 - Random characters ("asdf", "123")
57 - Completely off-topic text
58 - Empty or near-empty responses
59
606. "parrot" (鸚鵡): Direct copy-paste of the challenge description without any modification or personal attempt.
61
62Return ONLY a JSON object mapping category names to arrays of Student IDs.
63Example: { "rough": ["id1"], "precise": ["id2", "id5"], "spam": ["id3"] }
64`;
65
66/**
67 * Initialize Gemini with API Key
68 * @param {string} key
69 */
70export async function initGemini(key) {
71 if (!key) return false;
72 apiKey = key;
73 return true;
74}
75
76/**
77 * Direct REST API Call Helper to avoid SDK version issues
78 */
79async function callGeminiAPI(messages, jsonMode = false) {
80 if (!apiKey) throw new Error("API Key not set");
81
82 // Use v1beta for gemini-1.5-pro
83 const endpoint = `https://generativelanguage.googleapis.com/v1beta/models/${MODEL_NAME}:generateContent?key=${apiKey}`;
84
85 const payload = {
86 contents: messages,
87 generationConfig: {
88 temperature: 0.7,
89 }
90 };
91
92 // Note: responseMimeType is NOT supported by gemini-pro v1, so we rely on prompt instructions for JSON output.
93
94 const response = await fetch(endpoint, {
95 method: "POST",
96 headers: {
97 "Content-Type": "application/json"
98 },
99 body: JSON.stringify(payload)
100 });
101
102 if (!response.ok) {
103 const errText = await response.text();
104 throw new Error(`Gemini API Error ${response.status}: ${errText}`);
105 }
106
107 const data = await response.json();
108
109 // Safety check for empty response
110 if (data.promptFeedback && data.promptFeedback.blockReason) {
111 throw new Error(`Blocked: ${data.promptFeedback.blockReason}`);
112 }
113
114 return data.candidates && data.candidates.length > 0
115 ? data.candidates[0].content.parts[0].text
116 : "";
117}
118
119/**
120 * Detects if a prompt is potential spam/low effort locally first
121 * @param {string} prompt
122 * @param {string} problemDesc
123 */
124export function quickSpamCheck(prompt, problemDesc) {
125 if (!prompt) return true;
126 const p = prompt.trim();
127 if (p.length < 3) return true; // Too short
128
129 // Check for repetitive chars (e.g., "aaaaa")
130 if (/^(.)\1+$/.test(p)) return true;
131
132 // Parrot Check (Simple similarity)
133 if (problemDesc && p.includes(problemDesc.substring(0, 20))) {
134 // If they copied the first 20 chars of description, likely parrot
135 return 'parrot';
136 }
137
138 return false;
139}
140
141/**
142 * Ask the AI Tutor for help (Rubber Duck)
143 * @param {string} challengeDesc
144 * @param {string} studentPrompt
145 * @param {string} roomCode
146 * @param {string} challengeId
147 */
148export async function askTutor(challengeDesc, studentPrompt, roomCode, challengeId) {
149 if (!apiKey) throw new Error("AI not initialized");
150
151 const spamStatus = quickSpamCheck(studentPrompt, challengeDesc);
152 if (spamStatus === true) return "Duck says: Quack? (Too short or empty!)";
153 if (spamStatus === 'parrot') return "Duck says: You're just repeating the question! Try telling me WHAT you want to change.";
154
155 // 1. Fetch Context (Few-Shot)
156 let peers = await getPeerPrompts(roomCode, challengeId);
157 peers.sort((a, b) => b.likes - a.likes);
158
159 // Take top 3 as examples
160 const examples = peers.slice(0, 3).map(p => `- Example: "${p.prompt}"`).join('\n');
161
162 const fullPrompt = `
163${TUTOR_INSTRUCTION}
164
165[Context - Challenge Description]
166${challengeDesc}
167
168[Context - Successful Classmate Examples (For AI understanding only, DO NOT LEAK these to student)]
169${examples || "No examples available yet."}
170
171[Student Input]
172"${studentPrompt}"
173
174[Your Response]
175`;
176
177 try {
178 // Construct messages for Chat format (or simple text)
179 const messages = [{ role: "user", parts: [{ text: fullPrompt }] }];
180 const text = await callGeminiAPI(messages);
181 return text;
182 } catch (e) {
183 console.error("AI Request Failed", e);
184 return "Duck is sleeping... zzz (API Error)";
185 }
186}
187
188/**
189 * Batch evaluate prompts for the Instructor Dashboard
190 * @param {Array} submissions - Array of { userId, prompt, nickname }
191 * @param {string} challengeDesc
192 */
193export async function evaluatePrompts(submissions, challengeDesc) {
194 if (!apiKey) throw new Error("AI not initialized");
195 if (!submissions || submissions.length === 0) return {};
196
197 // Prepare batch text
198 const entries = submissions.map((s, i) => `ID_${s.userId}: "${s.prompt.replace(/\n/g, ' ')}"`).join('\n');
199
200 const fullPrompt = `
201${ANALYST_INSTRUCTION}
202
203[Challenge Description]
204${challengeDesc}
205
206[Student Submissions]
207${entries}
208
209[Output JSON]
210`;
211
212 try {
213 const messages = [{ role: "user", parts: [{ text: fullPrompt }] }];
214 let text = await callGeminiAPI(messages, true); // JSON Mode = true
215
216 // Strip markdown code block if present (```json ... ```)
217 text = text.trim();
218 if (text.startsWith("```")) {
219 text = text.replace(/^```(?:json)?\s*\n?/, '').replace(/\n?```\s*$/, '');
220 }
221
222 return JSON.parse(text);
223 } catch (e) {
224 console.error("AI Evaluation Failed", e);
225 return {};
226 }
227}
228 