Module split: extract Gemini-specific helpers to server/gemini-helpers.js
• PRICING table — per-1M-token rates by model • calcCost(model, usage) — Gemini usage object → cost record • buildAnalysisPrompt(...) — JSON-output topic-analysis prompt These all share the Gemini contract — pricing schema, usage shape, and prompt format. When we add other providers, each gets its own provider-specific helpers file; this becomes the basis of the Gemini provider implementation. server/index.js: 2828 → 2758 lines. Smoke tested: server boots; /api/license-status, /api/health, and / (frontend) all respond. No behavior change.
This commit is contained in:
+3
-73
@@ -18,6 +18,7 @@ import {
|
||||
safeText,
|
||||
retryGemini,
|
||||
} from "./util.js";
|
||||
import { calcCost, buildAnalysisPrompt } from "./gemini-helpers.js";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const app = express();
|
||||
@@ -427,41 +428,7 @@ async function autoUpdateYtdlp() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Pricing (per 1M tokens) ───────────────────────────────────────────────
|
||||
|
||||
const PRICING = {
|
||||
"gemini-3-flash-preview": { input: 0.50, output: 3.00, thinking: 3.00 },
|
||||
"gemini-3-pro-preview": { input: 2.00, output: 12.00, thinking: 12.00 },
|
||||
"gemini-3.1-pro-preview": { input: 2.00, output: 12.00, thinking: 12.00 },
|
||||
"gemini-2.5-flash": { input: 0.15, output: 0.60, thinking: 0.60 },
|
||||
// Fallback for unknown models
|
||||
"default": { input: 1.00, output: 5.00, thinking: 5.00 },
|
||||
};
|
||||
|
||||
function calcCost(modelName, usage) {
|
||||
const rates = PRICING[modelName] || PRICING["default"];
|
||||
const inputTokens = usage.promptTokenCount || 0;
|
||||
const outputTokens = usage.candidatesTokenCount || 0;
|
||||
const thinkingTokens = usage.thoughtsTokenCount || 0;
|
||||
|
||||
const inputCost = (inputTokens / 1_000_000) * rates.input;
|
||||
const outputCost = (outputTokens / 1_000_000) * rates.output;
|
||||
const thinkingCost = (thinkingTokens / 1_000_000) * rates.thinking;
|
||||
const totalCost = inputCost + outputCost + thinkingCost;
|
||||
|
||||
return {
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
thinkingTokens,
|
||||
totalTokens: usage.totalTokenCount || (inputTokens + outputTokens + thinkingTokens),
|
||||
inputCost: inputCost.toFixed(6),
|
||||
outputCost: outputCost.toFixed(6),
|
||||
thinkingCost: thinkingCost.toFixed(6),
|
||||
totalCost: totalCost.toFixed(6),
|
||||
totalCostDisplay: totalCost < 0.01 ? `$${(totalCost * 100).toFixed(3)}¢` : `$${totalCost.toFixed(4)}`,
|
||||
};
|
||||
}
|
||||
|
||||
// PRICING + calcCost + buildAnalysisPrompt moved to ./gemini-helpers.js
|
||||
// safeText + retryGemini moved to ./util.js
|
||||
|
||||
// ── Health check ───────────────────────────────────────────────────────────
|
||||
@@ -2714,44 +2681,7 @@ async function splitAudioFile(inputPath, outputDir, chunkSeconds = 2700) {
|
||||
|
||||
// sendEvent / extractVideoId / formatTime / parseTimestampedTranscript moved to ./util.js
|
||||
|
||||
function buildAnalysisPrompt(entries) {
|
||||
const numbered = entries
|
||||
.map((e, i) => `[${i}] (${formatTime(e.offset)}) ${e.text}`)
|
||||
.join("\n");
|
||||
|
||||
return `You are analyzing a video transcript. Your job is to identify natural topic boundaries and group the transcript into discussion-based sections.
|
||||
|
||||
TRANSCRIPT (each line is numbered with a timestamp):
|
||||
${numbered}
|
||||
|
||||
INSTRUCTIONS:
|
||||
1. Read the entire transcript carefully.
|
||||
2. Identify where the discussion naturally shifts from one topic to another.
|
||||
3. Group consecutive transcript segments by topic. Some sections may be short (a quick aside) and some may be long (an extended deep-dive). Let the content dictate the length.
|
||||
4. For each section, write:
|
||||
- A short, specific topic title (3-8 words)
|
||||
- A 1-3 sentence summary of what's discussed
|
||||
- The start and end segment indices (inclusive)
|
||||
|
||||
IMPORTANT:
|
||||
- Sections must be chronological and non-overlapping.
|
||||
- Every segment index from 0 to ${entries.length - 1} must belong to exactly one section.
|
||||
- startIndex of section N+1 must equal endIndex of section N plus 1.
|
||||
- Create as many or as few sections as the content naturally requires.
|
||||
- Titles should be descriptive and specific, not generic like "Introduction" unless it truly is one.
|
||||
|
||||
Respond with ONLY valid JSON in this exact format, no other text:
|
||||
{
|
||||
"sections": [
|
||||
{
|
||||
"title": "Brief Topic Title",
|
||||
"summary": "1-3 sentence summary of this discussion section.",
|
||||
"startIndex": 0,
|
||||
"endIndex": 15
|
||||
}
|
||||
]
|
||||
}`;
|
||||
}
|
||||
// buildAnalysisPrompt moved to ./gemini-helpers.js
|
||||
|
||||
// ── Network mode ──────────────────────────────────────────────────────────
|
||||
// On StartOS (DATA_DIR=/data): always bind to 0.0.0.0 (container networking)
|
||||
|
||||
Reference in New Issue
Block a user