Skip to content

Feat/ai orchestration ab testing#444

Open
ZarrarPeshimam wants to merge 7 commits into
IndieHub25:mainfrom
ZarrarPeshimam:feat/ai-orchestration-ab-testing
Open

Feat/ai orchestration ab testing#444
ZarrarPeshimam wants to merge 7 commits into
IndieHub25:mainfrom
ZarrarPeshimam:feat/ai-orchestration-ab-testing

Conversation

@ZarrarPeshimam
Copy link
Copy Markdown

@ZarrarPeshimam ZarrarPeshimam commented Mar 2, 2026

Description

Issue Reference: Fixes #331

Summary of Changes:

  • AI Orchestration: Implemented a routing layer that selects models based on user preferences (quality vs. speed) and story length.
  • Resilience: Added a try-primary-catch-fallback loop in the service layer to ensure 100% uptime even if a primary model fails.
  • Telemetry: Integrated an "Active Handshake" MongoDB utility to log latency, A/B test variant IDs, and fallback events.
  • System Impact: Impacted AI Logic (Groq integration) and Backend/API (Persistence layer).

Context / Motivation:

  • Currently, model selection is hardcoded and lacks a safety net for rate limits or timeouts.
  • This PR solves the need for data-driven model selection and provides the infrastructure for A/B testing prompt variants via MongoDB.

Type of Change

  • AI / Prompt Logic
  • Backend / API
  • Documentation (Added Phase 3 Handshake comments)
  • Refactor / Cleanup (Standardized API response objects)

Technical Checklist

AI / Application Logic

  • I tested story generation end‑to‑end with a valid GROQ_API_KEY.
  • I verified that prompts, model names, and parameters are up to date with current Groq APIs.
  • I confirmed that error states and empty responses are handled gracefully (no unhandled exceptions).

Backend / Database

  • I ran backend startup locally without runtime errors.
  • I validated new API routes with both success and failure cases.
  • I considered database performance (indexes, query filters) for any new queries.
  • I confirmed that new logic respects existing retry/health‑check behavior where relevant.

Security & Privacy

  • No API keys, private keys, secrets, or .env files are committed.
  • I avoided logging sensitive data (sanitized URIs in logs).

Testing Evidence

Environment: Local development with MongoDB Compass and Groq SDK.

Commands:
- npm run dev
- git fetch upstream main (to verify sync)

Results / Logs:
[DB] Attempt 1/5: Connecting to mongodb://127.0.0.1:27017/groqtales
[DB] SUCCESS: Connected to database on attempt 1
elemetry saved to MongoDB: { modelUsed: 'llama-3.1-8b-instant', fallbackTriggered: true }

Manual Test Steps:

  1. Modified experiments.json with an invalid primary model name to trigger the fallback.
  2. Verified the frontend received a story generated by the fallback model.
  3. Confirmed the generation_logs collection in MongoDB captured the correct variantId and latencyMs.

Visual Proof

Attach your screenshot showing the successful MongoDB log entry with fallbackTriggered: true.

Screenshot 2026-03-02 115827 Screenshot 2026-03-02 122427

Contributor Status

  • I am an OSCG’26 contributor.

Review & Impact

Breaking Changes

  • This PR introduces a breaking change (API / contract / DB).

Backward Compatibility / Migrations

  • Existing users can continue using GroqTales without manual steps.
  • Added getDb() to ensure backward compatibility with existing connection states.

Final Acknowledgements

  • I confirm that the information and code in this PR are my original work...
  • I understand that by submitting this PR, I take full responsibility...
  • I have read and agree to follow the project’s Code of Conduct...

Summary by CodeRabbit

  • New Features

    • Redesigned story creator: Input / Preview / Mint tabs, draft autosave/recovery, copy/reset, and preview-first generation flow.
    • Added "flash" and "epic" length options and a Mint NFT tab with wallet/connect UI and mint action.
  • Generation Improvements

    • More reliable generation with primary/fallback model handling, faster orchestration, and clearer success/error feedback.
  • Chores

    • Added experiments configuration and improved generation diagnostics/logging.

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 2, 2026

@ZarrarPeshimam is attempting to deploy a commit to the Drago's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added Enhancement New feature or request in progress Feature Request or proposal for a new feature size/XL testing labels Mar 2, 2026
@github-actions github-actions Bot requested a review from Drago-03 March 2, 2026 14:42
@ZarrarPeshimam
Copy link
Copy Markdown
Author

@Drago-03, plz review my PR

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
groq-tales Ready Ready Preview, Comment Mar 6, 2026 6:18am

@Drago-03 Drago-03 added Hard Advanced, complex, or challenging issues OSCG26 Open Source Connect Global 2026 Contributions labels Mar 5, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new /api/groq route with GET/POST handlers that orchestrate multi-model story generation (generate/analyze/ideas/improve), implements an orchestration layer with primary/fallback selection and variant tracking, updates Groq service to support model/fallback/temperature and new lengths, logs generation events to MongoDB, adds DB helper and experiments config, and refactors the frontend story generator UI and autosave.

Changes

Cohort / File(s) Summary
API Route & Logging
app/api/groq/route.ts, models/GenerationLog.ts
Adds /api/groq GET/POST handlers (actions: generate, analyze, ideas, improve, test), input validation, orchestration usage, calls into groq service, and Mongo-backed generation logging with resilient error handling and status enums.
Frontend Story Generator
components/ai-story-generator.tsx
Large UI and flow refactor: modular tabs (Input/Preview/Mint), draft autosave/recovery (DRAFT_KEY), simplified client POST to /api/groq (new payload shape), copy/reset, and mint scaffolding. UI components reorganized and some fields removed.
Orchestration & Generation Core
lib/ai/orchestrator.ts, lib/groq-service.ts
New orchestrator that reads config/experiments.json to pick primary/fallback models, prompt variant, and temperature; groq-service updated to accept model/fallback/temperature, add flash/epic lengths, caching, and structured return {content, actualModel, fallbackUsed} with fallback retry logic.
DB Helper & Config
lib/db/connect.ts, config/experiments.json
Adds getDb() helper returning native Mongo Db with retry-enabled connection; adds experiments JSON config for active_experiments (primary/fallback models, prompt variant, enable_fallback_logic) and metadata.
Type/Model Signatures
lib/groq-service.ts, lib/ai/orchestrator.ts
Public signature changes: StoryGenerationParams extended (length now includes flash/epic, plus model, fallbackModel, temperature); generateStoryContent now returns structured result; new types exported (StoryType, QualityPreference, OrchestrationResult).

Sequence Diagram

sequenceDiagram
    participant User
    participant Frontend as Frontend UI
    participant API as /api/groq
    participant Orchestrator as Orchestration Layer
    participant GroqService as Groq Service
    participant Primary as Groq API (Primary)
    participant Fallback as Groq API (Fallback)
    participant Mongo as MongoDB

    User->>Frontend: configure story & request generate
    Frontend->>API: POST (prompt, title, genre, length, pipelineParams)
    API->>Orchestrator: getOrchestrationPlan(type, preference)
    Orchestrator-->>API: plan (primaryModel, fallbackModel?, variant, temperature)
    API->>GroqService: generateStoryContent(params + plan)
    GroqService->>Primary: call primary model
    alt primary succeeds
        Primary-->>GroqService: content
        GroqService-->>API: {content, actualModel, fallbackUsed:false}
    else primary fails
        Primary-->>GroqService: error
        GroqService->>Fallback: call fallback model
        Fallback-->>GroqService: content
        GroqService-->>API: {content, actualModel, fallbackUsed:true}
    end
    API->>Mongo: log generation event (prompt, model, variant, latency, status)
    Mongo-->>API: logged
    API-->>Frontend: response (content, model metadata)
    Frontend->>Frontend: persist draft (DRAFT_KEY)
    Frontend-->>User: display generated story
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

AI

Suggested reviewers

  • Drago-03

Poem

🐇 I nibbled prompts beneath the moonlit key,

When primary stumbles, I call fallback free.
I tuck each draft in a cozy, local lair,
I log the models, latency, and variant flair.
A minted tale hops out — fresh as carrot tea!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feat/ai orchestration ab testing' clearly summarizes the main change: implementing AI orchestration with A/B testing capabilities for story generation.
Description check ✅ Passed The PR description follows the template with issue reference, comprehensive summary, context, type of change, technical checklist, testing evidence, and acknowledgements all completed.
Linked Issues check ✅ Passed All core requirements from issue #331 are met: orchestration layer routing by preference/type, fallback mechanism, telemetry logging (latency/model/variant), and experiment config via experiments.json.
Out of Scope Changes check ✅ Passed All changes are directly aligned with orchestration objectives: new orchestrator module, Groq service enhancements, MongoDB telemetry layer, experiments config, and frontend integration for the new flow.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ZarrarPeshimam
Copy link
Copy Markdown
Author

A merge conflict had occured
Just resolved it

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lib/groq-service.ts (1)

82-101: ⚠️ Potential issue | 🔴 Critical

Add a request timeout so fallback can actually trigger on hangs.

Without a timeout, a stalled primary request may never throw, and fallback logic never runs.

🔧 Suggested fix
   async function performRequest(targetModel: string): Promise<string> {
@@
-    const response = await fetch(
-      'https://api.groq.com/openai/v1/chat/completions',
-      {
-        method: 'POST',
-        headers: {
-          Authorization: `Bearer ${groqApiKey}`,
-          'Content-Type': 'application/json',
-        },
-        body: JSON.stringify({
-          model: targetModel,
-          messages: [
-            { role: 'system', content: systemPrompt },
-            { role: 'user', content: wrapUserContent(prompt) },
-          ],
-          max_tokens: getMaxTokensForLength(params.length || 'medium'),
-          temperature: selectedTemp,
-          top_p: 0.9,
-        }),
-      }
-    );
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 15000);
+    let response: Response;
+    try {
+      response = await fetch(
+        'https://api.groq.com/openai/v1/chat/completions',
+        {
+          method: 'POST',
+          headers: {
+            Authorization: `Bearer ${groqApiKey}`,
+            'Content-Type': 'application/json',
+          },
+          body: JSON.stringify({
+            model: targetModel,
+            messages: [
+              { role: 'system', content: systemPrompt },
+              { role: 'user', content: wrapUserContent(prompt) },
+            ],
+            max_tokens: getMaxTokensForLength(params.length || 'medium'),
+            temperature: selectedTemp,
+            top_p: 0.9,
+          }),
+          signal: controller.signal,
+        }
+      );
+    } finally {
+      clearTimeout(timeoutId);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/groq-service.ts` around lines 82 - 101, The fetch call that posts to
'https://api.groq.com/openai/v1/chat/completions' (using groqApiKey,
targetModel, wrapUserContent, getMaxTokensForLength, selectedTemp) needs a
request timeout so the fallback path can run on hangs; wrap the fetch with an
AbortController, start a timer (e.g., setTimeout) that calls controller.abort()
after a reasonable timeout, pass controller.signal into fetch options, and clear
the timer once fetch resolves or rejects so no stray timers remain; ensure the
abort error is propagated so existing fallback logic sees the failure.
lib/db/connect.ts (1)

72-115: ⚠️ Potential issue | 🟠 Major

Close failed MongoDB clients inside the retry loop.

When a connect/ping attempt fails, the attempt-local client is not closed before retrying. Repeated failures can accumulate open sockets.

🔧 Suggested fix
   connectionPromise = (async () => {
     for (let attempt = 1; attempt <= maxRetries; attempt++) {
+      let client: MongoClient | null = null;
       try {
         // 3. Log each specific attempt and the sanitized URI
         console.log(`[DB] Attempt ${attempt}/${maxRetries}: Connecting to ${sanitizedUri}`);
         state.connectionAttempts = attempt;

-        const client = new MongoClient(uri, {
+        client = new MongoClient(uri, {
           maxPoolSize: 10,
           serverSelectionTimeoutMS: 5000,
           socketTimeoutMS: 45000,
         });

         await client.connect();
@@
         console.log(`[DB] SUCCESS: Connected to database on attempt ${attempt}`);
         return client;

       } catch (error: any) {
+        if (client) {
+          await client.close().catch(() => {});
+        }
         const errorMessage = error.message || 'Unknown error';
         state.lastError = errorMessage;
         const delay = retryDelayMs * Math.pow(2, attempt - 1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/db/connect.ts` around lines 72 - 115, The retry loop in the connect
routine leaves the attempt-local MongoClient open on failures; inside the catch
block of the for loop (where `client` is created via `new MongoClient(uri, { ...
})` and `error` is handled), ensure you close the failed `client` before
retrying (await client.close() safely, guarded in case client.connect() never
completed), and also close it on the final max-retries failure before throwing;
update the catch to attempt a safe close of the `client` and swallow/attach any
close errors to `state.lastError` so sockets do not accumulate across attempts.
🧹 Nitpick comments (1)
lib/groq-service.ts (1)

134-147: Cache fallback successes to avoid repeated primary-fail-first behavior.

Fallback output is returned but never cached, so identical requests can repeatedly incur primary failure overhead.

♻️ Suggested refactor
       try {
         const fallbackContent = await performRequest(fallbackModel);
         
         const outputCheck = validateOutput(fallbackContent);
         if (!outputCheck.isSafe) throw new Error('Unsafe fallback output');

+        const fallbackCacheOptions = { ...params, model: fallbackModel };
+        await setCachedResponse(cacheCategory, params.theme, fallbackCacheOptions, fallbackContent);
+
         return { 
           content: fallbackContent, 
           actualModel: fallbackModel, 
           fallbackUsed: true 
         };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/groq-service.ts` around lines 134 - 147, The fallback branch currently
returns fallbackContent but does not store it in the cache, causing repeated
primary failures; update the code around the fallback handling (the block using
fallbackModel, performRequest, validateOutput and returning { content,
actualModel, fallbackUsed }) to save the successful fallback response into the
same caching mechanism used for primary responses (create a cache key derived
from the request parameters, store content, actualModel and fallbackUsed=true
with TTL), and ensure the request flow checks that cache before attempting the
primary model so identical requests use the cached fallback result instead of
reattempting the primary; use the existing cache helper (or add one) and keep
validation via validateOutput before caching.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/groq/route.ts`:
- Around line 70-77: The telemetry insertion currently writes a raw/truncated
promptSnippet into the generation_logs record; change this to avoid storing any
raw prompt text by replacing promptSnippet with a non-reversible identifier and
metadata (e.g., compute and store a secure hash of prompt using a SHA-256 or
similar and store promptLength) in the
db.collection('generation_logs').insertOne call so the record contains
promptHash and promptLength instead of promptSnippet; ensure generationResult
and plan usage is unchanged and update any downstream consumers to use
promptHash/promptLength rather than promptSnippet.
- Around line 141-147: The test branch handling "action === 'test'" in the
request handler (where searchParams and useSpecialModel decide between
testGroqSpecialModel and testGroqConnection and return via NextResponse.json) is
exposed to unauthenticated callers; add an internal guard at the top of that
branch that verifies a server-only secret or internal header (e.g., compare a
request header like "x-internal-token" against process.env.INTERNAL_API_KEY or
validate a server session) and if the check fails return a 403/NextResponse.json
error immediately; only call testGroqSpecialModel or testGroqConnection after
the guard passes so external requests cannot trigger provider calls.
- Around line 41-43: The current truthy check for prompt allows non-string or
whitespace values; change the validation inside the request handler to ensure
prompt is a string with non-empty trimmed content (e.g., check typeof prompt ===
'string' && prompt.trim().length > 0) and if it fails return NextResponse.json({
error: 'Prompt must be a non-empty string' }, { status: 400 }); update the block
that currently uses if (!prompt) to use this stricter check so downstream
logging and generation (references to prompt) always receive a valid string.

In `@components/ai-story-generator.tsx`:
- Line 690: The external OpenSea anchor uses target="_blank" without safe link
attributes; update the JSX where mintedNftUrl is rendered (the Button/asChild
anchor with target="_blank") to add rel="noopener noreferrer" to the <a> element
so the anchor rendering mintedNftUrl prevents opener/tab access and avoids the
security vulnerability.

In `@config/experiments.json`:
- Around line 3-4: The configured primary_story_model "llama-3.3-6767" is not
present in GROQ_MODELS in lib/groq-service.ts, causing the primary path to
always fail; update the experiments.json primary_story_model to one of the valid
IDs listed in GROQ_MODELS (e.g., "llama-3.3-70b-versatile",
"llama-3.1-8b-instant", or "mistral-saba-24b") or add the missing model key to
GROQ_MODELS in lib/groq-service.ts so the primary_story_model resolves
correctly, ensuring the symbol primary_story_model matches an entry in
GROQ_MODELS.

In `@lib/ai/orchestrator.ts`:
- Around line 19-37: The returned plan always sets fallbackModel, ignoring the
experiment toggle; update the orchestration logic (where config =
experiments.active_experiments and the plan objects are built) to respect
config.enable_fallback_logic: only include fallbackModel (or set it to
null/undefined) when config.enable_fallback_logic is true, and omit it when
false for both the speed/flash branch and the balanced/high-quality branch
(adjust temperature/promptVariant behavior unchanged).

In `@lib/db/connect.ts`:
- Around line 247-253: getDb currently uses a non-null assertion on
process.env.MONGODB_URI and calls client.db() without a database name; change it
to fail fast if MONGODB_URI is missing and explicitly pass the DB name when
calling client.db. Specifically, in the getDb function (which calls
connectWithRetry), validate process.env.MONGODB_URI and
process.env.MONGODB_DB_NAME up front and throw a clear error if either is
missing, then call client.db(process.env.MONGODB_DB_NAME) instead of client.db()
so the connection always targets the configured database.

In `@lib/groq-service.ts`:
- Around line 68-70: Add validation for model IDs before making any API calls:
in lib/groq-service.ts (where primaryModel, fallbackModel and selectedTemp are
selected) verify that primaryModel and fallbackModel (if provided) are in the
allowed model set (or match the provider's canonical ID pattern) and throw or
return a clear configuration error if not; perform this check immediately after
the lines assigning primaryModel/fallbackModel to fail fast on typos, and
include the invalid value in the error message. Also update
config/experiments.json to correct primary_story_model from "llama-3.3-6767" to
"llama-3.3-70b-versatile".

In `@models/GenerationLog.ts`:
- Around line 5-17: The interface IGenerationLog currently marks genre as
required while GenerationLogSchema defines genre as optional; update
IGenerationLog.genre to be optional (e.g., genre?: string) so the TypeScript
type matches GenerationLogSchema and consumers don't assume genre is always
present; locate the IGenerationLog declaration and change the genre property
accordingly to keep modelUsed, prompt, fallbackTriggered, variantId, latencyMs,
status, and timestamp unchanged.
- Around line 14-30: The GenerationLogSchema currently lacks an explicit
collection binding so mongoose.model('GenerationLog', GenerationLogSchema) will
default to the pluralized "generationlogs" collection; update the Schema
instantiation for GenerationLogSchema to include the collection option matching
the route ("generation_logs") by passing { collection: 'generation_logs' } into
the new Schema(...) call so the schema and the route write to the same
collection.

---

Outside diff comments:
In `@lib/db/connect.ts`:
- Around line 72-115: The retry loop in the connect routine leaves the
attempt-local MongoClient open on failures; inside the catch block of the for
loop (where `client` is created via `new MongoClient(uri, { ... })` and `error`
is handled), ensure you close the failed `client` before retrying (await
client.close() safely, guarded in case client.connect() never completed), and
also close it on the final max-retries failure before throwing; update the catch
to attempt a safe close of the `client` and swallow/attach any close errors to
`state.lastError` so sockets do not accumulate across attempts.

In `@lib/groq-service.ts`:
- Around line 82-101: The fetch call that posts to
'https://api.groq.com/openai/v1/chat/completions' (using groqApiKey,
targetModel, wrapUserContent, getMaxTokensForLength, selectedTemp) needs a
request timeout so the fallback path can run on hangs; wrap the fetch with an
AbortController, start a timer (e.g., setTimeout) that calls controller.abort()
after a reasonable timeout, pass controller.signal into fetch options, and clear
the timer once fetch resolves or rejects so no stray timers remain; ensure the
abort error is propagated so existing fallback logic sees the failure.

---

Nitpick comments:
In `@lib/groq-service.ts`:
- Around line 134-147: The fallback branch currently returns fallbackContent but
does not store it in the cache, causing repeated primary failures; update the
code around the fallback handling (the block using fallbackModel,
performRequest, validateOutput and returning { content, actualModel,
fallbackUsed }) to save the successful fallback response into the same caching
mechanism used for primary responses (create a cache key derived from the
request parameters, store content, actualModel and fallbackUsed=true with TTL),
and ensure the request flow checks that cache before attempting the primary
model so identical requests use the cached fallback result instead of
reattempting the primary; use the existing cache helper (or add one) and keep
validation via validateOutput before caching.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 71a8817f-e481-4862-b3c7-462d7b7fe40c

📥 Commits

Reviewing files that changed from the base of the PR and between 340b91f and 7940169.

📒 Files selected for processing (7)
  • app/api/groq/route.ts
  • components/ai-story-generator.tsx
  • config/experiments.json
  • lib/ai/orchestrator.ts
  • lib/db/connect.ts
  • lib/groq-service.ts
  • models/GenerationLog.ts

Comment thread app/api/groq/route.ts
Comment thread app/api/groq/route.ts
Comment thread app/api/groq/route.ts
Comment thread components/ai-story-generator.tsx Outdated
Comment thread config/experiments.json Outdated
Comment thread lib/ai/orchestrator.ts
Comment thread lib/db/connect.ts Outdated
Comment thread lib/groq-service.ts
Comment on lines +68 to +70
const primaryModel = params.model || GROQ_MODELS.STORY_GENERATION;
const fallbackModel = params.fallbackModel;
const selectedTemp = params.temperature ?? 0.8;
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check if the files mentioned exist
git ls-files | grep -E "(groq-service|experiments\.json)" | head -20

Repository: IndieHub25/GroqTales

Length of output: 142


🏁 Script executed:

# Check the structure of lib/groq-service.ts around lines 68-70
wc -l lib/groq-service.ts

Repository: IndieHub25/GroqTales

Length of output: 87


🏁 Script executed:

# Read the relevant section of lib/groq-service.ts
sed -n '60,85p' lib/groq-service.ts

Repository: IndieHub25/GroqTales

Length of output: 1069


🏁 Script executed:

# Find and display GROQ_MODELS definition
rg "GROQ_MODELS\s*=" lib/groq-service.ts -A 15

Repository: IndieHub25/GroqTales

Length of output: 628


🏁 Script executed:

# Check the experiments.json file to see configured models
cat config/experiments.json | jq '.active_experiments | {primary_story_model, fallback_story_model}'

Repository: IndieHub25/GroqTales

Length of output: 159


🏁 Script executed:

# Check if there's any validation logic after line 70
sed -n '70,150p' lib/groq-service.ts | head -40

Repository: IndieHub25/GroqTales

Length of output: 1551


🏁 Script executed:

# Search for any existing validation of model IDs in the file
rg "allowedModels|supported.*model|GROQ_MODELS.*has|valid.*model" lib/groq-service.ts -i

Repository: IndieHub25/GroqTales

Length of output: 46


🏁 Script executed:

# Test the Python script from the review comment
python3 - <<'PY'
import json, re, pathlib
exp = json.loads(pathlib.Path("config/experiments.json").read_text())
ts = pathlib.Path("lib/groq-service.ts").read_text()
block = ts.split("export const GROQ_MODELS = {", 1)[1].split("} as const;", 1)[0]
valid = set(re.findall(r":\s*'([^']+)'", block))
print("Valid models:", valid)
print("\nActive experiments:")
for k in ("primary_story_model", "fallback_story_model"):
    v = exp["active_experiments"].get(k)
    if v:
        status = 'OK' if v in valid else 'INVALID'
        print(f"  {k}: {v} -> {status}")
PY

Repository: IndieHub25/GroqTales

Length of output: 270


Add model ID validation before API calls to catch misconfiguration immediately.

This function accepts any model string without validation. Verification found that config/experiments.json contains an invalid model ID (llama-3.3-6767 should be llama-3.3-70b-versatile), which will cause a runtime API failure. Without validation, typos in model configuration become avoidable provider failures.

🔧 Suggested fix
 export async function generateStoryContent(
   params: StoryGenerationParams & { fallbackModel?: string }
 ): Promise<{ content: string; actualModel: string; fallbackUsed: boolean }> {
   const primaryModel = params.model || GROQ_MODELS.STORY_GENERATION;
   const fallbackModel = params.fallbackModel; 
   const selectedTemp = params.temperature ?? 0.8;
+  const allowedModels = new Set(Object.values(GROQ_MODELS));
+
+  if (!allowedModels.has(primaryModel)) {
+    throw new Error(`Unsupported primary model: ${primaryModel}`);
+  }
+  if (fallbackModel && !allowedModels.has(fallbackModel)) {
+    throw new Error(`Unsupported fallback model: ${fallbackModel}`);
+  }

Also fix the invalid model ID in config/experiments.json: change primary_story_model from llama-3.3-6767 to llama-3.3-70b-versatile.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const primaryModel = params.model || GROQ_MODELS.STORY_GENERATION;
const fallbackModel = params.fallbackModel;
const selectedTemp = params.temperature ?? 0.8;
export async function generateStoryContent(
params: StoryGenerationParams & { fallbackModel?: string }
): Promise<{ content: string; actualModel: string; fallbackUsed: boolean }> {
const primaryModel = params.model || GROQ_MODELS.STORY_GENERATION;
const fallbackModel = params.fallbackModel;
const selectedTemp = params.temperature ?? 0.8;
const allowedModels = new Set(Object.values(GROQ_MODELS));
if (!allowedModels.has(primaryModel)) {
throw new Error(`Unsupported primary model: ${primaryModel}`);
}
if (fallbackModel && !allowedModels.has(fallbackModel)) {
throw new Error(`Unsupported fallback model: ${fallbackModel}`);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/groq-service.ts` around lines 68 - 70, Add validation for model IDs
before making any API calls: in lib/groq-service.ts (where primaryModel,
fallbackModel and selectedTemp are selected) verify that primaryModel and
fallbackModel (if provided) are in the allowed model set (or match the
provider's canonical ID pattern) and throw or return a clear configuration error
if not; perform this check immediately after the lines assigning
primaryModel/fallbackModel to fail fast on typos, and include the invalid value
in the error message. Also update config/experiments.json to correct
primary_story_model from "llama-3.3-6767" to "llama-3.3-70b-versatile".

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ZarrarPeshimam same update here with models

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment thread models/GenerationLog.ts
Comment thread models/GenerationLog.ts Outdated
Copy link
Copy Markdown
Member

@Drago-03 Drago-03 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update with the requested changes

Comment thread config/experiments.json Outdated
@@ -0,0 +1,12 @@
{
"active_experiments": {
"primary_story_model": "llama-3.3-6767",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update ‎primary_story_model to ‎gemini-3-flash-preview (and ‎fallback_story_model to ‎`gemini-3.1-flash-lite-preview)

Comment thread lib/db/connect.ts Outdated
maxRetries: 5,
retryDelayMs: 1000
});
return client.db(); // Returns the native Db object
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Read ‎MONGODB_URI into a local ‎uri.

  • If it’s missing, throw ‎new Error('MONGODB_URI is not configured').

  • Then call ‎connectWithRetry({ uri, ... }).

  • Finally, return ‎client.db(process.env.MONGODB_DB_NAME) so the DB is always explicit.

Comment thread lib/groq-service.ts
Comment on lines +68 to +70
const primaryModel = params.model || GROQ_MODELS.STORY_GENERATION;
const fallbackModel = params.fallbackModel;
const selectedTemp = params.temperature ?? 0.8;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ZarrarPeshimam same update here with models

Comment thread models/GenerationLog.ts Outdated
});

export default mongoose.models.GenerationLog ||
mongoose.model<IGenerationLog>('GenerationLog', GenerationLogSchema); No newline at end of file
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add the collection bounding here

@ZarrarPeshimam
Copy link
Copy Markdown
Author

ZarrarPeshimam commented Mar 6, 2026

Done with required changes

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
lib/groq-service.ts (2)

159-162: Error message may be misleading.

The message "All available AI models are currently overloaded" assumes rate limiting, but failures could stem from authentication errors, invalid requests, or network issues. Consider a more accurate message.

♻️ Suggested improvement
       } catch (fallbackError) {
         console.error('Both primary and fallback models failed.');
-        throw new Error('All available AI models are currently overloaded.');
+        throw new Error('Story generation failed: both primary and fallback models encountered errors.');
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/groq-service.ts` around lines 159 - 162, The catch block that currently
logs "Both primary and fallback models failed." and throws a new Error('All
available AI models are currently overloaded.') should be updated to avoid
assuming rate-limiting: include the underlying fallbackError details in the
thrown error or replace the static message with a generic one such as 'AI model
request failed' and append or wrap fallbackError to preserve original error
information; locate the try/catch around the primary/fallback model calls (the
catch handling variable fallbackError in groq-service.ts) and modify the throw
to include fallbackError.message or rethrow a new Error that composes a clear,
non-assuming message plus the original error details.

29-31: Redundant fallbackModel declaration.

fallbackModel is already declared in StoryGenerationParams (line 30), so the intersection & { fallbackModel?: string } in the function signature (line 66) is redundant.

♻️ Suggested simplification
 export async function generateStoryContent(
-  params: StoryGenerationParams & { fallbackModel?: string }
+  params: StoryGenerationParams
 ): Promise<{ content: string; actualModel: string; fallbackUsed: boolean }> {

Also applies to: 66-66

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/groq-service.ts` around lines 29 - 31, Remove the redundant fallbackModel
declaration in the function signature that intersects StoryGenerationParams with
"& { fallbackModel?: string }"; update the relevant function (the one accepting
StoryGenerationParams) to accept StoryGenerationParams directly (or the existing
parameter type) and delete the extra "& { fallbackModel?: string }" intersection
so there's only a single fallbackModel declaration coming from
StoryGenerationParams (refer to StoryGenerationParams and the function signature
where "& { fallbackModel?: string }" appears).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/groq-service.ts`:
- Around line 119-121: The function currently returns the literal fallback
'Failed to generate story content' when data.choices[0]?.message?.content is
undefined, which may be cached as valid output; change the behavior in the block
that reads response.json() and inspects data.choices so that instead of
returning that string you either throw a descriptive error (including response
status/body) or return null/undefined so the caller can detect failure and avoid
caching; reference the variables/expressions data, response.json(), and
data.choices[0]?.message?.content and ensure any thrown error includes context
so upstream cache logic can skip storing invalid results.
- Around line 87-91: The code only sanitizes params.theme before calling
buildStoryPrompt; sanitize all user-supplied inputs used in that prompt
(params.genre, params.tone, params.characters, params.setting and theme) using
the existing sanitizeInput utility and pass the sanitized values into
buildStoryPrompt instead of raw params; update the call site where
sanitizedTheme is created (and any local variable names) so buildStoryPrompt
receives an object composed of sanitizedGenre, sanitizedTone,
sanitizedCharacters, sanitizedSetting, and sanitizedTheme while leaving
buildHardenedSystemPrompt unchanged.

---

Nitpick comments:
In `@lib/groq-service.ts`:
- Around line 159-162: The catch block that currently logs "Both primary and
fallback models failed." and throws a new Error('All available AI models are
currently overloaded.') should be updated to avoid assuming rate-limiting:
include the underlying fallbackError details in the thrown error or replace the
static message with a generic one such as 'AI model request failed' and append
or wrap fallbackError to preserve original error information; locate the
try/catch around the primary/fallback model calls (the catch handling variable
fallbackError in groq-service.ts) and modify the throw to include
fallbackError.message or rethrow a new Error that composes a clear, non-assuming
message plus the original error details.
- Around line 29-31: Remove the redundant fallbackModel declaration in the
function signature that intersects StoryGenerationParams with "& {
fallbackModel?: string }"; update the relevant function (the one accepting
StoryGenerationParams) to accept StoryGenerationParams directly (or the existing
parameter type) and delete the extra "& { fallbackModel?: string }" intersection
so there's only a single fallbackModel declaration coming from
StoryGenerationParams (refer to StoryGenerationParams and the function signature
where "& { fallbackModel?: string }" appears).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7df90872-93fc-4547-a05e-8db4f89266e2

📥 Commits

Reviewing files that changed from the base of the PR and between e1400fd and 6f3e4cb.

📒 Files selected for processing (5)
  • config/experiments.json
  • lib/ai/orchestrator.ts
  • lib/db/connect.ts
  • lib/groq-service.ts
  • models/GenerationLog.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • models/GenerationLog.ts
  • lib/ai/orchestrator.ts
  • lib/db/connect.ts
  • config/experiments.json

Comment thread lib/groq-service.ts
Comment on lines +87 to 91
const sanitizedTheme = sanitizeInput(params.theme).sanitized;
const prompt = buildStoryPrompt({ ...params, theme: sanitizedTheme });
const systemPrompt = buildHardenedSystemPrompt(
'You are a creative writing assistant that generates engaging, well-structured stories based on user parameters. Focus on compelling narratives with strong character development and vivid descriptions.'
'You are a creative writing assistant that generates engaging, well-structured stories.'
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent input sanitization.

Only params.theme is sanitized before prompt construction (line 87), while params.genre, params.tone, params.characters, and params.setting are used unsanitized in buildStoryPrompt. Other functions in this file (e.g., analyzeStoryContent, improveStoryContent) sanitize all user inputs. Apply consistent sanitization here.

🛡️ Suggested fix
   async function performRequest(targetModel: string): Promise<string> {
     const groqApiKey = process.env.GROQ_API_KEY;
     if (!groqApiKey) throw new Error('GROQ_API_KEY is not set');

     const sanitizedTheme = sanitizeInput(params.theme).sanitized;
-    const prompt = buildStoryPrompt({ ...params, theme: sanitizedTheme });
+    const sanitizedParams = {
+      ...params,
+      theme: sanitizedTheme,
+      genre: params.genre ? sanitizeInput(params.genre).sanitized : undefined,
+      tone: params.tone ? sanitizeInput(params.tone).sanitized : undefined,
+      characters: params.characters ? sanitizeInput(params.characters).sanitized : undefined,
+      setting: params.setting ? sanitizeInput(params.setting).sanitized : undefined,
+    };
+    const prompt = buildStoryPrompt(sanitizedParams);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const sanitizedTheme = sanitizeInput(params.theme).sanitized;
const prompt = buildStoryPrompt({ ...params, theme: sanitizedTheme });
const systemPrompt = buildHardenedSystemPrompt(
'You are a creative writing assistant that generates engaging, well-structured stories based on user parameters. Focus on compelling narratives with strong character development and vivid descriptions.'
'You are a creative writing assistant that generates engaging, well-structured stories.'
);
const sanitizedTheme = sanitizeInput(params.theme).sanitized;
const sanitizedParams = {
...params,
theme: sanitizedTheme,
genre: params.genre ? sanitizeInput(params.genre).sanitized : undefined,
tone: params.tone ? sanitizeInput(params.tone).sanitized : undefined,
characters: params.characters ? sanitizeInput(params.characters).sanitized : undefined,
setting: params.setting ? sanitizeInput(params.setting).sanitized : undefined,
};
const prompt = buildStoryPrompt(sanitizedParams);
const systemPrompt = buildHardenedSystemPrompt(
'You are a creative writing assistant that generates engaging, well-structured stories.'
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/groq-service.ts` around lines 87 - 91, The code only sanitizes
params.theme before calling buildStoryPrompt; sanitize all user-supplied inputs
used in that prompt (params.genre, params.tone, params.characters,
params.setting and theme) using the existing sanitizeInput utility and pass the
sanitized values into buildStoryPrompt instead of raw params; update the call
site where sanitizedTheme is created (and any local variable names) so
buildStoryPrompt receives an object composed of sanitizedGenre, sanitizedTone,
sanitizedCharacters, sanitizedSetting, and sanitizedTheme while leaving
buildHardenedSystemPrompt unchanged.

Comment thread lib/groq-service.ts
Comment on lines 119 to +121
const data = await response.json();
const generatedContent =
data.choices[0]?.message?.content || 'Failed to generate story content';
return data.choices[0]?.message?.content || 'Failed to generate story content';
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Silent failure returns a string that may be cached as valid content.

If data.choices[0]?.message?.content is undefined, the function returns 'Failed to generate story content' which will pass output validation and get cached. Subsequent requests with the same cache key will receive this error string as valid content.

🐛 Proposed fix
     const data = await response.json();
-    return data.choices[0]?.message?.content || 'Failed to generate story content';
+    const content = data.choices[0]?.message?.content;
+    if (!content) {
+      throw new Error('Groq API returned empty content');
+    }
+    return content;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const data = await response.json();
const generatedContent =
data.choices[0]?.message?.content || 'Failed to generate story content';
return data.choices[0]?.message?.content || 'Failed to generate story content';
}
const data = await response.json();
const content = data.choices[0]?.message?.content;
if (!content) {
throw new Error('Groq API returned empty content');
}
return content;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/groq-service.ts` around lines 119 - 121, The function currently returns
the literal fallback 'Failed to generate story content' when
data.choices[0]?.message?.content is undefined, which may be cached as valid
output; change the behavior in the block that reads response.json() and inspects
data.choices so that instead of returning that string you either throw a
descriptive error (including response status/body) or return null/undefined so
the caller can detect failure and avoid caching; reference the
variables/expressions data, response.json(), and
data.choices[0]?.message?.content and ensure any thrown error includes context
so upstream cache logic can skip storing invalid results.

@ZarrarPeshimam
Copy link
Copy Markdown
Author

Hi @Drago-03, I'd pushed the requested fixes that you had requested

Please let me know if anything else needs.

@ZarrarPeshimam ZarrarPeshimam requested a review from Drago-03 March 7, 2026 10:09
@ZarrarPeshimam
Copy link
Copy Markdown
Author

Had a merged conflict, resolved it

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/ai-story-generator.tsx`:
- Around line 92-111: The draft recovery state is set on mount (recoveredDraft,
showRecoveryModal) but there is no UI or handlers to resume or dismiss it; add a
visible recovery modal/confirmation component that renders when
showRecoveryModal is true and implement two handlers (e.g., handleResumeDraft
and handleDismissDraft) that respectively hydrate the editor/form state from
recoveredDraft (call whatever form setters you have, e.g., setPrompt, setTitle
or setFormFromDraft) and clear the saved draft
(localStorage.removeItem(DRAFT_KEY)) while closing the modal
(setShowRecoveryModal(false)); also ensure dismissing the modal does not
re-trigger recovery on next mount by removing the draft or setting a session
lock (mintSessionLock) as appropriate.
- Around line 247-254: The "MINT MASTERPIECE" button rendered when connected has
no onClick, so the mint CTA is dead; either wire it to the mint handler (e.g.,
call an existing mint function such as handleMint or mintMasterpiece from the
component) by adding onClick={handleMint} to the Button, or explicitly render a
disabled/placeholder state (disabled prop and label like "COMING SOON" and
appropriate styling) until minting is implemented; ensure you reference the same
state/props used for connectWallet and connected so the behavior is consistent.
- Around line 133-140: The client is sending generation params nested under
options (genres, length, temp) but the API (/api/groq route handler and
lib/groq-service.ts) expects flattened fields (top-level genre, length,
temp/temperature) and also expects options.preference plus mainCharacterName and
characterTraits to be present; update the payload in
components/ai-story-generator.tsx (the fetch POST for action 'generate') to send
flattened keys (e.g., genre, length, temp/temperature) at the top level, include
options.preference as its own field or as top-level preference, and include
mainCharacterName and characterTraits so the route handler and
lib/groq-service.ts consume them correctly, or alternatively update the
/api/groq route and lib/groq-service.ts to accept the current nested shape—pick
one and make all three places (client payload, route.ts handler,
lib/groq-service.ts) consistent.
- Around line 164-169: resetForm currently only clears prompt and
generatedStory, leaving other draft state to persist; update resetForm to also
reset storyTitle, mainCharacterName, selectedGenres, storyLength,
characterTraits, and temperature (and any other draft-related state vars) back
to their initial defaults, keep setActiveTab('input') and
localStorage.removeItem(DRAFT_KEY), and ensure any handlers that autosave read
the cleared values so the draft is not re-saved with stale data.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9e1bc2c4-b5f2-4809-841b-696f10499ab3

📥 Commits

Reviewing files that changed from the base of the PR and between 6f3e4cb and 1ca681c.

📒 Files selected for processing (1)
  • components/ai-story-generator.tsx

Comment thread components/ai-story-generator.tsx Outdated
Comment on lines +92 to +111
const [showRecoveryModal, setShowRecoveryModal] = useState(false);
const [recoveredDraft, setRecoveredDraft] = useState<StoryDraft | null>(null);

const { toast } = useToast();
const mintSessionLock = useRef(false);

const handleGenreToggle = (genre: string) => {
setSelectedGenres((prev) =>
prev.includes(genre) ? prev.filter((g) => g !== genre) : [...prev, genre]
);
};

const generateStory = async () => {
if (!prompt.trim()) {
toast({
title: 'Missing Prompt',
description: 'Please enter a story prompt to generate content.',
variant: 'destructive',
});
return;
// Draft Recovery Logic
useEffect(() => {
const saved = localStorage.getItem(DRAFT_KEY);
if (saved) {
try {
const draft = JSON.parse(saved);
if (draft?.prompt?.trim()) {
setRecoveredDraft(draft);
setShowRecoveryModal(true);
}
} catch (e) {
localStorage.removeItem(DRAFT_KEY);
}
}
}, []);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Wire the recovery flow before shipping.

recoveredDraft and showRecoveryModal are populated on mount, but nothing in this file ever renders a recovery UI or hydrates the form from that state. Saved drafts can be detected, but they can never be resumed or dismissed, so the same local draft will keep being rediscovered on every mount.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ai-story-generator.tsx` around lines 92 - 111, The draft recovery
state is set on mount (recoveredDraft, showRecoveryModal) but there is no UI or
handlers to resume or dismiss it; add a visible recovery modal/confirmation
component that renders when showRecoveryModal is true and implement two handlers
(e.g., handleResumeDraft and handleDismissDraft) that respectively hydrate the
editor/form state from recoveredDraft (call whatever form setters you have,
e.g., setPrompt, setTitle or setFormFromDraft) and clear the saved draft
(localStorage.removeItem(DRAFT_KEY)) while closing the modal
(setShowRecoveryModal(false)); also ensure dismissing the modal does not
re-trigger recovery on next mount by removing the draft or setting a session
lock (mintSessionLock) as appropriate.

Comment on lines +133 to 140
const response = await fetch('/api/groq', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'generate',
prompt: prompt.trim(),
title: title || undefined,
genre: selectedGenres.join(', ') || undefined,
setting: setting || undefined,
characters: mainCharacters || undefined,
themes: themes || undefined,
format: 'short',
maxTokens: 2000,
prompt,
options: { title: storyTitle, genres: selectedGenres, length: storyLength, temp: temperature[0] }
}),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Align this payload with /api/groq before relying on the new orchestration path.

app/api/groq/route.ts:22-61 reads top-level genre / length and only checks options.preference, while lib/groq-service.ts:65-71 consumes flattened generation params. Sending genres, length, and temp inside options means the selected genre, story length, and temperature are ignored, options.preference is never sent, and mainCharacterName / characterTraits never leave the client. Either flatten the payload here or update the route to consume this shape.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ai-story-generator.tsx` around lines 133 - 140, The client is
sending generation params nested under options (genres, length, temp) but the
API (/api/groq route handler and lib/groq-service.ts) expects flattened fields
(top-level genre, length, temp/temperature) and also expects options.preference
plus mainCharacterName and characterTraits to be present; update the payload in
components/ai-story-generator.tsx (the fetch POST for action 'generate') to send
flattened keys (e.g., genre, length, temp/temperature) at the top level, include
options.preference as its own field or as top-level preference, and include
mainCharacterName and characterTraits so the route handler and
lib/groq-service.ts consume them correctly, or alternatively update the
/api/groq route and lib/groq-service.ts to accept the current nested shape—pick
one and make all three places (client payload, route.ts handler,
lib/groq-service.ts) consistent.

Comment on lines 164 to 169
const resetForm = () => {
setPrompt('');
setTitle('');
setMainCharacters('');
setSetting('');
setThemes('');
setSelectedGenres([]);
setGeneratedContent('');
setGeneratedStory(null);
setActiveTab('input');
localStorage.removeItem(DRAFT_KEY);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

START OVER should clear the whole draft state.

This only resets prompt and generatedStory. storyTitle, mainCharacterName, selectedGenres, storyLength, characterTraits, and temperature all survive, so the next run starts with stale hidden state and those values get autosaved again.

🔧 Suggested fix
 const resetForm = () => {
   setPrompt('');
+  setStoryTitle('');
+  setSelectedGenres([]);
+  setStoryLength('medium');
+  setMainCharacterName('');
+  setCharacterTraits([]);
+  setTemperature([0.7]);
   setGeneratedStory(null);
   setActiveTab('input');
   localStorage.removeItem(DRAFT_KEY);
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const resetForm = () => {
setPrompt('');
setTitle('');
setMainCharacters('');
setSetting('');
setThemes('');
setSelectedGenres([]);
setGeneratedContent('');
setGeneratedStory(null);
setActiveTab('input');
localStorage.removeItem(DRAFT_KEY);
};
const resetForm = () => {
setPrompt('');
setStoryTitle('');
setSelectedGenres([]);
setStoryLength('medium');
setMainCharacterName('');
setCharacterTraits([]);
setTemperature([0.7]);
setGeneratedStory(null);
setActiveTab('input');
localStorage.removeItem(DRAFT_KEY);
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ai-story-generator.tsx` around lines 164 - 169, resetForm
currently only clears prompt and generatedStory, leaving other draft state to
persist; update resetForm to also reset storyTitle, mainCharacterName,
selectedGenres, storyLength, characterTraits, and temperature (and any other
draft-related state vars) back to their initial defaults, keep
setActiveTab('input') and localStorage.removeItem(DRAFT_KEY), and ensure any
handlers that autosave read the cleared values so the draft is not re-saved with
stale data.

Comment on lines +247 to +254
<TabsContent value="mint" className="text-center py-10">
<Wallet className="h-20 w-20 mx-auto mb-6" />
<h3 className="font-bangers text-4xl mb-6">MINT AS NFT?</h3>
{!connected ? (
<Button onClick={connectWallet} className="bg-black text-white font-bangers text-xl px-10 py-4 border-4 border-black">CONNECT WALLET</Button>
) : (
<Button className="bg-purple-500 text-white font-bangers text-2xl px-10 py-6 border-4 border-black shadow-[6px_6px_0px_0px_rgba(0,0,0,1)]">MINT MASTERPIECE</Button>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't ship a dead mint CTA.

When connected is true, the primary action at Line 253 has no onClick, so step 3 of the flow does nothing. Please either wire the mint handler here or render a disabled “coming soon” state until minting is implemented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ai-story-generator.tsx` around lines 247 - 254, The "MINT
MASTERPIECE" button rendered when connected has no onClick, so the mint CTA is
dead; either wire it to the mint handler (e.g., call an existing mint function
such as handleMint or mintMasterpiece from the component) by adding
onClick={handleMint} to the Button, or explicitly render a disabled/placeholder
state (disabled prop and label like "COMING SOON" and appropriate styling) until
minting is implemented; ensure you reference the same state/props used for
connectWallet and connected so the behavior is consistent.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (4)
components/ai-story-generator.tsx (4)

194-198: ⚠️ Potential issue | 🟡 Minor

resetForm leaves most form state intact.

Only prompt and generatedStory are cleared. Fields like storyTitle, mainCharacterName, selectedGenres, storyLength, and all pipeline parameters survive, causing the next generation to use stale values.

Proposed fix
 const resetForm = () => {
   setPrompt('');
+  setStoryTitle('');
+  setSelectedGenres([]);
+  setMainCharacterName('');
+  setStoryLength('medium');
+  // Reset pipeline params to defaults
+  setProseStyle('cinematic');
+  setNarrativeVoice('');
+  setSentimentTone('');
+  setDarknessLevel(3);
+  setHumorLevel(3);
+  // ... remaining pipeline params
   setGeneratedStory(null);
   setActiveTab('input');
+  localStorage.removeItem(DRAFT_KEY);
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ai-story-generator.tsx` around lines 194 - 198, resetForm
currently only clears prompt, generatedStory, and activeTab; update it to also
reset all other form state so subsequent generations don't reuse stale values.
In resetForm add calls to the other state setters used in this component—e.g.,
setStoryTitle(''), setMainCharacterName(''), setSelectedGenres([]),
setStoryLength(<defaultValue>)—and reset any pipeline parameter setters (for
example setTemperature, setTopP, setMaxTokens or other pipeline-related setters
used in this file) back to their defaults; keep setActiveTab('input') as is.

146-152: ⚠️ Potential issue | 🟡 Minor

Autosave without recovery is ineffective.

Drafts are persisted to localStorage but there's no corresponding useEffect on mount to restore them. Users will never see their saved work recovered—this makes the autosave logic dead code.

Add recovery logic on mount:

+  // Recovery on mount
+  useEffect(() => {
+    const saved = localStorage.getItem(DRAFT_KEY);
+    if (saved) {
+      try {
+        const draft = JSON.parse(saved);
+        if (draft.prompt) setPrompt(draft.prompt);
+        if (draft.storyTitle) setStoryTitle(draft.storyTitle);
+        if (draft.selectedGenres) setSelectedGenres(draft.selectedGenres);
+      } catch { /* ignore parse errors */ }
+    }
+  }, []);

   // Autosave Logic
   useEffect(() => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ai-story-generator.tsx` around lines 146 - 152, The autosave
useEffect writes drafts to localStorage under DRAFT_KEY but never restores them;
add a mounting useEffect that reads localStorage.getItem(DRAFT_KEY), JSON.parse
the value safely (try/catch), validate fields (prompt, storyTitle,
selectedGenres), and call the existing state setters (setPrompt, setStoryTitle,
setSelectedGenres) to repopulate the form; on parse error or invalid data, clear
the stored draft or ignore it to avoid breaking the component.

319-321: ⚠️ Potential issue | 🟠 Major

"MINT MASTERPIECE" button has no onClick handler.

When the wallet is connected, clicking this button does nothing. The useWeb3 hook (per web3-provider.tsx:340-356) doesn't expose a mint function either.

Either wire a mint handler or disable the button with a clear indicator:

-  <Button className="bg-purple-500 text-white font-bangers text-2xl px-10 py-6 border-4 border-black shadow-[6px_6px_0px_0px_rgba(0,0,0,1)]">MINT MASTERPIECE</Button>
+  <Button disabled className="bg-purple-500 text-white font-bangers text-2xl px-10 py-6 border-4 border-black opacity-50 cursor-not-allowed">
+    MINT MASTERPIECE (COMING SOON)
+  </Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ai-story-generator.tsx` around lines 319 - 321, The "MINT
MASTERPIECE" Button rendered in the AI story generator lacks an onClick handler
and the useWeb3 hook does not currently expose a mint function; add a mint flow
and wire it up or explicitly disable the button with a clear UI state.
Concretely, either (A) extend useWeb3 to export a mintMintable or mintArtwork
async function that performs the contract interaction (using the provider/signer
and existing contract wrapper) and then call that function from the Button's
onClick in the component, or (B) if minting isn't implemented yet, set the
Button to disabled with an explanatory label/state (e.g., "Connect Wallet to
Mint" or a tooltip) and ensure the component uses the hook's connection state to
toggle enabled/disabled. Update the component's Button props to include
onClick={handleMint} or disabled={...} and use the hook's unique symbols
(useWeb3, the new mint function or connection state) to locate and wire the
logic.

168-178: ⚠️ Potential issue | 🟠 Major

Payload fields are silently ignored by the API.

Per app/api/groq/route.ts:22-34, the route handler does not destructure title or pipelineParams. Additionally, lib/groq-service.ts expects tone, characters, and setting inside an options object—none of which are sent here.

Concrete gaps:

Field Sent? Used by API?
title ✗ (ignored)
pipelineParams ✗ (ignored)
mainCharacterName Expected as characters
options.tone Expected
options.setting Expected

Either update the API to consume these fields, or restructure the payload to match the existing contract:

 body: JSON.stringify({
   action: 'generate',
   prompt: prompt.trim(),
-  title: storyTitle,
   genre: selectedGenres.join(', '),
   length: storyLength,
-  pipelineParams,
+  options: {
+    tone: sentimentTone,
+    characters: mainCharacterName,
+    setting: settingType,
+  },
 }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ai-story-generator.tsx` around lines 168 - 178, The payload sent
in the fetch call in ai-story-generator.tsx (the body that includes action,
prompt, title, pipelineParams, etc.) does not match the API contract used by
app/api/groq/route.ts and lib/groq-service.ts (which expects an options object
with tone/setting and a characters field), so update the payload to match the
existing contract: replace or augment title/pipelineParams with the fields
lib/groq-service.ts expects by sending characters (e.g., characters: [{ name:
mainCharacterName }]) and an options object containing tone and setting
(options: { tone, setting }), ensure genre/length remain mapped to the names the
route handler expects, and remove or stop sending unused fields (or
alternatively update app/api/groq/route.ts and lib/groq-service.ts to
destructure and consume title and pipelineParams if you prefer changing the API
instead).
🧹 Nitpick comments (2)
components/ai-story-generator.tsx (2)

3-3: Unused import: AnimatePresence.

AnimatePresence is imported but not used in this component. Remove it to avoid dead code.

-import { motion, AnimatePresence } from 'framer-motion';
+import { motion } from 'framer-motion';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ai-story-generator.tsx` at line 3, The import line includes an
unused symbol AnimatePresence; remove AnimatePresence from the import statement
(keep motion) in the module so the import becomes only motion and verify there
are no remaining references to AnimatePresence in this component (e.g., search
for AnimatePresence usage) before committing to avoid dead code and linter
errors.

70-70: Consider stricter types for helper component props.

icon: any and options: any[] lose type safety. Consider:

import { LucideIcon } from 'lucide-react';

interface CollapsibleSectionProps {
  title: string;
  icon: LucideIcon;
  children: React.ReactNode;
  defaultOpen?: boolean;
}

interface OptionItem {
  value: string;
  label: string;
}

Also applies to: 83-83

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ai-story-generator.tsx` at line 70, Update the helper component
and related option usage to use stricter types: replace the loose any types in
CollapsibleSection's props by importing LucideIcon from lucide-react and typing
the component props (e.g., CollapsibleSectionProps with title: string, icon:
LucideIcon, children: React.ReactNode, defaultOpen?: boolean) and replace the
options: any[] usage with a typed OptionItem interface (value: string, label:
string). Apply these types to the CollapsibleSection function signature and to
any variables/props named options or option items used around the
CollapsibleSection and the code referenced at the other occurrence (line ~83) so
the icon and option arrays are strongly typed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@components/ai-story-generator.tsx`:
- Around line 194-198: resetForm currently only clears prompt, generatedStory,
and activeTab; update it to also reset all other form state so subsequent
generations don't reuse stale values. In resetForm add calls to the other state
setters used in this component—e.g., setStoryTitle(''),
setMainCharacterName(''), setSelectedGenres([]),
setStoryLength(<defaultValue>)—and reset any pipeline parameter setters (for
example setTemperature, setTopP, setMaxTokens or other pipeline-related setters
used in this file) back to their defaults; keep setActiveTab('input') as is.
- Around line 146-152: The autosave useEffect writes drafts to localStorage
under DRAFT_KEY but never restores them; add a mounting useEffect that reads
localStorage.getItem(DRAFT_KEY), JSON.parse the value safely (try/catch),
validate fields (prompt, storyTitle, selectedGenres), and call the existing
state setters (setPrompt, setStoryTitle, setSelectedGenres) to repopulate the
form; on parse error or invalid data, clear the stored draft or ignore it to
avoid breaking the component.
- Around line 319-321: The "MINT MASTERPIECE" Button rendered in the AI story
generator lacks an onClick handler and the useWeb3 hook does not currently
expose a mint function; add a mint flow and wire it up or explicitly disable the
button with a clear UI state. Concretely, either (A) extend useWeb3 to export a
mintMintable or mintArtwork async function that performs the contract
interaction (using the provider/signer and existing contract wrapper) and then
call that function from the Button's onClick in the component, or (B) if minting
isn't implemented yet, set the Button to disabled with an explanatory
label/state (e.g., "Connect Wallet to Mint" or a tooltip) and ensure the
component uses the hook's connection state to toggle enabled/disabled. Update
the component's Button props to include onClick={handleMint} or disabled={...}
and use the hook's unique symbols (useWeb3, the new mint function or connection
state) to locate and wire the logic.
- Around line 168-178: The payload sent in the fetch call in
ai-story-generator.tsx (the body that includes action, prompt, title,
pipelineParams, etc.) does not match the API contract used by
app/api/groq/route.ts and lib/groq-service.ts (which expects an options object
with tone/setting and a characters field), so update the payload to match the
existing contract: replace or augment title/pipelineParams with the fields
lib/groq-service.ts expects by sending characters (e.g., characters: [{ name:
mainCharacterName }]) and an options object containing tone and setting
(options: { tone, setting }), ensure genre/length remain mapped to the names the
route handler expects, and remove or stop sending unused fields (or
alternatively update app/api/groq/route.ts and lib/groq-service.ts to
destructure and consume title and pipelineParams if you prefer changing the API
instead).

---

Nitpick comments:
In `@components/ai-story-generator.tsx`:
- Line 3: The import line includes an unused symbol AnimatePresence; remove
AnimatePresence from the import statement (keep motion) in the module so the
import becomes only motion and verify there are no remaining references to
AnimatePresence in this component (e.g., search for AnimatePresence usage)
before committing to avoid dead code and linter errors.
- Line 70: Update the helper component and related option usage to use stricter
types: replace the loose any types in CollapsibleSection's props by importing
LucideIcon from lucide-react and typing the component props (e.g.,
CollapsibleSectionProps with title: string, icon: LucideIcon, children:
React.ReactNode, defaultOpen?: boolean) and replace the options: any[] usage
with a typed OptionItem interface (value: string, label: string). Apply these
types to the CollapsibleSection function signature and to any variables/props
named options or option items used around the CollapsibleSection and the code
referenced at the other occurrence (line ~83) so the icon and option arrays are
strongly typed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: da875b2b-8f74-4443-8a70-c75ea168760d

📥 Commits

Reviewing files that changed from the base of the PR and between 1ca681c and 8f22023.

📒 Files selected for processing (1)
  • components/ai-story-generator.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request in progress Feature Request or proposal for a new feature Hard Advanced, complex, or challenging issues OSCG26 Open Source Connect Global 2026 Contributions size/XL testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[AI] Multi-Model Orchestration, Fallback, and A/B Testing for Story Generation

2 participants