Download lib/llm.ts from d3evil4/scrape: direct link, hf CLI and curl.
- Browser
- Download file 3.27 kB
-
https://huggingface.co/spaces/d3evil4/scrape/resolve/main/lib/llm.ts
- Command line
-
hf download hf://spaces/d3evil4/scrape/lib/llm.ts
-
curl -L -o llm.ts https://huggingface.co/spaces/d3evil4/scrape/resolve/main/lib/llm.ts
3.27 kB
| import { | |
| enforceScriptLimit, | |
| MAX_SCRIPT_CHARS, | |
| MIN_SCRIPT_CHARS, | |
| TARGET_SECONDS, | |
| } from "@/lib/script-limits"; | |
| import type { GitHubRepoData } from "@/types"; | |
| // ponytail: 2.5 Flash is closed to new API keys; 3.8 Flash is Google's suggested replacement | |
| const GEMINI_MODEL = "gemini-3.8-flash"; | |
| interface GeminiResponse { | |
| candidates?: Array<{ | |
| content?: { parts?: Array<{ text?: string }> }; | |
| }>; | |
| } | |
| export async function callLlm( | |
| prompt: string, | |
| { maxTokens = 300, temperature = 0.8, timeoutMs = 150_000 } = {}, | |
| ): Promise<string> { | |
| const apiKey = process.env.GEMINI_API_KEY; | |
| if (!apiKey) throw new Error("GEMINI_API_KEY is not set"); | |
| // ponytail: 3 tries with 2s/4s backoff on 429/503 ("high demand") — these are transient | |
| let response!: Response; | |
| for (let attempt = 1; attempt <= 3; attempt++) { | |
| response = await fetch( | |
| `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json", "x-goog-api-key": apiKey }, | |
| body: JSON.stringify({ | |
| contents: [{ role: "user", parts: [{ text: prompt }] }], | |
| generationConfig: { | |
| temperature, | |
| maxOutputTokens: maxTokens, | |
| // ponytail: Flash thinks by default and burns maxOutputTokens on it; off for short copy | |
| thinkingConfig: { thinkingBudget: 0 }, | |
| }, | |
| }), | |
| signal: AbortSignal.timeout(timeoutMs), | |
| }, | |
| ); | |
| if (response.status !== 429 && response.status !== 503) break; | |
| if (attempt < 3) await new Promise((r) => setTimeout(r, 2000 * attempt)); | |
| } | |
| if (!response.ok) { | |
| const errorText = await response.text(); | |
| throw new Error(`Gemini request failed (${response.status}): ${errorText.slice(0, 200)}`); | |
| } | |
| const data = (await response.json()) as GeminiResponse; | |
| const raw = data.candidates?.[0]?.content?.parts | |
| ?.map((p) => p.text ?? "") | |
| .join("") | |
| .trim(); | |
| if (!raw) throw new Error("Gemini returned an empty response"); | |
| return raw; | |
| } | |
| export async function generateIntroScript(repo: GitHubRepoData): Promise<string> { | |
| const prompt = `You write viral ${TARGET_SECONDS} second TikTok/Reels intro scripts for GitHub repos. | |
| Repo: ${repo.fullName} | |
| Description: ${repo.description || "No description"} | |
| Language: ${repo.language} | |
| Stars: ${repo.stars} | |
| Topics: ${repo.topics.join(", ") || "none"} | |
| README excerpt: | |
| ${repo.readme.slice(0, 2500)} | |
| Write a punchy spoken intro script that: | |
| - Takes ${TARGET_SECONDS} seconds to read aloud: 65-80 words, between ${MIN_SCRIPT_CHARS} and ${MAX_SCRIPT_CHARS} characters | |
| - Never shorter than ${MIN_SCRIPT_CHARS} characters — add a concrete detail from the README rather than padding | |
| - Opens with a hook about what the project does | |
| - Highlights 2-3 key features or use cases | |
| - Ends by asking viewers to comment below to get the repo link directly in their DM | |
| - Uses short sentences, no markdown, no emojis | |
| - Sounds natural when read aloud | |
| Return ONLY the script text, nothing else.`; | |
| const raw = await callLlm(prompt, { maxTokens: 300 }); | |
| const script = enforceScriptLimit(raw.replace(/^["']|["']$/g, "")); | |
| if (!script) throw new Error("LLM returned empty script"); | |
| return script; | |
| } | |