Update version to v1.2.0

Add LaTex formula convert to image
This commit is contained in:
2024-11-06 22:56:22 +08:00
parent a6bd48aa4e
commit 49b1b0c345
3 changed files with 105 additions and 12 deletions

View File

@ -1,5 +1,5 @@
plugins { plugins {
val kotlinVersion = "1.9.24" val kotlinVersion = "1.8.10"
kotlin("jvm") version kotlinVersion kotlin("jvm") version kotlinVersion
kotlin("plugin.serialization") version kotlinVersion kotlin("plugin.serialization") version kotlinVersion
@ -7,7 +7,7 @@ plugins {
} }
group = "top.jie65535.mirai" group = "top.jie65535.mirai"
version = "1.1.0" version = "1.2.0"
repositories { repositories {
mavenCentral() mavenCentral()
@ -16,8 +16,10 @@ repositories {
val openaiClientVersion = "3.8.2" val openaiClientVersion = "3.8.2"
val ktorVersion = "2.3.12" val ktorVersion = "2.3.12"
val jLatexMathVersion = "1.0.7"
dependencies { dependencies {
implementation("com.aallam.openai:openai-client:$openaiClientVersion") implementation("com.aallam.openai:openai-client:$openaiClientVersion")
implementation("io.ktor:ktor-client-okhttp:$ktorVersion") implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
implementation("org.scilab.forge:jlatexmath:$jLatexMathVersion")
} }

View File

@ -16,6 +16,7 @@ import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.permission.PermissionService.Companion.hasPermission import net.mamoe.mirai.console.permission.PermissionService.Companion.hasPermission
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.isOperator import net.mamoe.mirai.contact.isOperator
import net.mamoe.mirai.event.GlobalEventChannel import net.mamoe.mirai.event.GlobalEventChannel
import net.mamoe.mirai.event.events.FriendMessageEvent import net.mamoe.mirai.event.events.FriendMessageEvent
@ -24,14 +25,16 @@ import net.mamoe.mirai.event.events.MessageEvent
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.MessageSource.Key.quote import net.mamoe.mirai.message.data.MessageSource.Key.quote
import net.mamoe.mirai.message.sourceIds import net.mamoe.mirai.message.sourceIds
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
import net.mamoe.mirai.utils.info import net.mamoe.mirai.utils.info
import java.util.regex.Pattern
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
object JChatGPT : KotlinPlugin( object JChatGPT : KotlinPlugin(
JvmPluginDescription( JvmPluginDescription(
id = "top.jie65535.mirai.JChatGPT", id = "top.jie65535.mirai.JChatGPT",
name = "J ChatGPT", name = "J ChatGPT",
version = "1.1.0", version = "1.2.0",
) { ) {
author("jie65535") author("jie65535")
} }
@ -61,13 +64,14 @@ object JChatGPT : KotlinPlugin(
fun updateOpenAiToken(token: String) { fun updateOpenAiToken(token: String) {
val timeout = PluginConfig.timeout.milliseconds val timeout = PluginConfig.timeout.milliseconds
openAi = OpenAI(token, openAi = OpenAI(
token,
host = OpenAIHost(baseUrl = PluginConfig.openAiApi), host = OpenAIHost(baseUrl = PluginConfig.openAiApi),
timeout = Timeout(request = timeout, connect = timeout, socket = timeout) timeout = Timeout(request = timeout, connect = timeout, socket = timeout)
) )
} }
// private val userContext = ConcurrentMap<Long, MutableList<ChatMessage>>() // private val userContext = ConcurrentMap<Long, MutableList<ChatMessage>>()
private const val REPLAY_QUEUE_MAX = 30 private const val REPLAY_QUEUE_MAX = 30
private val replyMap = ConcurrentMap<Int, MutableList<ChatMessage>>() private val replyMap = ConcurrentMap<Int, MutableList<ChatMessage>>()
private val replyQueue = mutableListOf<Int>() private val replyQueue = mutableListOf<Int>()
@ -143,23 +147,26 @@ object JChatGPT : KotlinPlugin(
val content = reply.content ?: "..." val content = reply.content ?: "..."
val replyMsg = subject.sendMessage( val replyMsg = subject.sendMessage(
if (content.length < 100) { if (content.length < 128) {
message.quote() + content message.quote() + toMessage(subject, content)
} else { } else {
// 消息内容太长则转为转发消息避免刷屏 // 消息内容太长则转为转发消息避免刷屏
buildForwardMessage { buildForwardMessage {
for (item in history) { for (item in history) {
val temp = toMessage(subject, item.content ?: "...")
when (item.role) { when (item.role) {
Role.User -> sender says (item.content ?: "...") Role.User -> sender says temp
Role.Assistant -> bot says (item.content ?: "...") Role.Assistant -> bot says temp
} }
} }
} }
} }
) )
val msgId = replyMsg.sourceIds[0] if (replyMsg.sourceIds.isNotEmpty()) {
replyMap[msgId] = history val msgId = replyMsg.sourceIds[0]
replyQueue.add(msgId) replyMap[msgId] = history
replyQueue.add(msgId)
}
if (replyQueue.size > REPLAY_QUEUE_MAX) { if (replyQueue.size > REPLAY_QUEUE_MAX) {
replyMap.remove(replyQueue.removeAt(0)) replyMap.remove(replyQueue.removeAt(0))
} }
@ -171,6 +178,58 @@ object JChatGPT : KotlinPlugin(
} }
} }
private val laTeXPattern = Pattern.compile(
"\\\\\\((.+?)\\\\\\)|" + // 匹配行内公式 \(...\)
"\\\\\\[(.+?)\\\\\\]|" + // 匹配独立公式 \[...\]
"\\$\\$([^$]+?)\\$\\$|" + // 匹配独立公式 $$...$$
"\\$(.+?)\\$|" + // 匹配行内公式 $...$
"```latex\\s*([^`]+?)\\s*```" // 匹配 ```latex ... ```
, Pattern.DOTALL
)
/**
* 将聊天内容转为聊天消息如果聊天中包含LaTeX表达式将会转为图片拼接到消息中
*
* @param contact 联系对象
* @param content 文本内容
* @return 构造的消息
*/
private suspend fun toMessage(contact: Contact, content: String): Message {
if (content.length < 3) {
return PlainText(content)
}
return buildMessageChain {
// 匹配LaTeX表达式
val matcher = laTeXPattern.matcher(content)
var index = 0
while (matcher.find()) {
for (i in 1..matcher.groupCount()) {
if (matcher.group(i) == null) {
continue
}
try {
// 将所有匹配的LaTeX公式转为图片拼接到消息中
val formula = matcher.group(i)
val imageByteArray = LaTeXConverter.convertToImage(formula, "png")
val resource = imageByteArray.toExternalResource("png")
val image = contact.uploadImage(resource)
// 拼接公式前的文本
append(content, index, matcher.start())
// 插入图片
append(image)
// 移动索引
index = matcher.end()
} catch (ex: Throwable) {
logger.warning("处理LaTeX表达式时异常", ex)
}
}
}
// 拼接后续消息
append(content, index, content.length)
}
}
private suspend fun chatCompletion(messages: List<ChatMessage>): ChatMessage { private suspend fun chatCompletion(messages: List<ChatMessage>): ChatMessage {
val openAi = this.openAi ?: throw NullPointerException("OpenAI Token 未设置,无法开始") val openAi = this.openAi ?: throw NullPointerException("OpenAI Token 未设置,无法开始")
val request = ChatCompletionRequest(ModelId(PluginConfig.chatModel), messages) val request = ChatCompletionRequest(ModelId(PluginConfig.chatModel), messages)

View File

@ -0,0 +1,32 @@
package top.jie65535.mirai
import org.scilab.forge.jlatexmath.TeXConstants
import org.scilab.forge.jlatexmath.TeXFormula
import java.awt.Color
import java.awt.Insets
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import javax.imageio.ImageIO
import javax.swing.JLabel
object LaTeXConverter {
/**
* 转换LaTeX到图片字节数组
*/
fun convertToImage(latexString: String, format: String = "png"): ByteArray {
val formula = TeXFormula(latexString)
val icon = formula.TeXIconBuilder().setStyle(TeXConstants.STYLE_DISPLAY).setSize(20f).build()
icon.insets = Insets(5, 5, 5, 5)
val image = BufferedImage(icon.iconWidth, icon.iconHeight, BufferedImage.TYPE_INT_ARGB)
val g2 = image.createGraphics()
g2.color = Color.white
g2.fillRect(0, 0, icon.iconWidth, icon.iconHeight)
val jl = JLabel()
jl.setForeground(Color(0, 0, 0))
icon.paintIcon(jl, g2, 0, 0)
val stream = ByteArrayOutputStream()
ImageIO.write(image, format, stream)
return stream.toByteArray()
}
}