fix: 修复 Liblib provider 认证和多个图片生成关键缺陷
- 重写 LiblibImageProvider:Bearer token 改为 HMAC-SHA1 签名认证, 适配 Liblib 真实 API(Star-3 Alpha 文生图端点) - 修复 chapter.images JSON 列原地修改不持久化(深拷贝+整列重赋值) - 修复 generate_chapter_images 在事务提交前派发(改为 commit 后统一 delay) - 修复 initialize_chapter_images 覆盖已完成图片(新增 merge 去重逻辑) - 修复 Android failed 图片渲染为错误卡片(改为隐藏,保持正文连续) - 模型模板 UUID 改为环境变量配置(LIBLIB_TEMPLATE_UUID) - 更新 .env 凭证格式为 ACCESS_KEY/SECRET_KEY - 补充 test_memoir_image_bootstrap 缺失的 unittest.mock 导入 Made-with: Cursor
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.huaga.life_echo.ui.components.memoir
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.assertDoesNotExist
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
@@ -50,7 +51,7 @@ class ChapterReadingImageBlocksTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun chapterReadingView_showsFailureCard_forFailedImage_withoutRawPlaceholderText() {
|
||||
fun chapterReadingView_hidesFailedImageBlock() {
|
||||
val chapter = ChapterContentDto(
|
||||
id = "chapter-1",
|
||||
title = "童年的夏天",
|
||||
@@ -81,6 +82,6 @@ class ChapterReadingImageBlocksTest {
|
||||
|
||||
composeRule.setContent { ChapterReadingView(chapter = chapter) }
|
||||
|
||||
composeRule.onNodeWithTag("memoir-image-error-0").assertIsDisplayed()
|
||||
composeRule.onNodeWithTag("memoir-image-error-0").assertDoesNotExist()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ fun splitMemoirContent(content: String, images: List<ChapterImageDto>): List<Mem
|
||||
if (before.isNotBlank()) blocks += MemoirContentBlock.Text(before)
|
||||
if (image.status == "completed" && !image.url.isNullOrBlank()) {
|
||||
blocks += MemoirContentBlock.Image(image)
|
||||
} else if (image.status in listOf("pending", "processing", "failed")) {
|
||||
} else if (image.status == "pending" || image.status == "processing") {
|
||||
blocks += MemoirContentBlock.Image(image)
|
||||
}
|
||||
remaining = parts.getOrElse(1) { "" }
|
||||
|
||||
@@ -8,10 +8,8 @@ import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -21,7 +19,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import com.huaga.life_echo.network.models.ChapterImageDto
|
||||
@@ -73,29 +70,6 @@ fun MemoirInlineImage(
|
||||
)
|
||||
}
|
||||
}
|
||||
"failed" -> Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(LightPurple.copy(alpha = 0.10f))
|
||||
.padding(16.dp)
|
||||
.testTag("memoir-image-error-${image.index}")
|
||||
) {
|
||||
Text(
|
||||
text = "图片生成失败",
|
||||
fontSize = AppTypography.bodyMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = SlatePurple,
|
||||
)
|
||||
if (image.description.isNotBlank()) {
|
||||
Text(
|
||||
text = image.description,
|
||||
fontSize = AppTypography.captionMedium,
|
||||
color = SlatePurple.copy(alpha = 0.6f),
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.huaga.life_echo.ui.components.memoir
|
||||
|
||||
import com.huaga.life_echo.network.models.ChapterImageDto
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
@@ -56,4 +57,33 @@ class MemoirContentBlocksTest {
|
||||
val text = (blocks[0] as MemoirContentBlock.Text).content
|
||||
assertTrue(!text.contains("IMAGE:"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun splitMemoirContent_skipsFailedImages_andRemovesTheirPlaceholders() {
|
||||
val blocks = splitMemoirContent(
|
||||
content = "开头。\n\n{{{{IMAGE:生成失败的图}}}}\n\n结尾。",
|
||||
images = listOf(
|
||||
ChapterImageDto(
|
||||
index = 0,
|
||||
placeholder = "{{{{IMAGE:生成失败的图}}}}",
|
||||
description = "生成失败的图",
|
||||
prompt = null,
|
||||
url = null,
|
||||
status = "failed",
|
||||
provider = "liblib",
|
||||
style = "watercolor",
|
||||
size = "1024x1024",
|
||||
error = "provider timeout",
|
||||
created_at = null,
|
||||
updated_at = null,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
assertFalse(blocks.any { it is MemoirContentBlock.Image })
|
||||
val combinedText = blocks.filterIsInstance<MemoirContentBlock.Text>().joinToString("\n") { it.content }
|
||||
assertFalse(combinedText.contains("IMAGE:"))
|
||||
assertTrue(combinedText.contains("开头"))
|
||||
assertTrue(combinedText.contains("结尾"))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user