From ec62bf5bcc7a392bcbc80c96732cc55b2892127d Mon Sep 17 00:00:00 2001 From: HanZiyao Date: Sat, 8 Feb 2025 16:05:44 +0800 Subject: [PATCH] =?UTF-8?q?AI=20=E5=AF=B9=E8=AF=9D=E5=8A=A9=E6=89=8B?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + cloudapi-web/build.gradle.kts | 11 ++- .../models/ChatCompletionRequest.kt | 29 +++++++ .../ChatCompletionRequestMessagesInner.kt | 24 ++++++ .../scs/controller/models/ChatHistoryItem.kt | 30 +++++++ .../models/GetChatRecordsRequest.kt | 30 +++++++ .../buaa/scs/controller/plugins/Monitoring.kt | 2 +- .../buaa/scs/controller/plugins/Routing.kt | 1 + .../cn/edu/buaa/scs/model/ChatHistory.kt | 34 ++++++++ .../main/kotlin/cn/edu/buaa/scs/route/Chat.kt | 32 ++++++++ .../kotlin/cn/edu/buaa/scs/service/Chat.kt | 78 +++++++++++++++++++ .../cn/edu/buaa/scs/utils/ApplicationCall.kt | 3 + gradle.properties | 1 + openapi/cloudapi_v2.yaml | 64 +++++++++++++++ 14 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequest.kt create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequestMessagesInner.kt create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatHistoryItem.kt create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/GetChatRecordsRequest.kt create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/model/ChatHistory.kt create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Chat.kt create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Chat.kt diff --git a/.gitignore b/.gitignore index bdf87c89..b7bcbc95 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,11 @@ bin/ ### IntelliJ IDEA ### .idea +.kotlin *.iws *.iml *.ipr +*.log out/ !**/src/main/**/out/ !**/src/test/**/out/ diff --git a/cloudapi-web/build.gradle.kts b/cloudapi-web/build.gradle.kts index 04a6fd3b..05ecb550 100644 --- a/cloudapi-web/build.gradle.kts +++ b/cloudapi-web/build.gradle.kts @@ -20,8 +20,15 @@ kotlin { } repositories { + maven { setUrl("https://maven.aliyun.com/repository/central") } + maven { setUrl("https://maven.aliyun.com/repository/jcenter") } + maven { setUrl("https://maven.aliyun.com/repository/google") } + maven { setUrl("https://maven.aliyun.com/repository/gradle-plugin") } + maven { setUrl("https://maven.aliyun.com/repository/public") } + maven { setUrl("https://jitpack.io") } + gradlePluginPortal() + google() mavenCentral() - maven(url = "https://jitpack.io") } dependencies { @@ -43,6 +50,8 @@ dependencies { implementation("io.ktor:ktor-server-status-pages:$ktor_version") testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version") testImplementation("io.ktor:ktor-server-test-host-jvm:$ktor_version") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:1.7.3") + implementation("ch.qos.logback:logback-classic:1.4.11") // Redis implementation("io.lettuce:lettuce-core:6.1.5.RELEASE") diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequest.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequest.kt new file mode 100644 index 00000000..82933eec --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequest.kt @@ -0,0 +1,29 @@ +/** +* cloudapi_v2 +* buaa scs cloud api v2 +* +* The version of the OpenAPI document: 2.0 +* Contact: loheagn@icloud.com +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ +package cn.edu.buaa.scs.controller.models + +/** + * + * @param chatId + * @param stream + * @param detail + * @param messages + * @param customUid + */ +data class ChatCompletionRequest( + val chatId: kotlin.String, + val stream: kotlin.Boolean, + val detail: kotlin.Boolean, + val messages: kotlin.collections.List, + val customUid: kotlin.String? = null +) + diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequestMessagesInner.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequestMessagesInner.kt new file mode 100644 index 00000000..7f078847 --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequestMessagesInner.kt @@ -0,0 +1,24 @@ +/** +* cloudapi_v2 +* buaa scs cloud api v2 +* +* The version of the OpenAPI document: 2.0 +* Contact: loheagn@icloud.com +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ +package cn.edu.buaa.scs.controller.models + + +/** + * + * @param role + * @param content + */ +data class ChatCompletionRequestMessagesInner( + val role: kotlin.String, + val content: kotlin.String +) + diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatHistoryItem.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatHistoryItem.kt new file mode 100644 index 00000000..fc94336d --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatHistoryItem.kt @@ -0,0 +1,30 @@ +/** +* cloudapi_v2 +* buaa scs cloud api v2 +* +* The version of the OpenAPI document: 2.0 +* Contact: loheagn@icloud.com +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ +package cn.edu.buaa.scs.controller.models + + +/** + * + * @param id + * @param chatId + * @param updateTime + * @param title + * @param top + */ +data class ChatHistoryItem( + val id: kotlin.Int? = null, + val chatId: kotlin.String? = null, + val updateTime: kotlin.Long? = null, + val title: kotlin.String? = null, + val top: kotlin.Boolean? = null +) + diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/GetChatRecordsRequest.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/GetChatRecordsRequest.kt new file mode 100644 index 00000000..8ab9aa22 --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/GetChatRecordsRequest.kt @@ -0,0 +1,30 @@ +/** +* cloudapi_v2 +* buaa scs cloud api v2 +* +* The version of the OpenAPI document: 2.0 +* Contact: loheagn@icloud.com +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ +package cn.edu.buaa.scs.controller.models + + +/** + * + * @param chatId + * @param appId + * @param offset + * @param pageSize + * @param loadCustomFeedbacks + */ +data class GetChatRecordsRequest( + val chatId: kotlin.String, + val appId: kotlin.String? = null, + val offset: kotlin.Int? = null, + val pageSize: kotlin.Int? = null, + val loadCustomFeedbacks: kotlin.Boolean? = null +) + diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Monitoring.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Monitoring.kt index 242c4d46..bd61f738 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Monitoring.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Monitoring.kt @@ -41,7 +41,7 @@ fun Application.configureMonitoring() { pathDescription = "", headers = call.request.headers.toMap(), version = version, - realIp = call.request.headers["X-Custom-Remote-Addr"] ?: call.request.origin.host, + realIp = call.request.headers["X-Custom-Remote-Addr"] ?: call.request.origin.localHost, userAgent = userAgent, ) val logRecordResp = LogRecordResp( diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Routing.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Routing.kt index 2afe8c2a..5cd8445d 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Routing.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Routing.kt @@ -11,6 +11,7 @@ fun Application.configureRouting() { route("/api/v2") { authRoute() userRoute() + chatRoute() courseRoute() experimentRoute() fileRoute() diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/model/ChatHistory.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/model/ChatHistory.kt new file mode 100644 index 00000000..d550be9b --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/model/ChatHistory.kt @@ -0,0 +1,34 @@ +package cn.edu.buaa.scs.model + +import org.ktorm.database.Database +import org.ktorm.entity.Entity +import org.ktorm.entity.sequenceOf +import org.ktorm.schema.* + +interface ChatHistory : Entity, IEntity { + var id: Int + var userId: String + var chatId: String + var updateTime: Long + var title: String + var top: Boolean + + companion object : Entity.Factory() + + override fun entityId(): IntOrString { + return IntOrString(this.id) + } +} + +object ChatHistories : Table("chat_history") { + val id = int("id").primaryKey().bindTo { it.id } + val userId = varchar("userId").bindTo { it.userId } + val chatId = varchar("chatId").bindTo { it.chatId } + val updateTime = long("updateTime").bindTo { it.updateTime } + val title = varchar("title").bindTo { it.title } + val top = boolean("top").bindTo { it.top } +} + +@Suppress("unused") +val Database.chatHistory + get() = this.sequenceOf(ChatHistories) diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Chat.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Chat.kt new file mode 100644 index 00000000..d48406e5 --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Chat.kt @@ -0,0 +1,32 @@ +package cn.edu.buaa.scs.route + +import cn.edu.buaa.scs.controller.models.ChatHistoryItem +import cn.edu.buaa.scs.controller.models.GetChatRecordsRequest +import cn.edu.buaa.scs.service.chat +import cn.edu.buaa.scs.utils.userId +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +fun Route.chatRoute() { + route("/chat") { + route("/history") { + get { + call.respond(call.chat.getChatHistoryByUserId(call.userId())) + } + + post { + val chatHistoryItem = call.receive() + call.chat.createChatHistory(call.userId(), chatHistoryItem) + call.respond("OK") + } + } + + post("/records") { + val requestBody = call.receive() + val records = call.chat.getChatRecords(requestBody) + call.respond(records) + } + } +} diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Chat.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Chat.kt new file mode 100644 index 00000000..8b62c604 --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Chat.kt @@ -0,0 +1,78 @@ +package cn.edu.buaa.scs.service + +import cn.edu.buaa.scs.controller.models.ChatHistoryItem +import cn.edu.buaa.scs.controller.models.GetChatRecordsRequest +import cn.edu.buaa.scs.model.* +import cn.edu.buaa.scs.storage.mysql +import cn.edu.buaa.scs.utils.HttpClientWrapper +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.http.* +import io.ktor.serialization.jackson.* +import io.ktor.server.application.* +import org.ktorm.dsl.* +import org.ktorm.entity.* + +val ApplicationCall.chat + get() = ChatService.getSvc(this) { ChatService(this) } + +class ChatService(val call: ApplicationCall) : IService { + companion object : IService.Caller() + + internal val client by lazy { + HttpClientWrapper( + HttpClient(CIO) { + install(ContentNegotiation) { // 确保 JSON 序列化功能可用 + jackson() + } + defaultRequest { + header(HttpHeaders.Authorization, "Bearer fastgpt-sbHq9MGmKbijSJZe3l43BcFWUxNlkcISo1SSBqAHKsqXv8bGQoSIGh28Vw4Dryeu") + } + }, + basePath = "http://10.251.254.178:3000/api" + ) + } + + // 获取指定用户的聊天记录 + fun getChatHistoryByUserId(userId: String): List { + return mysql.chatHistory.filter { it.userId eq userId } + .map { convertToChatHistoryItem(it) }.toList() + } + + // 创建新的聊天记录 + fun createChatHistory(userId: String, chatHistoryItem: ChatHistoryItem): Int { + return mysql.insertAndGenerateKey(ChatHistories) { + set(ChatHistories.userId, userId) + set(ChatHistories.chatId, chatHistoryItem.chatId) + set(ChatHistories.updateTime, chatHistoryItem.updateTime) + set(ChatHistories.title, chatHistoryItem.title) + set(ChatHistories.top, chatHistoryItem.top) + } as Int + } + + // 获取聊天记录 + suspend fun getChatRecords(request: GetChatRecordsRequest): String { + val body = GetChatRecordsRequest( + chatId = request.chatId, + appId = "679e3b3b5fbd929e37095abc", + offset = 0, + pageSize = 30, + loadCustomFeedbacks = false, + ) + val response = client.post("/core/chat/getPaginationRecords", body) + return response.getOrThrow() + } + + private fun convertToChatHistoryItem(chatHistory: ChatHistory): ChatHistoryItem { + return ChatHistoryItem( + id = chatHistory.id, + chatId = chatHistory.chatId, + updateTime = chatHistory.updateTime, + title = chatHistory.title, + top = chatHistory.top + ) + } +} diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/utils/ApplicationCall.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/utils/ApplicationCall.kt index 57403aa7..bc15f381 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/utils/ApplicationCall.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/utils/ApplicationCall.kt @@ -3,10 +3,13 @@ package cn.edu.buaa.scs.utils import cn.edu.buaa.scs.model.User +import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.plugins.* import io.ktor.server.plugins.callid.* import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.utils.io.* fun ApplicationCall.headerString(key: String): String = this.request.header(key) ?: throw BadRequestException("missing header: $key") diff --git a/gradle.properties b/gradle.properties index 6f11cfc3..4827c9ec 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,3 @@ kotlin_version=1.7.10 kotlin.code.style=official +org.gradle.jvmargs=-Xmx4g \ No newline at end of file diff --git a/openapi/cloudapi_v2.yaml b/openapi/cloudapi_v2.yaml index ec7320f7..8ef25a5d 100644 --- a/openapi/cloudapi_v2.yaml +++ b/openapi/cloudapi_v2.yaml @@ -2725,6 +2725,41 @@ paths: description: 教师获取自己的全部助教 security: - Authorization: [] + /chat/history: + get: + summary: 按用户 id 获取历史聊天 + responses: + '200': + description: 聊天历史获取成功 + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ChatHistoryItem' + post: + summary: 新增一条聊天记录 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ChatHistoryItem' + responses: + '200': + description: 成功保存历史记录 + /chat/records: + post: + summary: 获取某个聊天的聊天记录 + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/GetChatRecordsRequest" + responses: + "200": + description: 成功获取到聊天记录 components: schemas: AssignmentResponse: @@ -4913,6 +4948,35 @@ components: type: string required: - studentId + ChatHistoryItem: + type: object + properties: + id: + type: integer + chatId: + type: string + updateTime: + type: integer + format: int64 + title: + type: string + top: + type: boolean + GetChatRecordsRequest: + type: object + required: + - chatId + properties: + appId: + type: string + chatId: + type: string + offset: + type: integer + pageSize: + type: integer + loadCustomFeedbacks: + type: boolean securitySchemes: Authorization: type: apiKey