feat 存档

This commit is contained in:
2026-03-21 20:21:37 +08:00
parent 21965ef95e
commit 86c63292b2
39 changed files with 8881 additions and 51 deletions

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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
View 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
}
}

View 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
}

View 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
View 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
}
}