mirror of
https://github.com/coaidev/coai.git
synced 2025-05-30 10:20:21 +09:00
add conversation storage and donate
This commit is contained in:
parent
d26f97d71f
commit
9c6244bb19
33
api/chat.go
33
api/chat.go
@ -4,7 +4,9 @@ import (
|
||||
"chat/auth"
|
||||
"chat/conversation"
|
||||
"chat/middleware"
|
||||
"chat/types"
|
||||
"chat/utils"
|
||||
"database/sql"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"net/http"
|
||||
@ -14,6 +16,10 @@ type WebsocketAuthForm struct {
|
||||
Token string `json:"token" binding:"required"`
|
||||
}
|
||||
|
||||
func SendSegmentMessage(conn *websocket.Conn, message types.ChatGPTSegmentResponse) {
|
||||
_ = conn.WriteMessage(websocket.TextMessage, []byte(utils.ToJson(message)))
|
||||
}
|
||||
|
||||
func ChatAPI(c *gin.Context) {
|
||||
// websocket connection
|
||||
upgrader := websocket.Upgrader{
|
||||
@ -55,34 +61,25 @@ func ChatAPI(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
instance := conversation.NewConversation(user.Username, user.ID)
|
||||
db := c.MustGet("db").(*sql.DB)
|
||||
instance := conversation.NewConversation(db, user.ID)
|
||||
|
||||
for {
|
||||
_, message, err = conn.ReadMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if _, err := instance.AddMessageFromUserForm(message); err == nil {
|
||||
if instance.HandleMessage(db, message) {
|
||||
keyword, segment := ChatWithWeb(instance.GetMessageSegment(12), true)
|
||||
_ = conn.WriteMessage(websocket.TextMessage, []byte(utils.ToJson(map[string]interface{}{
|
||||
"keyword": keyword,
|
||||
"message": "",
|
||||
"end": false,
|
||||
})))
|
||||
SendSegmentMessage(conn, types.ChatGPTSegmentResponse{Keyword: keyword, End: false})
|
||||
|
||||
StreamRequest("gpt-3.5-turbo-16k", segment, 2000, func(resp string) {
|
||||
data := utils.ToJson(map[string]interface{}{
|
||||
"keyword": keyword,
|
||||
"message": resp,
|
||||
"end": false,
|
||||
StreamRequest("gpt-3.5-turbo-16k-0613", segment, 2000, func(resp string) {
|
||||
SendSegmentMessage(conn, types.ChatGPTSegmentResponse{
|
||||
Message: resp,
|
||||
End: false,
|
||||
})
|
||||
_ = conn.WriteMessage(websocket.TextMessage, []byte(data))
|
||||
})
|
||||
data := utils.ToJson(map[string]interface{}{
|
||||
"message": "",
|
||||
"end": true,
|
||||
})
|
||||
_ = conn.WriteMessage(websocket.TextMessage, []byte(data))
|
||||
SendSegmentMessage(conn, types.ChatGPTSegmentResponse{End: true})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import Star from "./components/icons/star.vue";
|
||||
import { mobile, gpt4 } from "./assets/script/shared";
|
||||
import Post from "./components/icons/post.vue";
|
||||
import Github from "./components/icons/github.vue";
|
||||
import Heart from "./components/icons/heart.vue";
|
||||
|
||||
|
||||
function goto() {
|
||||
@ -40,6 +41,10 @@ function toggle(n: boolean) {
|
||||
<span>GPT-4</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="donate-container" target="_blank" href="https://zmh-program.site/donate">
|
||||
<heart />
|
||||
捐助我们
|
||||
</a>
|
||||
<div class="grow" />
|
||||
<div class="user" v-if="auth">
|
||||
<img class="avatar" :src="'https://api.deeptrain.net/avatar/' + username" alt="">
|
||||
@ -155,6 +160,35 @@ aside {
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.donate-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin: 6px auto;
|
||||
padding: 6px 8px;
|
||||
vertical-align: center;
|
||||
border-radius: 8px;
|
||||
gap: 8px;
|
||||
width: 235px;
|
||||
background: rgba(220, 119, 127, 0.25);
|
||||
transition: .25s;
|
||||
cursor: pointer;
|
||||
color: rgb(255, 110, 122);
|
||||
font-size: 16px;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.donate-container:hover {
|
||||
background: rgba(255, 110, 122, .3);
|
||||
}
|
||||
|
||||
.donate-container svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
stroke: rgb(255, 110, 122);
|
||||
}
|
||||
|
||||
.model {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -280,6 +314,10 @@ aside {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.donate-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: none;
|
||||
}
|
||||
|
3
app/src/components/icons/heart.vue
Normal file
3
app/src/components/icons/heart.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M352.92 80C288 80 256 144 256 144s-32-64-96.92-64c-52.76 0-94.54 44.14-95.08 96.81-1.1 109.33 86.73 187.08 183 252.42a16 16 0 0018 0c96.26-65.34 184.09-143.09 183-252.42-.54-52.67-42.32-96.81-95.08-96.81z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
||||
</template>
|
@ -160,7 +160,7 @@ onMounted(() => {
|
||||
.input input {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
margin: 4px 16px;
|
||||
margin: 4px 16px 8px;
|
||||
color: var(--card-text);
|
||||
background: var(--card-input);
|
||||
border: 1px solid var(--card-input-border);
|
||||
|
@ -8,11 +8,11 @@ import (
|
||||
"log"
|
||||
)
|
||||
|
||||
var Database *sql.DB
|
||||
var _ *sql.DB
|
||||
|
||||
func ConnectMySQL() *sql.DB {
|
||||
// connect to MySQL
|
||||
Database, err := sql.Open("mysql", fmt.Sprintf(
|
||||
db, err := sql.Open("mysql", fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%d)/%s",
|
||||
viper.GetString("mysql.user"),
|
||||
viper.GetString("mysql.password"),
|
||||
@ -26,11 +26,12 @@ func ConnectMySQL() *sql.DB {
|
||||
log.Println("Connected to MySQL server successfully")
|
||||
}
|
||||
|
||||
CreateUserTable(Database)
|
||||
CreateSubscriptionTable(Database)
|
||||
CreatePackageTable(Database)
|
||||
CreatePaymentLogTable(Database)
|
||||
return Database
|
||||
CreateUserTable(db)
|
||||
CreateConversationTable(db)
|
||||
CreateSubscriptionTable(db)
|
||||
CreatePackageTable(db)
|
||||
CreatePaymentLogTable(db)
|
||||
return db
|
||||
}
|
||||
|
||||
func CreateUserTable(db *sql.DB) {
|
||||
@ -91,3 +92,20 @@ func CreatePackageTable(db *sql.DB) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func CreateConversationTable(db *sql.DB) {
|
||||
_, err := db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS conversation (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INT,
|
||||
conversation_id INT,
|
||||
conversation_name VARCHAR(255),
|
||||
data TEXT,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES auth(id)
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@ -3,35 +3,51 @@ package conversation
|
||||
import (
|
||||
"chat/types"
|
||||
"chat/utils"
|
||||
"database/sql"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Conversation struct {
|
||||
Username string `json:"username"`
|
||||
Id int64 `json:"id"`
|
||||
Message []types.ChatGPTMessage `json:"message"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Message []types.ChatGPTMessage `json:"message"`
|
||||
}
|
||||
|
||||
type FormMessage struct {
|
||||
Message string `json:"message" binding:"required"`
|
||||
}
|
||||
|
||||
func NewConversation(username string, id int64) *Conversation {
|
||||
func NewConversation(db *sql.DB, id int64) *Conversation {
|
||||
return &Conversation{
|
||||
Username: username,
|
||||
Id: id,
|
||||
Message: []types.ChatGPTMessage{},
|
||||
UserID: id,
|
||||
Id: GetConversationLengthByUserID(db, id),
|
||||
Name: "new chat",
|
||||
Message: []types.ChatGPTMessage{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conversation) GetUsername() string {
|
||||
return c.Username
|
||||
func (c *Conversation) GetName() string {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
func (c *Conversation) SetName(db *sql.DB, name string) {
|
||||
c.Name = name
|
||||
c.SaveConversation(db)
|
||||
}
|
||||
|
||||
func (c *Conversation) GetId() int64 {
|
||||
return c.Id
|
||||
}
|
||||
|
||||
func (c *Conversation) GetUserID() int64 {
|
||||
return c.UserID
|
||||
}
|
||||
|
||||
func (c *Conversation) SetId(id int64) {
|
||||
c.Id = id
|
||||
}
|
||||
|
||||
func (c *Conversation) GetMessage() []types.ChatGPTMessage {
|
||||
return c.Message
|
||||
}
|
||||
@ -56,21 +72,21 @@ func (c *Conversation) AddMessage(message types.ChatGPTMessage) {
|
||||
}
|
||||
|
||||
func (c *Conversation) AddMessageFromUser(message string) {
|
||||
c.Message = append(c.Message, types.ChatGPTMessage{
|
||||
c.AddMessage(types.ChatGPTMessage{
|
||||
Role: "user",
|
||||
Content: message,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Conversation) AddMessageFromAssistant(message string) {
|
||||
c.Message = append(c.Message, types.ChatGPTMessage{
|
||||
c.AddMessage(types.ChatGPTMessage{
|
||||
Role: "assistant",
|
||||
Content: message,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Conversation) AddMessageFromSystem(message string) {
|
||||
c.Message = append(c.Message, types.ChatGPTMessage{
|
||||
c.AddMessage(types.ChatGPTMessage{
|
||||
Role: "system",
|
||||
Content: message,
|
||||
})
|
||||
@ -95,9 +111,16 @@ func (c *Conversation) AddMessageFromUserForm(data []byte) (string, error) {
|
||||
return "", errors.New("message is empty")
|
||||
}
|
||||
|
||||
c.Message = append(c.Message, types.ChatGPTMessage{
|
||||
Role: "user",
|
||||
Content: form.Message,
|
||||
})
|
||||
c.AddMessageFromUser(form.Message)
|
||||
return form.Message, nil
|
||||
}
|
||||
|
||||
func (c *Conversation) HandleMessage(db *sql.DB, data []byte) bool {
|
||||
_, err := c.AddMessageFromUserForm(data)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
c.SaveConversation(db)
|
||||
return true
|
||||
}
|
||||
|
75
conversation/storage.go
Normal file
75
conversation/storage.go
Normal file
@ -0,0 +1,75 @@
|
||||
package conversation
|
||||
|
||||
import (
|
||||
"chat/types"
|
||||
"chat/utils"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
func (c *Conversation) SaveConversation(db *sql.DB) bool {
|
||||
data := utils.ToJson(c.GetMessage())
|
||||
_, err := db.Exec(`
|
||||
INSERT INTO conversation (
|
||||
user_id,
|
||||
conversation_id,
|
||||
conversation_name,
|
||||
data
|
||||
) VALUES (?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
conversation_name = VALUES(conversation_name),
|
||||
data = VALUES(data)
|
||||
`, c.GetUserID(), c.GetId(), c.GetName(), data)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func GetConversationLengthByUserID(db *sql.DB, userId int64) int64 {
|
||||
var length int64
|
||||
err := db.QueryRow("SELECT COUNT(*) FROM conversation WHERE user_id = ?", userId).Scan(&length)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
||||
func LoadConversation(db *sql.DB, userId int64, conversationId int64) *Conversation {
|
||||
conversation := Conversation{
|
||||
UserID: userId,
|
||||
Id: conversationId,
|
||||
}
|
||||
|
||||
var data string
|
||||
err := db.QueryRow("SELECT conversation_name, data FROM conversation WHERE user_id = ? AND conversation_id = ?", userId, conversationId).Scan(&conversation.Name, &data)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
conversation.Message, err = utils.Unmarshal[[]types.ChatGPTMessage]([]byte(data))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &conversation
|
||||
}
|
||||
|
||||
func LoadConversationList(db *sql.DB, userId int64) []Conversation {
|
||||
var conversationList []Conversation
|
||||
rows, err := db.Query("SELECT conversation_id, conversation_name FROM conversation WHERE user_id = ?", userId)
|
||||
if err != nil {
|
||||
return conversationList
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var conversation Conversation
|
||||
err := rows.Scan(&conversation.Id, &conversation.Name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
conversationList = append(conversationList, conversation)
|
||||
}
|
||||
|
||||
return conversationList
|
||||
}
|
@ -26,3 +26,9 @@ type ChatGPTStreamResponse struct {
|
||||
} `json:"choices"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type ChatGPTSegmentResponse struct {
|
||||
Keyword string `json:"keyword"`
|
||||
Message string `json:"message"`
|
||||
End bool `json:"end"`
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user