Compare commits
2 Commits
21965ef95e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| fd0aebcbaa | |||
| 86c63292b2 |
@@ -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)
|
inToken, outToken := int32(0), int32(0)
|
||||||
messages := []Message{{
|
messages := make([]Message, 0)
|
||||||
Role: "user",
|
for _, s := range pre {
|
||||||
Content: pre,
|
messages = append(messages, Message{
|
||||||
}}
|
Role: "user",
|
||||||
|
Content: s,
|
||||||
|
})
|
||||||
|
}
|
||||||
for i, step := range prompt {
|
for i, step := range prompt {
|
||||||
messages = append(messages, Message{
|
messages = append(messages, Message{
|
||||||
Role: "user",
|
Role: "user",
|
||||||
Content: step,
|
Content: step,
|
||||||
})
|
})
|
||||||
r, err := request("qwen3-max", Parameters{
|
r, err := request("qwen-plus", Parameters{
|
||||||
//r, err := request("deepseek-v3.2", Parameters{
|
//r, err := request("deepseek-v3.2", Parameters{
|
||||||
EnableSearch: c.enableSearch,
|
EnableSearch: c.enableSearch,
|
||||||
SearchOptions: SearchOptions{
|
SearchOptions: SearchOptions{
|
||||||
@@ -39,21 +42,21 @@ func (c *Client) RequestAI(pre string, prompt []string, cb func(content string,
|
|||||||
Temperature: c.temperature,
|
Temperature: c.temperature,
|
||||||
}, messages)
|
}, messages)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
inToken += r.Usage.InputTokens
|
inToken += r.Usage.InputTokens
|
||||||
outToken += r.Usage.OutputTokens
|
outToken += r.Usage.OutputTokens
|
||||||
if len(r.Output.Choices) == 0 {
|
if len(r.Output.Choices) == 0 {
|
||||||
fmt.Printf("AI response err, r: %#+v\n", r)
|
fmt.Printf("AI response err: %+v\n", r)
|
||||||
return err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
messages = append(messages, r.Output.Choices[0].Message)
|
messages = append(messages, r.Output.Choices[0].Message)
|
||||||
}
|
}
|
||||||
fmt.Printf("AI 回答 %v: %v\n", i, r.Output.Choices[0].Message.Content)
|
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 {
|
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)
|
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 {
|
type RequestBody struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
Input Input `json:"input"`
|
Input Input `json:"input"`
|
||||||
Parameters Parameters `json:"parameters"`
|
Parameters Parameters `json:"parameters,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResponseBody struct {
|
type ResponseBody struct {
|
||||||
Output struct {
|
StatusCode string `json:"status_code"`
|
||||||
Choices []struct {
|
RequestID string `json:"request_id"`
|
||||||
Message Message `json:"message"`
|
Code string `json:"code"`
|
||||||
|
Output struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
FinishReason string `json:"finish_reason"`
|
||||||
|
Choices []struct {
|
||||||
FinishReason string `json:"finish_reason"`
|
FinishReason string `json:"finish_reason"`
|
||||||
|
Message Message `json:"message"`
|
||||||
} `json:"choices"`
|
} `json:"choices"`
|
||||||
} `json:"output"`
|
} `json:"output"`
|
||||||
Usage struct {
|
Usage struct {
|
||||||
|
|||||||
173
backend/app.go
173
backend/app.go
@@ -4,9 +4,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fiction/backend/ai"
|
"fiction/backend/ai"
|
||||||
|
"fiction/backend/doubao"
|
||||||
|
"fiction/db/model"
|
||||||
|
"fiction/db/sqlite"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// App struct
|
// App struct
|
||||||
@@ -21,6 +28,14 @@ func NewApp() *App {
|
|||||||
|
|
||||||
func (a *App) Startup(ctx context.Context) {
|
func (a *App) Startup(ctx context.Context) {
|
||||||
a.ctx = ctx
|
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
|
// GetNovelList returns the list of txt files in the source directory
|
||||||
@@ -57,33 +72,173 @@ func (a *App) GetNovelContent(filename string) string {
|
|||||||
return string(content)
|
return string(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) WriteNovelContent(filename string) string {
|
// GetFangWenContent 获取仿文的内容
|
||||||
filePath := filepath.Join("./config", "config.json")
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("读取提示词文件错误: %v\n", err)
|
fmt.Printf("读取提示词文件错误: %v\n", err)
|
||||||
return "Error reading file"
|
return "Error reading file"
|
||||||
}
|
}
|
||||||
|
|
||||||
var str []string
|
var str []string
|
||||||
if err = json.Unmarshal(content, &str); err != nil {
|
if err = json.Unmarshal(content, &str); err != nil {
|
||||||
fmt.Printf("读取提示词错误: %v\n", err)
|
fmt.Printf("读取提示词错误: %v\n", err)
|
||||||
return "Error Unmarshal JSON"
|
return "Error Unmarshal JSON"
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := ""
|
first := &model.List{}
|
||||||
err = ai.NewAIClient(false, "", 0.9).
|
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(
|
RequestAI(
|
||||||
a.GetNovelContent(filename),
|
a.GetModelConfig().Model,
|
||||||
|
a.GetModelConfig().Key,
|
||||||
|
[]string{
|
||||||
|
"本次对话是通过API的方式对话,所以请你不要在回答中掺杂各种图片、修正格式的符号等等,仅以纯文本+换行符的方式回答",
|
||||||
|
a.GetNovelContent(filename),
|
||||||
|
},
|
||||||
str,
|
str,
|
||||||
func(content string, i int) error {
|
func(content string, i int) error {
|
||||||
resp = content
|
processedContent := strings.ReplaceAll(content, "\n\n", "\n")
|
||||||
|
resp = append(resp, processedContent)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err.Error()
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenSourceDir 打开原文目录
|
||||||
|
func (a *App) OpenSourceDir() error {
|
||||||
|
// 获取当前工作目录
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建绝对路径
|
||||||
|
sourceDir := filepath.Join(cwd, "source")
|
||||||
|
// 确保目录存在
|
||||||
|
if _, err := os.Stat(sourceDir); os.IsNotExist(err) {
|
||||||
|
os.MkdirAll(sourceDir, 0755)
|
||||||
|
}
|
||||||
|
// 打开目录
|
||||||
|
return openDir(sourceDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenGenerateDir 打开仿文目录
|
||||||
|
func (a *App) OpenGenerateDir() error {
|
||||||
|
// 获取当前工作目录
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建绝对路径
|
||||||
|
generateDir := filepath.Join(cwd, "generate")
|
||||||
|
// 确保目录存在
|
||||||
|
if _, err := os.Stat(generateDir); os.IsNotExist(err) {
|
||||||
|
os.MkdirAll(generateDir, 0755)
|
||||||
|
}
|
||||||
|
// 打开目录
|
||||||
|
return openDir(generateDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// openDir 打开指定目录
|
||||||
|
func openDir(dir string) error {
|
||||||
|
var cmd string
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
// 根据操作系统选择打开命令
|
||||||
|
switch os := runtime.GOOS; os {
|
||||||
|
case "windows":
|
||||||
|
cmd = "explorer"
|
||||||
|
args = []string{dir}
|
||||||
|
case "darwin":
|
||||||
|
cmd = "open"
|
||||||
|
args = []string{dir}
|
||||||
|
case "linux":
|
||||||
|
cmd = "xdg-open"
|
||||||
|
args = []string{dir}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported operating system: %s", os)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行命令
|
||||||
|
return exec.Command(cmd, args...).Start()
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1 @@
|
|||||||
[
|
["你是一个短篇小说家,这篇是你之前写出的小说,请仔细阅读,并识别小说的信息。具体识别内容: 1.识别并记住原文的核心论点、支撑论据、风格特点及其独特的表达方式。 2.识别并记住原文的结构布局。","我需要对本篇小说进行仿写,请思考并提供几个同类型的仿写主题","请直接根据你最推荐的主题,完整仿写一篇小说,要求如下: 1.根据新小说的主题,创造性地重新表达原文的核心观点和信息,避免直接抄袭。 2.仿写的小说符合抖音短篇小说风格:开篇炸点、简单粗暴、节奏快、情绪强等。 3.全文要有逻辑性、口语化、大白话、短句多(尽量10个字以上,内容丰满)、通俗易懂。 4.模仿原小说60%的写作风格和语气、语法、拼写,同时确保内容的原创性和流畅性。 5.仿写后的小说字数必须在1.2万字左右(非常重要)。 6.只输出小说内容,不要输出其他任何多余的话,内容不要写行号"]
|
||||||
"小说识别,识别出的仿写小说的信息,具体识别内容:\n(1)识别并记住原文的核心论点、支撑论据、风格特点及其独特的表达方式。(2)识别并记住原文的结构布局。",
|
|
||||||
"第2步 - 提供几个同类型的仿写主题,引导用户选择仿写的主题。本步骤完成后,询问用户提供信息是否 ” 满意,如果用户反馈 ” “ 满 意 ” 直接可以进入下一步,如果用户反馈 “ 不满意,则根据用户要求修改。",
|
|
||||||
"第3步,\n满意,仿写最推荐的主题。\n文章重塑 - 任务:根据新小说的主题,创造性地重新表达原文的核心观点和信息,避免直接抄袭。本步骤完成后,询问用户提供信息是否 ” 满意,如果用户反馈 ” ” 满意 ” 直接可以进入下一步,如果 用户反馈 “ 不满意,则根据用户要求修改。 ” \n要求:\n(1)仿写的小说符合抖音短篇小说风格:开篇炸点、简单粗暴、节奏快、情绪强等。\n(2)全文要有逻辑性、口语化、大白话、短句多、通俗易懂。\n(3)一句为一段。\n(4)模仿原小说60%的写作风格和语气、语法、拼写,同时确保内容的原创性和流畅性。 \n(5)仿写后的小说字数必须在1.2万字左右。(6)只输出小说内容,不要输出其他任何多余的话,内容不要写行号"
|
|
||||||
]
|
|
||||||
1
config/model.json
Normal file
1
config/model.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"key":"7edc03ad-d2c9-4dfe-85ad-2860a77123ae","model":"doubao-seed-2-0-pro-260215","in":3.2,"out":16}
|
||||||
14
db/model/user.go
Normal file
14
db/model/user.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type List struct {
|
||||||
|
ID int32 `json:"id" gorm:"column:id;primaryKey"` // 主键
|
||||||
|
Name string `json:"name" gorm:"column:name;uniqueIndex"` // 小说名称
|
||||||
|
Status int32 `json:"status" gorm:"column:status"` // 状态 0 未知 1 仿写中 2 仿写完成
|
||||||
|
Imitation string `json:"imitation" gorm:"column:imitation"` // 仿文
|
||||||
|
AI string `json:"ai" gorm:"column:ai"` // AI对话记录
|
||||||
|
Token string `json:"token" gorm:"column:token"` // Token消耗
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*List) TableName() string {
|
||||||
|
return "list"
|
||||||
|
}
|
||||||
51
db/sqlite/sqlite.go
Normal file
51
db/sqlite/sqlite.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fiction/db/model"
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DB *gorm.DB
|
||||||
|
|
||||||
|
func Init(dsn string) error {
|
||||||
|
fmt.Println("init SQLite")
|
||||||
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{
|
||||||
|
TranslateError: true,
|
||||||
|
Logger: logger.New(
|
||||||
|
log.New(log.Writer(), "\r\n", log.LstdFlags),
|
||||||
|
logger.Config{
|
||||||
|
SlowThreshold: 10 * time.Millisecond,
|
||||||
|
Colorful: true,
|
||||||
|
LogLevel: logger.Info,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
DB = db
|
||||||
|
if err = initDB(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDB() error {
|
||||||
|
tableArr := []interface{}{
|
||||||
|
&model.List{},
|
||||||
|
}
|
||||||
|
for _, table := range tableArr {
|
||||||
|
if err := DB.AutoMigrate(table); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Stop() {
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import Sidebar from './components/Sidebar.vue'
|
import Sidebar from './components/Sidebar.vue'
|
||||||
import { GetNovelContent, WriteNovelContent } from '../wailsjs/go/backend/App'
|
import { GetNovelContent, WriteNovelContent, GetFangWenContent, GetAIContent, GetToken } from '../wailsjs/go/backend/App'
|
||||||
|
|
||||||
const activeButton = ref('')
|
const activeButton = ref('')
|
||||||
const content = ref('请选择要查看的小说')
|
const content = ref('请选择要查看的小说')
|
||||||
@@ -9,14 +9,43 @@ const currentTitle = ref('小说内容')
|
|||||||
const viewMode = ref('original')
|
const viewMode = ref('original')
|
||||||
const currentFilename = ref('')
|
const currentFilename = ref('')
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
const aiMessages = ref([])
|
||||||
|
const tokenInfo = ref('')
|
||||||
|
|
||||||
// 监听 viewMode 变化
|
// 监听 viewMode 变化
|
||||||
watch(viewMode, (newMode) => {
|
watch(viewMode, async (newMode) => {
|
||||||
if (newMode === 'imitation') {
|
if (newMode === 'imitation' && currentFilename.value) {
|
||||||
content.value = currentFilename.value + ' 的仿写内容'
|
isLoading.value = true
|
||||||
|
try {
|
||||||
|
const fangWenContent = await GetFangWenContent(currentFilename.value)
|
||||||
|
if (fangWenContent) {
|
||||||
|
content.value = fangWenContent
|
||||||
|
} else {
|
||||||
|
content.value = currentFilename.value + ' 的仿写内容'
|
||||||
|
}
|
||||||
|
// 获取 token 信息
|
||||||
|
const token = await GetToken(currentFilename.value)
|
||||||
|
tokenInfo.value = token
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting fangwen content:', error)
|
||||||
|
content.value = currentFilename.value + ' 的仿写内容'
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
} else if (newMode === 'original' && currentFilename.value) {
|
} else if (newMode === 'original' && currentFilename.value) {
|
||||||
// 切换回原文模式时,重新加载原文
|
// 切换回原文模式时,重新加载原文
|
||||||
handleMenuClick({ id: activeButton.value, filename: currentFilename.value })
|
handleMenuClick({ id: activeButton.value, filename: currentFilename.value })
|
||||||
|
} else if (newMode === 'ai' && currentFilename.value) {
|
||||||
|
isLoading.value = true
|
||||||
|
try {
|
||||||
|
const aiContent = await GetAIContent(currentFilename.value)
|
||||||
|
aiMessages.value = aiContent
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting AI content:', error)
|
||||||
|
aiMessages.value = []
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -53,6 +82,9 @@ async function handleImitation() {
|
|||||||
try {
|
try {
|
||||||
const imitationContent = await WriteNovelContent(currentFilename.value)
|
const imitationContent = await WriteNovelContent(currentFilename.value)
|
||||||
content.value = imitationContent
|
content.value = imitationContent
|
||||||
|
// 更新 token 信息
|
||||||
|
const token = await GetToken(currentFilename.value)
|
||||||
|
tokenInfo.value = token
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error generating imitation content:', error)
|
console.error('Error generating imitation content:', error)
|
||||||
content.value = '生成仿写内容失败'
|
content.value = '生成仿写内容失败'
|
||||||
@@ -63,6 +95,13 @@ async function handleImitation() {
|
|||||||
content.value = '请先选择要仿写的小说'
|
content.value = '请先选择要仿写的小说'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算去掉换行符后的字数
|
||||||
|
function countCharacters(text) {
|
||||||
|
if (!text) return 0
|
||||||
|
// 去掉所有换行符,然后统计长度
|
||||||
|
return text.replace(/\n/g, '').length
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -87,11 +126,22 @@ async function handleImitation() {
|
|||||||
>
|
>
|
||||||
仿文
|
仿文
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
:type="viewMode === 'ai' ? 'primary' : 'default'"
|
||||||
|
@click="viewMode = 'ai'"
|
||||||
|
>
|
||||||
|
AI对话记录
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content-body">
|
<div class="content-body">
|
||||||
<div v-if="viewMode === 'original'">
|
<div v-if="viewMode === 'original'" class="content-wrapper">
|
||||||
{{ content }}
|
<div class="content-scrollable">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
<div class="content-footer">
|
||||||
|
字数: {{ countCharacters(content) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="viewMode === 'imitation'" class="imitation-container">
|
<div v-else-if="viewMode === 'imitation'" class="imitation-container">
|
||||||
<div v-if="!isLoading && (content === '请选择要查看的小说' || content === '正在生成仿写内容...' || content === currentFilename + ' 的仿写内容')" class="imitation-button-container">
|
<div v-if="!isLoading && (content === '请选择要查看的小说' || content === '正在生成仿写内容...' || content === currentFilename + ' 的仿写内容')" class="imitation-button-container">
|
||||||
@@ -106,12 +156,42 @@ async function handleImitation() {
|
|||||||
仿写
|
仿写
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="imitation-content">
|
<div v-else class="content-wrapper">
|
||||||
{{ content }}
|
<div class="content-scrollable">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
<div class="content-footer">
|
||||||
|
<span class="token-info">{{ tokenInfo }}</span>
|
||||||
|
<span class="word-count">字数: {{ countCharacters(content) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="viewMode === 'ai'" class="content-wrapper">
|
||||||
|
<div class="content-scrollable ai-chat-container">
|
||||||
|
<div
|
||||||
|
v-for="(message, index) in aiMessages"
|
||||||
|
:key="index"
|
||||||
|
:class="['ai-chat-message', message.role === 'user' ? 'user-message' : 'ai-message']"
|
||||||
|
>
|
||||||
|
<div class="message-content">
|
||||||
|
{{ message.content }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="aiMessages.length === 0" class="empty-ai-messages">
|
||||||
|
暂无AI对话记录
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content-footer">
|
||||||
|
对话条数: {{ aiMessages.length }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else class="content-wrapper">
|
||||||
{{ content }}
|
<div class="content-scrollable">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
<div class="content-footer">
|
||||||
|
字数: {{ countCharacters(content) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -172,18 +252,78 @@ body {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
color: #ecf0f1;
|
color: #ecf0f1;
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-scrollable {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
padding-right: 20px;
|
||||||
|
text-align: left;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-footer {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-info {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-count {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-scrollable::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-scrollable::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-scrollable::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-scrollable::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.imitation-container {
|
.imitation-container {
|
||||||
height: 100%;
|
flex: 1;
|
||||||
padding: 20px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.imitation-button-container {
|
.imitation-button-container {
|
||||||
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.imitation-button {
|
.imitation-button {
|
||||||
@@ -191,10 +331,42 @@ body {
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.imitation-content {
|
.ai-chat-container {
|
||||||
height: 100%;
|
display: flex;
|
||||||
overflow-y: auto;
|
flex-direction: column;
|
||||||
white-space: pre-wrap;
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-chat-message {
|
||||||
|
max-width: 80%;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-radius: 18px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-message {
|
||||||
|
align-self: flex-end;
|
||||||
|
background-color: #3498db;
|
||||||
|
color: white;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-message {
|
||||||
|
align-self: flex-start;
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #ecf0f1;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-ai-messages {
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
padding: 40px 0;
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { Document } from '@element-plus/icons-vue'
|
import { Document, Setting, Folder } from '@element-plus/icons-vue'
|
||||||
import { GetNovelList } from '../../wailsjs/go/backend/App'
|
import { GetNovelList, GetCallWord, SetCallWord, GetModelConfig, SetModelConfig, OpenSourceDir, OpenGenerateDir } from '../../wailsjs/go/backend/App'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
activeButton: {
|
activeButton: {
|
||||||
@@ -13,12 +13,99 @@ const props = defineProps({
|
|||||||
const emit = defineEmits(['update:activeButton', 'menu-click'])
|
const emit = defineEmits(['update:activeButton', 'menu-click'])
|
||||||
|
|
||||||
const novels = ref([])
|
const novels = ref([])
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const modelDialogVisible = ref(false)
|
||||||
|
const callWord = ref([])
|
||||||
|
const newCallWord = ref('')
|
||||||
|
const modelConfig = ref({})
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
function handleNovelClick(novelId, novelLabel) {
|
function handleNovelClick(novelId, novelLabel) {
|
||||||
emit('update:activeButton', novelId)
|
emit('update:activeButton', novelId)
|
||||||
emit('menu-click', { id: novelId, filename: novelLabel })
|
emit('menu-click', { id: novelId, filename: novelLabel })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openCallWordDialog() {
|
||||||
|
isLoading.value = true
|
||||||
|
try {
|
||||||
|
const words = await GetCallWord()
|
||||||
|
callWord.value = words
|
||||||
|
dialogVisible.value = true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting call word:', error)
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openModelDialog() {
|
||||||
|
isLoading.value = true
|
||||||
|
try {
|
||||||
|
const config = await GetModelConfig()
|
||||||
|
modelConfig.value = config
|
||||||
|
modelDialogVisible.value = true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting model config:', error)
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveModelConfig() {
|
||||||
|
isLoading.value = true
|
||||||
|
try {
|
||||||
|
await SetModelConfig(modelConfig.value)
|
||||||
|
modelDialogVisible.value = false
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving model config:', error)
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开原文目录
|
||||||
|
async function openSourceDirectory() {
|
||||||
|
try {
|
||||||
|
await OpenSourceDir()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error opening source directory:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开仿文目录
|
||||||
|
async function openGenerateDirectory() {
|
||||||
|
try {
|
||||||
|
await OpenGenerateDir()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error opening generate directory:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveCallWord() {
|
||||||
|
isLoading.value = true
|
||||||
|
try {
|
||||||
|
await SetCallWord(callWord.value)
|
||||||
|
dialogVisible.value = false
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving call word:', error)
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCallWord() {
|
||||||
|
if (newCallWord.value.trim()) {
|
||||||
|
callWord.value.push(newCallWord.value.trim())
|
||||||
|
newCallWord.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustTextareaHeight(event) {
|
||||||
|
const textarea = event.target
|
||||||
|
textarea.style.height = 'auto'
|
||||||
|
textarea.style.height = Math.min(textarea.scrollHeight, 300) + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
async function loadNovelList() {
|
async function loadNovelList() {
|
||||||
try {
|
try {
|
||||||
const novelList = await GetNovelList()
|
const novelList = await GetNovelList()
|
||||||
@@ -35,6 +122,14 @@ async function loadNovelList() {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 组件挂载时加载小说列表
|
// 组件挂载时加载小说列表
|
||||||
loadNovelList()
|
loadNovelList()
|
||||||
|
|
||||||
|
// 添加窗口焦点事件监听器
|
||||||
|
window.addEventListener('focus', loadNovelList)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组件卸载时移除事件监听器
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('focus', loadNovelList)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -55,7 +150,163 @@ onMounted(() => {
|
|||||||
<div v-if="novels.length === 0" class="empty-state">
|
<div v-if="novels.length === 0" class="empty-state">
|
||||||
暂无小说文件
|
暂无小说文件
|
||||||
</div>
|
</div>
|
||||||
|
<div class="setting-button">
|
||||||
|
<el-button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
@click="openCallWordDialog"
|
||||||
|
:loading="isLoading"
|
||||||
|
>
|
||||||
|
<el-icon><Setting /></el-icon>
|
||||||
|
<span>设置提示词</span>
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
@click="openModelDialog"
|
||||||
|
:loading="isLoading"
|
||||||
|
>
|
||||||
|
<el-icon><Setting /></el-icon>
|
||||||
|
<span>模型设置</span>
|
||||||
|
</el-button>
|
||||||
|
<div class="directory-buttons">
|
||||||
|
<el-button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
@click="openSourceDirectory"
|
||||||
|
>
|
||||||
|
<el-icon><Folder /></el-icon>
|
||||||
|
<span>打开原文目录</span>
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
@click="openGenerateDirectory"
|
||||||
|
>
|
||||||
|
<el-icon><Folder /></el-icon>
|
||||||
|
<span>打开仿文目录</span>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 提示词设置对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
title="设置提示词"
|
||||||
|
width="800px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<div v-if="isLoading" class="dialog-loading">
|
||||||
|
<el-icon class="is-loading"><i-ep-loading /></el-icon>
|
||||||
|
加载中...
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<el-form>
|
||||||
|
<el-form-item>
|
||||||
|
<div
|
||||||
|
v-for="(word, index) in callWord"
|
||||||
|
:key="index"
|
||||||
|
class="call-word-item"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="callWord[index]"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
class="call-word-textarea"
|
||||||
|
placeholder="输入提示词"
|
||||||
|
@input="adjustTextareaHeight"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
@click="callWord.splice(index, 1)"
|
||||||
|
class="delete-button"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="new-call-word-container">
|
||||||
|
<el-input
|
||||||
|
v-model="newCallWord"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
placeholder="输入新的提示词"
|
||||||
|
class="new-call-word-input"
|
||||||
|
@keyup.enter="addCallWord"
|
||||||
|
@input="adjustTextareaHeight"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="addCallWord"
|
||||||
|
class="add-button"
|
||||||
|
>
|
||||||
|
添加
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="saveCallWord" :loading="isLoading">保存</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 模型设置对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="modelDialogVisible"
|
||||||
|
title="模型设置"
|
||||||
|
width="600px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<div v-if="isLoading" class="dialog-loading">
|
||||||
|
<el-icon class="is-loading"><i-ep-loading /></el-icon>
|
||||||
|
加载中...
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<el-form>
|
||||||
|
<el-form-item label="API Key">
|
||||||
|
<el-input
|
||||||
|
v-model="modelConfig.key"
|
||||||
|
placeholder="输入API Key"
|
||||||
|
class="model-config-input"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="模型名称">
|
||||||
|
<el-input
|
||||||
|
v-model="modelConfig.model"
|
||||||
|
placeholder="输入模型名称"
|
||||||
|
class="model-config-input"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="输入价格">
|
||||||
|
<el-input
|
||||||
|
v-model.number="modelConfig.in"
|
||||||
|
type="number"
|
||||||
|
placeholder="输入价格"
|
||||||
|
class="model-config-input"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="输出价格">
|
||||||
|
<el-input
|
||||||
|
v-model.number="modelConfig.out"
|
||||||
|
type="number"
|
||||||
|
placeholder="输出价格"
|
||||||
|
class="model-config-input"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="modelDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="saveModelConfig" :loading="isLoading">保存</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -111,4 +362,89 @@ onMounted(() => {
|
|||||||
color: #95a5a6;
|
color: #95a5a6;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.setting-button {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 0 15px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-button .el-button {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.directory-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.directory-buttons .el-button {
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-word-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-word-textarea {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 60px;
|
||||||
|
resize: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-button {
|
||||||
|
margin-top: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-call-word-container {
|
||||||
|
margin-top: 15px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-call-word-input {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 60px;
|
||||||
|
resize: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-button {
|
||||||
|
margin-top: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-config-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-loading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-loading .el-icon {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
20
frontend/wailsjs/go/backend/App.d.ts
vendored
20
frontend/wailsjs/go/backend/App.d.ts
vendored
@@ -1,8 +1,28 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
// This file is automatically generated. DO NOT EDIT
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
import {ai} from '../models';
|
||||||
|
import {backend} from '../models';
|
||||||
|
|
||||||
|
export function GetAIContent(arg1:string):Promise<Array<ai.Message>>;
|
||||||
|
|
||||||
|
export function GetCallWord():Promise<Array<string>>;
|
||||||
|
|
||||||
|
export function GetFangWenContent(arg1:string):Promise<string>;
|
||||||
|
|
||||||
|
export function GetModelConfig():Promise<backend.ModelConfig>;
|
||||||
|
|
||||||
export function GetNovelContent(arg1:string):Promise<string>;
|
export function GetNovelContent(arg1:string):Promise<string>;
|
||||||
|
|
||||||
export function GetNovelList():Promise<Array<string>>;
|
export function GetNovelList():Promise<Array<string>>;
|
||||||
|
|
||||||
|
export function GetToken(arg1:string):Promise<string>;
|
||||||
|
|
||||||
|
export function OpenGenerateDir():Promise<void>;
|
||||||
|
|
||||||
|
export function OpenSourceDir():Promise<void>;
|
||||||
|
|
||||||
|
export function SetCallWord(arg1:Array<string>):Promise<void>;
|
||||||
|
|
||||||
|
export function SetModelConfig(arg1:backend.ModelConfig):Promise<void>;
|
||||||
|
|
||||||
export function WriteNovelContent(arg1:string):Promise<string>;
|
export function WriteNovelContent(arg1:string):Promise<string>;
|
||||||
|
|||||||
@@ -2,6 +2,22 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
// This file is automatically generated. DO NOT EDIT
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export function GetAIContent(arg1) {
|
||||||
|
return window['go']['backend']['App']['GetAIContent'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetCallWord() {
|
||||||
|
return window['go']['backend']['App']['GetCallWord']();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetFangWenContent(arg1) {
|
||||||
|
return window['go']['backend']['App']['GetFangWenContent'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetModelConfig() {
|
||||||
|
return window['go']['backend']['App']['GetModelConfig']();
|
||||||
|
}
|
||||||
|
|
||||||
export function GetNovelContent(arg1) {
|
export function GetNovelContent(arg1) {
|
||||||
return window['go']['backend']['App']['GetNovelContent'](arg1);
|
return window['go']['backend']['App']['GetNovelContent'](arg1);
|
||||||
}
|
}
|
||||||
@@ -10,6 +26,26 @@ export function GetNovelList() {
|
|||||||
return window['go']['backend']['App']['GetNovelList']();
|
return window['go']['backend']['App']['GetNovelList']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetToken(arg1) {
|
||||||
|
return window['go']['backend']['App']['GetToken'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OpenGenerateDir() {
|
||||||
|
return window['go']['backend']['App']['OpenGenerateDir']();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OpenSourceDir() {
|
||||||
|
return window['go']['backend']['App']['OpenSourceDir']();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SetCallWord(arg1) {
|
||||||
|
return window['go']['backend']['App']['SetCallWord'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SetModelConfig(arg1) {
|
||||||
|
return window['go']['backend']['App']['SetModelConfig'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
export function WriteNovelContent(arg1) {
|
export function WriteNovelContent(arg1) {
|
||||||
return window['go']['backend']['App']['WriteNovelContent'](arg1);
|
return window['go']['backend']['App']['WriteNovelContent'](arg1);
|
||||||
}
|
}
|
||||||
|
|||||||
42
frontend/wailsjs/go/models.ts
Normal file
42
frontend/wailsjs/go/models.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
export namespace ai {
|
||||||
|
|
||||||
|
export class Message {
|
||||||
|
role: string;
|
||||||
|
content: string;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new Message(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.role = source["role"];
|
||||||
|
this.content = source["content"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace backend {
|
||||||
|
|
||||||
|
export class ModelConfig {
|
||||||
|
key: string;
|
||||||
|
model: string;
|
||||||
|
in: number;
|
||||||
|
out: number;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new ModelConfig(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.key = source["key"];
|
||||||
|
this.model = source["model"];
|
||||||
|
this.in = source["in"];
|
||||||
|
this.out = source["out"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
9
go.mod
9
go.mod
@@ -2,7 +2,11 @@ module fiction
|
|||||||
|
|
||||||
go 1.23
|
go 1.23
|
||||||
|
|
||||||
require github.com/wailsapp/wails/v2 v2.11.0
|
require (
|
||||||
|
github.com/wailsapp/wails/v2 v2.11.0
|
||||||
|
gorm.io/driver/sqlite v1.6.0
|
||||||
|
gorm.io/gorm v1.31.1
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bep/debounce v1.2.1 // indirect
|
github.com/bep/debounce v1.2.1 // indirect
|
||||||
@@ -11,6 +15,8 @@ require (
|
|||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/labstack/echo/v4 v4.13.3 // indirect
|
github.com/labstack/echo/v4 v4.13.3 // indirect
|
||||||
github.com/labstack/gommon v0.4.2 // indirect
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||||
@@ -19,6 +25,7 @@ require (
|
|||||||
github.com/leaanthony/u v1.1.1 // indirect
|
github.com/leaanthony/u v1.1.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -12,6 +12,10 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
|
|||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
|
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
|
||||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
|
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
|
||||||
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
|
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
|
||||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||||
@@ -34,6 +38,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
|||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -79,3 +85,7 @@ golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
|||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||||
|
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||||
|
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||||
|
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
|
|||||||
1
main.go
1
main.go
@@ -25,6 +25,7 @@ func main() {
|
|||||||
},
|
},
|
||||||
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
|
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
|
||||||
OnStartup: app.Startup,
|
OnStartup: app.Startup,
|
||||||
|
OnShutdown: app.Shutdown,
|
||||||
Bind: []interface{}{
|
Bind: []interface{}{
|
||||||
app,
|
app,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
这个是旭旭宝宝
|
|
||||||
Reference in New Issue
Block a user