diff --git a/backend/internal/backend/handlers_textgen.go b/backend/internal/backend/handlers_textgen.go index 2659140..2c21536 100644 --- a/backend/internal/backend/handlers_textgen.go +++ b/backend/internal/backend/handlers_textgen.go @@ -99,12 +99,17 @@ func (s *Server) handleAdminTextGenChapterNames(w http.ResponseWriter, r *http.R } systemPrompt := `You are a chapter title editor for a web novel platform. ` + - `The user will provide a list of chapter numbers with their current titles, ` + - `and a naming pattern. Your task is to produce a renamed version of every chapter ` + - `following the pattern exactly. ` + - `Respond ONLY with a JSON array — no prose, no markdown fences, no explanation. ` + - `Each element must be an object: {"number": , "title": }. ` + - `Output every chapter in the input list. Do not skip any.` + `The user provides a list of chapter numbers with their current titles, ` + + `and a naming pattern template. ` + + `Your job: produce one new title for every chapter, following the pattern exactly. ` + + `Pattern placeholders: {n} = the chapter number (integer), {scene} = a very short (2–5 word) scene hint derived from the existing title. ` + + `RULES: ` + + `1. Do NOT include the chapter number inside the title text — the {n} placeholder is already in the pattern. ` + + `2. Do NOT include any prefix like "Chapter X -" or "Chapter X:" inside the title field itself. ` + + `3. The "title" field in your JSON must be the fully-rendered string (e.g. if pattern is "Chapter {n}: {scene}", output "Chapter 3: The Bet"). ` + + `4. Respond ONLY with a raw JSON array — no prose, no markdown fences, no explanation. ` + + `5. Each element: {"number": , "title": }. ` + + `6. Output every chapter in the input list, in order. Do not skip any.` userPrompt := fmt.Sprintf( "Naming pattern: %s\n\nChapters:\n%s", @@ -117,8 +122,14 @@ func (s *Server) handleAdminTextGenChapterNames(w http.ResponseWriter, r *http.R model = cfai.DefaultTextModel } + // Default to 4096 tokens so large chapter lists are not truncated. + maxTokens := req.MaxTokens + if maxTokens <= 0 { + maxTokens = 4096 + } + s.deps.Log.Info("admin: text-gen chapter-names requested", - "slug", req.Slug, "chapters", len(chapters), "model", model) + "slug", req.Slug, "chapters", len(chapters), "model", model, "max_tokens", maxTokens) raw, genErr := s.deps.TextGen.Generate(r.Context(), cfai.TextRequest{ Model: model, @@ -126,7 +137,7 @@ func (s *Server) handleAdminTextGenChapterNames(w http.ResponseWriter, r *http.R {Role: "system", Content: systemPrompt}, {Role: "user", Content: userPrompt}, }, - MaxTokens: req.MaxTokens, + MaxTokens: maxTokens, }) if genErr != nil { s.deps.Log.Error("admin: text-gen chapter-names failed", "err", genErr) diff --git a/ui/src/routes/admin/text-gen/+page.svelte b/ui/src/routes/admin/text-gen/+page.svelte index eabce6d..35f617c 100644 --- a/ui/src/routes/admin/text-gen/+page.svelte +++ b/ui/src/routes/admin/text-gen/+page.svelte @@ -71,6 +71,11 @@ ); chRawResponse = body.raw_response ?? ''; chUsedModel = body.model ?? ''; + // If backend returned chapters:[] but we have a raw response, the model + // output was unparseable (likely truncated). Treat it as an error. + if (chProposals.length === 0 && chRawResponse.trim().length > 0) { + chError = 'Model response could not be parsed (output may be truncated). Raw response shown below.'; + } } catch { chError = 'Network error.'; } finally { @@ -287,6 +292,9 @@ {#if chError}

{chError}

+ {#if chRawResponse} +
{chRawResponse}
+ {/if} {/if}