{"record":{"id":"bf3189fec35fdbbc","repo":"chatboxai/chatbox","slug":"no-readable-text-content-found-in-epub-file","errorCode":null,"errorMessage":"No readable text content found in EPUB file","messagePattern":"No readable text content found in EPUB file","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/main/file-parser.ts","lineNumber":309,"sourceCode":"\n            return plainText || null\n          } catch (chapterError) {\n            log.warn(`Failed to read chapter ${chapter.id}, skipping:`, chapterError)\n            return null // Return null for failed chapters to continue processing\n          }\n        }\n\n        // Extract text from all chapters using concurrent processing\n        log.info(`Starting concurrent processing of ${epub.flow.length} chapters with concurrency: 8`)\n\n        const chapterResults = await concurrentMap(epub.flow as { id: string }[], processChapter, 8)\n        const chapterTexts = chapterResults.filter((text: string | null) => text !== null) as string[]\n        log.info(`Successfully processed ${chapterTexts.length}/${epub.flow.length} chapters`)\n\n        const fullText = chapterTexts.join('\\n\\n')\n\n        if (!fullText) {\n          throw new Error('No readable text content found in EPUB file')\n        }\n\n        log.info(`Successfully extracted ${fullText.length} characters from ${chapterTexts.length} chapters`)\n        resolve(fullText)\n      } catch (error) {\n        log.error('Error extracting EPUB content:', error)\n        reject(error)\n      }\n    })\n\n    epub.parse()\n  })\n}\n","sourceCodeStart":291,"sourceCodeEnd":323,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/file-parser.ts#L291-L323","documentation":"Thrown at the top of CustomGemini.paint() when `isGeminiImageModel(this.options.model.modelId)` is false. isGeminiImageModel (in definitions/image-models.ts) returns true only when the model id contains both 'gemini' and 'image'. paint() is the image-generation entry point, so calling it on a text-only Gemini model is a programmer/UX error, not a network condition — the guard fires before any API call.","triggerScenarios":"UI invokes paint() with the currently selected model id even though it is a text model (e.g. `gemini-2.0-flash`, `gemini-1.5-pro`); a saved session has a non-image Gemini model selected when the user clicks the paint button; model list refresh replaced an image model id with its text counterpart; the model id does not contain the literal substring 'image' (e.g. a renamed alias).","commonSituations":"User selects 'gemini-2.5-flash' (text) and opens the image-generation panel expecting it to work; a custom Gemini provider exposes a model under a nickname whose underlying id lacks 'image'; version upgrade renamed the image model (e.g. from `gemini-2.0-flash-exp-image-generation` to something without 'image' in the id) so isGeminiImageModel no longer recognises it.","solutions":["Before calling paint(), gate the UI on `isGeminiImageModel(modelId)` (model id must include both 'gemini' and 'image') and only enable the paint action for matching models.","Switch the selected model to a known Gemini image model such as `gemini-2.0-flash-exp-image-generation` or `gemini-2.5-flash-image-preview`.","If you maintain a custom provider, ensure the model id for image-capable models contains the substring 'image' so the heuristic matches.","Update image-models.ts if Google ships a new image model whose id does not match the `gemini`+`image` rule."],"exampleFix":"// before\nawait model.paint({ prompt, num: 1 })  // throws on text models\n\n// after\nguard before calling:\nif (!isGeminiImageModel(session.modelId)) {\n  showWarning('Select a Gemini image model to generate images')\n} else {\n  await model.paint({ prompt, num: 1 })\n}","handlingStrategy":"validation","validationCode":"function canPaint(modelId: string): boolean {\n  return modelId.includes('gemini') && modelId.includes('image')\n}\n// before invoking paint:\nif (!canPaint(session.modelId)) disablePaintUI()","typeGuard":"import { isGeminiImageModel } from '../image-models'\nfunction assertImageCapable(modelId: string): void {\n  if (!isGeminiImageModel(modelId)) throw new Error(`Model ${modelId} cannot generate images`)\n}","tryCatchPattern":"try { await gemini.paint({ prompt, num: 1 }) }\ncatch (e) {\n  if (e instanceof ApiError && e.message.includes('does not support image generation')) {\n    showWarning('Switch to a Gemini image model to paint')\n    return []\n  }\n  throw e\n}","preventionTips":["Bind the paint button's `disabled` state to isGeminiImageModel(selectedModelId).","Update image-models.ts when Google ships image models whose id breaks the gemini+image rule.","Never invoke paint() on a model chosen by display name — always check the underlying id."],"tags":["gemini","image-generation","validation","model-capability","ui"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}