feat 存档
This commit is contained in:
@@ -18,18 +18,21 @@ func NewAIClient(enableSearch bool, searchStrategy string, temperature float32)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) RequestAI(pre string, prompt []string, cb func(content string, i int) error) error {
|
||||
func (c *Client) RequestAI(pre []string, prompt []string, cb func(content string, i int) error) ([]Message, error) {
|
||||
inToken, outToken := int32(0), int32(0)
|
||||
messages := []Message{{
|
||||
Role: "user",
|
||||
Content: pre,
|
||||
}}
|
||||
messages := make([]Message, 0)
|
||||
for _, s := range pre {
|
||||
messages = append(messages, Message{
|
||||
Role: "user",
|
||||
Content: s,
|
||||
})
|
||||
}
|
||||
for i, step := range prompt {
|
||||
messages = append(messages, Message{
|
||||
Role: "user",
|
||||
Content: step,
|
||||
})
|
||||
r, err := request("qwen3-max", Parameters{
|
||||
r, err := request("qwen-plus", Parameters{
|
||||
//r, err := request("deepseek-v3.2", Parameters{
|
||||
EnableSearch: c.enableSearch,
|
||||
SearchOptions: SearchOptions{
|
||||
@@ -39,21 +42,21 @@ func (c *Client) RequestAI(pre string, prompt []string, cb func(content string,
|
||||
Temperature: c.temperature,
|
||||
}, messages)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
inToken += r.Usage.InputTokens
|
||||
outToken += r.Usage.OutputTokens
|
||||
if len(r.Output.Choices) == 0 {
|
||||
fmt.Printf("AI response err, r: %#+v\n", r)
|
||||
return err
|
||||
fmt.Printf("AI response err: %+v\n", r)
|
||||
return nil, err
|
||||
} else {
|
||||
messages = append(messages, r.Output.Choices[0].Message)
|
||||
}
|
||||
fmt.Printf("AI 回答 %v: %v\n", i, r.Output.Choices[0].Message.Content)
|
||||
if err = cb(r.Output.Choices[0].Message.Content, i); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
fmt.Printf("AI finished, use token: in-%v out-%v\n", inToken, outToken)
|
||||
return nil
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
@@ -30,14 +30,19 @@ type SearchOptions struct {
|
||||
type RequestBody struct {
|
||||
Model string `json:"model"`
|
||||
Input Input `json:"input"`
|
||||
Parameters Parameters `json:"parameters"`
|
||||
Parameters Parameters `json:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
type ResponseBody struct {
|
||||
Output struct {
|
||||
Choices []struct {
|
||||
Message Message `json:"message"`
|
||||
StatusCode string `json:"status_code"`
|
||||
RequestID string `json:"request_id"`
|
||||
Code string `json:"code"`
|
||||
Output struct {
|
||||
Text string `json:"text"`
|
||||
FinishReason string `json:"finish_reason"`
|
||||
Choices []struct {
|
||||
FinishReason string `json:"finish_reason"`
|
||||
Message Message `json:"message"`
|
||||
} `json:"choices"`
|
||||
} `json:"output"`
|
||||
Usage struct {
|
||||
|
||||
111
backend/app.go
111
backend/app.go
@@ -4,9 +4,14 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fiction/backend/ai"
|
||||
"fiction/backend/doubao"
|
||||
"fiction/db/model"
|
||||
"fiction/db/sqlite"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// App struct
|
||||
@@ -21,6 +26,14 @@ func NewApp() *App {
|
||||
|
||||
func (a *App) Startup(ctx context.Context) {
|
||||
a.ctx = ctx
|
||||
if err := sqlite.Init("./db/db.db"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) Shutdown(ctx context.Context) {
|
||||
a.ctx = nil
|
||||
sqlite.Stop()
|
||||
}
|
||||
|
||||
// GetNovelList returns the list of txt files in the source directory
|
||||
@@ -57,33 +70,113 @@ func (a *App) GetNovelContent(filename string) string {
|
||||
return string(content)
|
||||
}
|
||||
|
||||
func (a *App) WriteNovelContent(filename string) string {
|
||||
filePath := filepath.Join("./config", "config.json")
|
||||
// GetFangWenContent 获取仿文的内容
|
||||
func (a *App) GetFangWenContent(filename string) string {
|
||||
first := &model.List{}
|
||||
sqlite.DB.Model(&model.List{}).
|
||||
Where("name = ?", filename).
|
||||
First(&first)
|
||||
if first.ID > 0 {
|
||||
if len(first.Imitation) > 0 {
|
||||
return first.Imitation
|
||||
}
|
||||
if first.Status == 1 {
|
||||
return "正在仿写中..."
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(filePath)
|
||||
// GetAIContent 获取ai对话记录
|
||||
func (a *App) GetAIContent(filename string) []ai.Message {
|
||||
first := &model.List{}
|
||||
sqlite.DB.Model(&model.List{}).
|
||||
Where("name = ?", filename).
|
||||
First(&first)
|
||||
if first.ID > 0 {
|
||||
if len(first.AI) > 0 {
|
||||
resp := make([]ai.Message, 0)
|
||||
_ = json.Unmarshal([]byte(first.AI), &resp)
|
||||
return resp
|
||||
}
|
||||
}
|
||||
return make([]ai.Message, 0)
|
||||
}
|
||||
|
||||
// GetToken 获取ai token消耗
|
||||
func (a *App) GetToken(filename string) string {
|
||||
first := &model.List{}
|
||||
sqlite.DB.Model(&model.List{}).
|
||||
Where("name = ?", filename).
|
||||
First(&first)
|
||||
return first.Token
|
||||
}
|
||||
|
||||
func (a *App) WriteNovelContent(filename string) string {
|
||||
content, err := os.ReadFile(filepath.Join("./config", "config.json"))
|
||||
if err != nil {
|
||||
fmt.Printf("读取提示词文件错误: %v\n", err)
|
||||
return "Error reading file"
|
||||
}
|
||||
|
||||
var str []string
|
||||
if err = json.Unmarshal(content, &str); err != nil {
|
||||
fmt.Printf("读取提示词错误: %v\n", err)
|
||||
return "Error Unmarshal JSON"
|
||||
}
|
||||
|
||||
resp := ""
|
||||
err = ai.NewAIClient(false, "", 0.9).
|
||||
first := &model.List{}
|
||||
sqlite.DB.Model(&model.List{}).
|
||||
Where("name = ?", filename).
|
||||
First(&first)
|
||||
if first.ID == 0 {
|
||||
first.Name = filename
|
||||
first.Status = 1
|
||||
sqlite.DB.Create(&first)
|
||||
}
|
||||
|
||||
resp := make([]string, 0)
|
||||
msgList, in, out, err := doubao.NewAIClient(false, "", 0).
|
||||
RequestAI(
|
||||
a.GetNovelContent(filename),
|
||||
a.GetModelConfig().Model,
|
||||
a.GetModelConfig().Key,
|
||||
[]string{
|
||||
"本次对话是通过API的方式对话,所以请你不要在回答中掺杂各种图片、修正格式的符号等等,仅以纯文本+换行符的方式回答",
|
||||
a.GetNovelContent(filename),
|
||||
},
|
||||
str,
|
||||
func(content string, i int) error {
|
||||
resp = content
|
||||
processedContent := strings.ReplaceAll(content, "\n\n", "\n")
|
||||
resp = append(resp, processedContent)
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return resp
|
||||
marshal, _ := json.Marshal(msgList)
|
||||
|
||||
first.Name = filename
|
||||
first.Imitation = resp[len(resp)-1]
|
||||
first.AI = string(marshal)
|
||||
first.Status = 2
|
||||
first.Token = fmt.Sprintf("模型:%v,输入 %d Token,输出 %d Token,预计费用:%.4f元", a.GetModelConfig().Model, in, out, a.GetModelConfig().In/1000000*float64(in)+a.GetModelConfig().Out/1000000*float64(out))
|
||||
sqlite.DB.Save(first)
|
||||
|
||||
saveToFile(filename, resp[len(resp)-1])
|
||||
|
||||
return resp[len(resp)-1]
|
||||
}
|
||||
|
||||
func saveToFile(name string, content string) {
|
||||
// 获取当前时间戳(精确到秒)
|
||||
timestamp := time.Now().Format("01-02 15-04-05") // 格式化为 YYYYMMDDHHMMSS
|
||||
|
||||
// 构建输出文件名:原文件名 + 时间戳 + .txt
|
||||
baseName := strings.TrimSuffix(name, filepath.Ext(name))
|
||||
outputFileName := fmt.Sprintf("%s_%s.txt", baseName, timestamp)
|
||||
|
||||
outputPath := filepath.Join("./generate", outputFileName)
|
||||
if err := os.WriteFile(outputPath, []byte(content), 0644); err != nil {
|
||||
fmt.Printf("写入文件失败: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
38
backend/call_word.go
Normal file
38
backend/call_word.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func (a *App) GetCallWord() []string {
|
||||
content, err := os.ReadFile(filepath.Join("./config", "config.json"))
|
||||
if err != nil {
|
||||
fmt.Printf("读取提示词文件错误: %v\n", err)
|
||||
return make([]string, 0)
|
||||
}
|
||||
var str []string
|
||||
if err = json.Unmarshal(content, &str); err != nil {
|
||||
fmt.Printf("读取提示词错误: %v\n", err)
|
||||
return make([]string, 0)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func (a *App) SetCallWord(callWord []string) {
|
||||
data, err := json.Marshal(callWord)
|
||||
if err != nil {
|
||||
fmt.Printf("序列化提示词失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 写入配置文件
|
||||
configPath := filepath.Join("./config", "config.json")
|
||||
if err := os.WriteFile(configPath, data, 0644); err != nil {
|
||||
fmt.Printf("写入配置文件失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
54
backend/doubao/ai_client.go
Normal file
54
backend/doubao/ai_client.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package doubao
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
enableSearch bool // 是否开启搜索
|
||||
searchStrategy string // 搜索策略(开了搜索才生效)
|
||||
temperature float32 // 采样温度
|
||||
}
|
||||
|
||||
func NewAIClient(enableSearch bool, searchStrategy string, temperature float32) *Client {
|
||||
return &Client{
|
||||
enableSearch: enableSearch,
|
||||
searchStrategy: searchStrategy,
|
||||
temperature: temperature,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) RequestAI(model, key string, pre []string, prompt []string, cb func(content string, i int) error) ([]Message, int32, int32, error) {
|
||||
inToken, outToken := int32(0), int32(0)
|
||||
messages := make([]Message, 0)
|
||||
for _, s := range pre {
|
||||
messages = append(messages, Message{
|
||||
Role: "user",
|
||||
Content: s,
|
||||
})
|
||||
}
|
||||
for i, step := range prompt {
|
||||
messages = append(messages, Message{
|
||||
Role: "user",
|
||||
Content: step,
|
||||
})
|
||||
r, err := request(model, key, messages)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
inToken += r.Usage.InputTokens
|
||||
outToken += r.Usage.OutputTokens
|
||||
if len(r.Choices) == 0 {
|
||||
fmt.Printf("AI response err: %+v\n", r)
|
||||
return nil, 0, 0, err
|
||||
} else {
|
||||
messages = append(messages, r.Choices[0].Message)
|
||||
}
|
||||
fmt.Printf("AI 回答 %v: %v\n", i, r.Choices[0].Message.Content)
|
||||
if err = cb(r.Choices[0].Message.Content, i); err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
}
|
||||
fmt.Printf("AI finished, use token: in-%v out-%v\n", inToken, outToken)
|
||||
return messages, inToken, outToken, nil
|
||||
}
|
||||
67
backend/doubao/ai_request.go
Normal file
67
backend/doubao/ai_request.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package doubao
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type RequestBody struct {
|
||||
Model string `json:"model"`
|
||||
Messages []Message `json:"messages"`
|
||||
MaxCompletionTokens int32 `json:"max_completion_tokens"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type SearchOptions struct {
|
||||
SearchStrategy string `json:"search_strategy"`
|
||||
}
|
||||
|
||||
type ResponseBody struct {
|
||||
Choices []struct {
|
||||
FinishReason string `json:"finish_reason"`
|
||||
Message Message `json:"message"`
|
||||
} `json:"choices"`
|
||||
Usage struct {
|
||||
InputTokens int32 `json:"prompt_tokens"`
|
||||
OutputTokens int32 `json:"completion_tokens"`
|
||||
TotalTokens int32 `json:"total_tokens"`
|
||||
} `json:"usage"`
|
||||
}
|
||||
|
||||
func request(model, key string, messages []Message) (*ResponseBody, error) {
|
||||
requestBody := RequestBody{
|
||||
Model: model,
|
||||
Messages: messages,
|
||||
MaxCompletionTokens: 65535,
|
||||
}
|
||||
jsonData, _ := json.Marshal(requestBody)
|
||||
|
||||
req, err := http.NewRequest("POST", "https://ark.cn-beijing.volces.com/api/v3/chat/completions", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+key)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := (&http.Client{}).Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res ResponseBody
|
||||
if err = json.Unmarshal(body, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
43
backend/model.go
Normal file
43
backend/model.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type ModelConfig struct {
|
||||
Key string `json:"key"`
|
||||
Model string `json:"model"`
|
||||
In float64 `json:"in"`
|
||||
Out float64 `json:"out"`
|
||||
}
|
||||
|
||||
func (a *App) GetModelConfig() *ModelConfig {
|
||||
content, err := os.ReadFile(filepath.Join("./config", "model.json"))
|
||||
if err != nil {
|
||||
return &ModelConfig{}
|
||||
}
|
||||
resp := &ModelConfig{}
|
||||
if err = json.Unmarshal(content, &resp); err != nil {
|
||||
return &ModelConfig{}
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func (a *App) SetModelConfig(req *ModelConfig) {
|
||||
data, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
fmt.Printf("保存模型配置失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 写入配置文件
|
||||
configPath := filepath.Join("./config", "model.json")
|
||||
if err := os.WriteFile(configPath, data, 0644); err != nil {
|
||||
fmt.Printf("保存模型配置失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user