fix: refresh meme prompt on reload

This commit is contained in:
2026-08-08 14:21:32 +08:00
parent 9ab4432450
commit e3e296bd5e
3 changed files with 64 additions and 24 deletions
@@ -18,6 +18,7 @@ import top.jie65535.mirai.JChatGPT.reload
import top.jie65535.mirai.config.PluginConfig import top.jie65535.mirai.config.PluginConfig
import top.jie65535.mirai.config.ModelConfig import top.jie65535.mirai.config.ModelConfig
import top.jie65535.mirai.config.ModelConfigMigration import top.jie65535.mirai.config.ModelConfigMigration
import top.jie65535.mirai.conversation.ConversationContext
import top.jie65535.mirai.data.PluginData import top.jie65535.mirai.data.PluginData
import top.jie65535.mirai.data.SkillStore import top.jie65535.mirai.data.SkillStore
import top.jie65535.mirai.data.TokenUsageStore import top.jie65535.mirai.data.TokenUsageStore
@@ -58,6 +59,7 @@ object PluginCommands : CompositeCommand(
ProfileAutoMaintenance.clear() ProfileAutoMaintenance.clear()
} }
SkillStore.reload() SkillStore.reload()
ConversationContext.invalidateMemePromptCache()
sendMessage("OK") sendMessage("OK")
} }
@@ -72,6 +72,39 @@ internal class ReplyIndex {
fun indexOfIds(ids: String): Int? = indexByIds[ids] fun indexOfIds(ids: String): Int? = indexByIds[ids]
} }
internal class MemePromptCache {
private var prompt: String? = null
fun get(directoryPath: String): String = synchronized(this) {
prompt ?: buildPrompt(directoryPath).also { prompt = it }
}
fun clear() {
synchronized(this) { prompt = null }
}
private fun buildPrompt(directoryPath: String): String {
if (directoryPath.isEmpty()) return ""
return buildString {
val directory = File(directoryPath)
if (!directory.isDirectory) {
append("配置的meme路径不存在!")
return@buildString
}
append("memes文件夹地址为:").appendLine(directoryPath)
val memes = directory.list().orEmpty()
if (memes.isEmpty()) {
append("暂无表情包~")
} else {
memes.forEach { append("- ").appendLine(it) }
appendLine()
append("表情包示例:![").append(memes[0]).append("](")
.append(File(directory, memes[0]).absoluteFile).appendLine(")")
}
}
}
}
internal object ConversationContext { internal object ConversationContext {
private val chronologicalRecordOrder = compareBy<ChatMessageRecord> { it.time } private val chronologicalRecordOrder = compareBy<ChatMessageRecord> { it.time }
.thenBy { if (it.id == 0L) Long.MAX_VALUE else it.id } .thenBy { if (it.id == 0L) Long.MAX_VALUE else it.id }
@@ -81,16 +114,21 @@ internal object ConversationContext {
private val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd E HH:mm:ss") private val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd E HH:mm:ss")
private val shortTimeFormatter = DateTimeFormatter.ofPattern("HH:mm") private val shortTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
.withZone(ZoneOffset.systemDefault()) .withZone(ZoneOffset.systemDefault())
private var memePrompt: String? = null private val memePromptCache = MemePromptCache()
fun clearCache() { fun clearCache() {
synchronized(contextCache) { contextCache.clear() } synchronized(contextCache) { contextCache.clear() }
} }
fun invalidateMemePromptCache() {
memePromptCache.clear()
}
fun clearAll() { fun clearAll() {
synchronized(contextCache) { contextCache.clear() } synchronized(contextCache) { contextCache.clear() }
synchronized(replyIndexes) { replyIndexes.clear() } synchronized(replyIndexes) { replyIndexes.clear() }
synchronized(imageIndexes) { imageIndexes.clear() } synchronized(imageIndexes) { imageIndexes.clear() }
memePromptCache.clear()
} }
fun cache(subjectId: Long): ConversationCache? = synchronized(contextCache) { fun cache(subjectId: Long): ConversationCache? = synchronized(contextCache) {
@@ -150,7 +188,7 @@ internal object ConversationContext {
replace("{skills}") { replace("{skills}") {
if (PluginConfig.skillsEnabled) SkillStore.buildIndexPrompt() else "暂无技能" if (PluginConfig.skillsEnabled) SkillStore.buildIndexPrompt() else "暂无技能"
} }
replace("{meme}") { buildMemePrompt() } replace("{meme}") { memePromptCache.get(PluginConfig.memeDir) }
return prompt.toString() return prompt.toString()
} }
@@ -258,28 +296,6 @@ internal object ConversationContext {
imageIndexes.getOrPut(subjectId) { ImageIndex() } imageIndexes.getOrPut(subjectId) { ImageIndex() }
} }
private fun buildMemePrompt(): String {
memePrompt?.let { return it }
if (PluginConfig.memeDir.isEmpty()) return ""
return buildString {
val directory = File(PluginConfig.memeDir)
if (!directory.isDirectory) {
append("配置的meme路径不存在!")
return@buildString
}
append("memes文件夹地址为:").appendLine(PluginConfig.memeDir)
val memes = directory.list().orEmpty()
if (memes.isEmpty()) {
append("暂无表情包~")
} else {
memes.forEach { append("- ").appendLine(it) }
appendLine()
append("表情包示例:![").append(memes[0]).append("](")
.append(File(directory, memes[0]).absoluteFile).appendLine(")")
}
}.also { memePrompt = it }
}
private fun appendUserProfileContext( private fun appendUserProfileContext(
target: StringBuilder, target: StringBuilder,
history: List<ChatMessageRecord>, history: List<ChatMessageRecord>,
@@ -1,9 +1,12 @@
package top.jie65535.mirai.conversation package top.jie65535.mirai.conversation
import java.nio.file.Files
import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.message.data.MessageSourceKind
import top.jie65535.mirai.data.ChatMessageRecord import top.jie65535.mirai.data.ChatMessageRecord
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull import kotlin.test.assertNull
class ConversationContextTest { class ConversationContextTest {
@@ -22,6 +25,25 @@ class ConversationContextTest {
assertNull(index.indexOfIds("missing")) assertNull(index.indexOfIds("missing"))
} }
@Test
fun memePromptCacheRefreshesDirectoryListingAfterClear() {
val directory = Files.createTempDirectory("jchatgpt-meme-prompt-test-")
try {
Files.createFile(directory.resolve("before.png"))
val cache = MemePromptCache()
assertContains(cache.get(directory.toString()), "before.png")
Files.createFile(directory.resolve("after.png"))
assertFalse(cache.get(directory.toString()).contains("after.png"))
cache.clear()
assertContains(cache.get(directory.toString()), "after.png")
} finally {
directory.toFile().deleteRecursively()
}
}
private fun record(ids: String?, time: Int) = ChatMessageRecord( private fun record(ids: String?, time: Int) = ChatMessageRecord(
botId = 1, botId = 1,
fromId = 2, fromId = 2,