Team Ai
Apppublic

Lashtw/Vibecodingex

sourceHugging Faceupdated 7mo agoView on Hugging Face
0likes
gemini.js228 linesDownload Raw Back to services
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