| import { |
| enforceScriptLimit, |
| MAX_SCRIPT_CHARS, |
| MIN_SCRIPT_CHARS, |
| TARGET_SECONDS, |
| } from "@/lib/script-limits"; |
| import type { GitHubRepoData } from "@/types"; |
|
|
| const DEFAULT_LLM_URL = "https://cocoxkhushal.online/v1/chat/completions"; |
|
|
| interface ChatCompletionResponse { |
| choices?: Array<{ |
| message?: { |
| content?: string; |
| }; |
| }>; |
| } |
|
|
| function getLlmUrl(): string { |
| return process.env.LLM_API_URL || DEFAULT_LLM_URL; |
| } |
|
|
| function buildHeaders(): Record<string, string> { |
| const headers: Record<string, string> = { |
| accept: "application/json", |
| "Content-Type": "application/json", |
| }; |
|
|
| const apiKey = process.env.LLM_API_KEY || process.env.OPENAI_API_KEY; |
| if (apiKey) { |
| headers.Authorization = `Bearer ${apiKey}`; |
| } |
|
|
| return headers; |
| } |
|
|
| |
| export async function callLlm( |
| prompt: string, |
| { maxTokens = 300, temperature = 0.8, timeoutMs = 150_000 } = {}, |
| ): Promise<string> { |
| const body: Record<string, unknown> = { |
| messages: [{ role: "user", content: prompt }], |
| temperature, |
| top_p: 1, |
| max_tokens: maxTokens, |
| stream: false, |
| top_k: 0, |
| presence_penalty: 0, |
| frequency_penalty: 0, |
| }; |
|
|
| const model = process.env.LLM_MODEL || process.env.OPENAI_MODEL; |
| if (model) body.model = model; |
|
|
| |
| |
| const response = await fetch(getLlmUrl(), { |
| method: "POST", |
| headers: buildHeaders(), |
| body: JSON.stringify(body), |
| signal: AbortSignal.timeout(timeoutMs), |
| }); |
|
|
| if (!response.ok) { |
| const errorText = await response.text(); |
| throw new Error(`LLM request failed (${response.status}): ${errorText.slice(0, 200)}`); |
| } |
|
|
| const data = (await response.json()) as ChatCompletionResponse; |
| const raw = data.choices?.[0]?.message?.content?.trim(); |
| if (!raw) throw new Error("LLM 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 with a call to check the repo |
| - 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; |
| } |
|
|