mirror of
https://github.com/jie65535/JChatGPT.git
synced 2025-06-02 17:39:10 +08:00
Update version to v1.2.0
Add LaTex formula convert to image
This commit is contained in:
parent
a6bd48aa4e
commit
49b1b0c345
@ -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")
|
||||||
}
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
if (replyMsg.sourceIds.isNotEmpty()) {
|
||||||
val msgId = replyMsg.sourceIds[0]
|
val msgId = replyMsg.sourceIds[0]
|
||||||
replyMap[msgId] = history
|
replyMap[msgId] = history
|
||||||
replyQueue.add(msgId)
|
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)
|
||||||
|
32
src/main/kotlin/LaTeXConverter.kt
Normal file
32
src/main/kotlin/LaTeXConverter.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user