add some files

This commit is contained in:
skong 2024-01-13 17:07:43 +08:00
parent 06e151b1f3
commit 768b1e7f00
71 changed files with 14410 additions and 36 deletions

37
.gitignore vendored
View File

@ -1,34 +1,3 @@
# ---> Go
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
# ---> Vue
# gitignore template for Vue.js projects
#
# Recommended template: Node.gitignore
# TODO: where does this rule come from?
docs/_book
# TODO: where does this rule come from?
test/
build/bin
node_modules
frontend/dist

View File

@ -1,3 +1,19 @@
# wails-cloud-work
# README
wails 构建的云就业打卡
## About
This is the official Wails React template.
You can configure the project by editing `wails.json`. More information about the project settings can be found
here: https://wails.io/docs/reference/project-config
## Live Development
To run in live development mode, run `wails dev` in the project directory. This will run a Vite development
server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser
and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect
to this in your browser, and you can call your Go code from devtools.
## Building
To build a redistributable, production mode package, use `wails build`.

34
backend/app.go Normal file
View File

@ -0,0 +1,34 @@
package backend
import (
"context"
"could-work/backend/core/define"
"could-work/backend/event"
"could-work/backend/util"
)
var log = define.Log
// App struct
type App struct {
ctx context.Context
}
// NewApp creates a new App application struct
func NewApp() *App {
return &App{}
}
func (a *App) Startup(ctx context.Context) {
a.ctx = ctx
log.Info("程序启动")
util.TaskRunner(
event.InitUserDB,
event.InitGinServer,
)
}
func (a *App) Shutdown(ctx context.Context) {
log.Info("程序关闭")
}

70
backend/core/bot/bot.go Normal file
View File

@ -0,0 +1,70 @@
package bot
import (
"context"
"could-work/backend/core/define"
"could-work/backend/util"
"time"
"github.com/tencent-connect/botgo"
"github.com/tencent-connect/botgo/openapi"
"github.com/tencent-connect/botgo/token"
"github.com/tencent-connect/botgo/websocket"
)
var (
log = define.Log
api openapi.OpenAPI
dispose = &Processor{}
)
func RegisterBot(config *util.QBot, env string) {
intent := websocket.RegisterHandlers(
// at 机器人事件,目前是在这个事件处理中有逻辑,会回消息,其他的回调处理都只把数据打印出来,不做任何处理
ATMessageEventHandler(),
// 如果想要捕获到连接成功的事件,可以实现这个回调
ReadyHandler(),
// 连接关闭回调
ErrorNotifyHandler(),
// 频道事件
GuildEventHandler(),
// 成员事件
MemberEventHandler(),
// 子频道事件
ChannelEventHandler(),
// 私信,目前只有私域才能够收到这个,如果你的机器人不是私域机器人,会导致连接报错,那么启动 example 就需要注释掉这个回调
DirectMessageHandler(),
// 频道消息,只有私域才能够收到这个,如果你的机器人不是私域机器人,会导致连接报错,那么启动 example 就需要注释掉这个回调
CreateMessageHandler(),
// 互动事件
InteractionHandler(),
// 发帖事件
ThreadEventHandler(),
)
token := token.BotToken(config.AppID, config.Token)
// 初始化机器人
switch env {
case "release":
api = botgo.NewOpenAPI(token)
default:
api = botgo.NewSandboxOpenAPI(token)
}
// 连接超时
api.WithTimeout(3 * time.Second)
dispose.API = api
// Websocket 连接
wsInfo, err := api.WS(context.Background(), nil, "")
if err != nil {
log.Errorf("WebSocket connection error %s", err)
}
// 指定需要启动的分片数为 2 的话可以手动修改 wsInfo
if err := botgo.NewSessionManager().Start(wsInfo, token, &intent); err != nil {
log.Errorf("Start bot failed :> %s", err)
}
}

88
backend/core/bot/event.go Normal file
View File

@ -0,0 +1,88 @@
package bot
import (
"strings"
"github.com/tencent-connect/botgo/dto"
"github.com/tencent-connect/botgo/dto/message"
"github.com/tencent-connect/botgo/event"
)
// ReadyHandler 感知连接成功事件
func ReadyHandler() event.ReadyHandler {
return func(event *dto.WSPayload, data *dto.WSReadyData) {
log.Info("连接成功事件: ", data)
}
}
// ErrorNotifyHandler 通知失败事件
func ErrorNotifyHandler() event.ErrorNotifyHandler {
return func(err error) {
log.Error("error notify receive: ", err)
}
}
// ATMessageEventHandler 实现处理 at 消息的回调
func ATMessageEventHandler() event.ATMessageEventHandler {
return func(event *dto.WSPayload, data *dto.WSATMessageData) error {
input := strings.ToLower(message.ETLInput(data.Content))
return dispose.ProcessMessage(input, data)
}
}
// GuildEventHandler 处理频道事件
func GuildEventHandler() event.GuildEventHandler {
return func(event *dto.WSPayload, data *dto.WSGuildData) error {
log.Info("处理频道事件: ", data)
return nil
}
}
// ChannelEventHandler 处理子频道事件
func ChannelEventHandler() event.ChannelEventHandler {
return func(event *dto.WSPayload, data *dto.WSChannelData) error {
log.Info("子频道事件: ", data)
return nil
}
}
// MemberEventHandler 处理成员变更事件
func MemberEventHandler() event.GuildMemberEventHandler {
return func(event *dto.WSPayload, data *dto.WSGuildMemberData) error {
log.Info("成员变更事件: ", data)
return nil
}
}
// DirectMessageHandler 处理私信事件
func DirectMessageHandler() event.DirectMessageEventHandler {
return func(event *dto.WSPayload, data *dto.WSDirectMessageData) error {
log.Info("私信事件: ", data)
return nil
}
}
// CreateMessageHandler 处理消息事件
func CreateMessageHandler() event.MessageEventHandler {
return func(event *dto.WSPayload, data *dto.WSMessageData) error {
log.Info("消息事件: ", data)
cmd := PasserMessage(data.Content)
return dispose.PublicMessage(cmd, data)
}
}
// InteractionHandler 处理内联交互事件
func InteractionHandler() event.InteractionEventHandler {
return func(event *dto.WSPayload, data *dto.WSInteractionData) error {
log.Info("内联交互事件: ", data)
return dispose.ProcessInlineSearch(data)
}
}
// ThreadEventHandler 论坛主贴事件
func ThreadEventHandler() event.ThreadEventHandler {
return func(event *dto.WSPayload, data *dto.WSThreadData) error {
log.Info("论坛主贴事件: ", data)
return nil
}
}

View File

@ -0,0 +1,87 @@
package bot
import (
"context"
"strings"
"github.com/tencent-connect/botgo/dto"
"github.com/tencent-connect/botgo/dto/message"
)
// ProcessMessage 消息处理流
func (p Processor) ProcessMessage(input string, data *dto.WSATMessageData) error {
ctx := context.Background()
cmd := message.ParseCommand(input)
toCreate := &dto.MessageToCreate{
Content: "默认回复" + message.Emoji(307),
MessageReference: &dto.MessageReference{
// 引用这条消息
MessageID: data.ID,
IgnoreGetMessageError: true,
},
}
log.Infof("执行命令: %s \n 频道ID: %s", cmd, data.ID)
switch cmd.Cmd {
case "/dm":
p.dmHandler(data)
case "/chat":
p.sendChatReply(ctx, cmd.Content, data.ChannelID, toCreate)
case "/test":
switch cmd.Content {
case "hi":
p.sendReply(ctx, data.ChannelID, toCreate)
case "time":
toCreate.Content = genReplyContent(data)
p.sendReply(ctx, data.ChannelID, toCreate)
case "ark":
toCreate.Ark = genReplyArk(data)
p.sendReply(ctx, data.ChannelID, toCreate)
case "公告":
p.setAnnounces(ctx, data)
case "pin":
if data.MessageReference != nil {
p.setPins(ctx, data.ChannelID, data.MessageReference.MessageID)
}
case "emoji":
if data.MessageReference != nil {
p.setEmoji(ctx, data.ChannelID, data.MessageReference.MessageID)
}
default:
toCreate.Image = "https://c-ssl.duitang.com/uploads/blog/202207/09/20220709150824_97667.jpg"
p.sendReply(ctx, data.ChannelID, toCreate)
}
}
return nil
}
// PublicMessage 公开信息
func (p Processor) PublicMessage(cmd *message.CMD, data *dto.WSMessageData) error {
toCreate := &dto.MessageToCreate{
MessageReference: &dto.MessageReference{
MessageID: data.ID,
IgnoreGetMessageError: true,
},
}
p.sendChatReply(
context.Background(),
cmd.Content,
data.ChannelID,
toCreate,
)
return nil
}
// PasserMessage 解析命令
func PasserMessage(content string) *message.CMD {
return message.ParseCommand(
strings.ToLower(message.ETLInput(content)),
)
}

197
backend/core/bot/process.go Normal file
View File

@ -0,0 +1,197 @@
package bot
import (
"context"
"could-work/backend/core/chat"
"encoding/json"
"fmt"
"time"
"github.com/tencent-connect/botgo/dto"
"github.com/tencent-connect/botgo/dto/message"
"github.com/tencent-connect/botgo/openapi"
)
// Processor is a struct to process message
type Processor struct {
API openapi.OpenAPI
}
func (p Processor) sendChatReply(ctx context.Context, msg, channelID string, toCreate *dto.MessageToCreate) {
log.Info("sendChatReply: ", toCreate.Markdown)
workBot := chat.NewChatBot()
reply, err := workBot.Send(msg)
if err != nil {
log.Error(err)
}
if reply == "" {
reply = "默认信息"
}
log.Error(reply)
toCreate.Content = reply
if _, err := p.API.PostMessage(ctx, channelID, toCreate); err != nil {
log.Println(err)
}
}
// ProcessInlineSearch is a function to process inline search
func (p Processor) ProcessInlineSearch(interaction *dto.WSInteractionData) error {
if interaction.Data.Type != dto.InteractionDataTypeChatSearch {
return fmt.Errorf("interaction data type not chat search")
}
search := &dto.SearchInputResolved{}
if err := json.Unmarshal(interaction.Data.Resolved, search); err != nil {
log.Println(err)
return err
}
if search.Keyword != "test" {
return fmt.Errorf("resolved search key not allowed")
}
searchRsp := &dto.SearchRsp{
Layouts: []dto.SearchLayout{
{
LayoutType: 0,
ActionType: 0,
Title: "内联搜索",
Records: []dto.SearchRecord{
{
Cover: "https://pub.idqqimg.com/pc/misc/files/20211208/311cfc87ce394c62b7c9f0508658cf25.png",
Title: "内联搜索标题",
Tips: "内联搜索 tips",
URL: "https://www.qq.com",
},
},
},
},
}
body, _ := json.Marshal(searchRsp)
if err := p.API.PutInteraction(context.Background(), interaction.ID, string(body)); err != nil {
log.Println("api call putInteractionInlineSearch error: ", err)
return err
}
return nil
}
func (p Processor) dmHandler(data *dto.WSATMessageData) {
dm, err := p.API.CreateDirectMessage(
context.Background(), &dto.DirectMessageToCreate{
SourceGuildID: data.GuildID,
RecipientID: data.Author.ID,
},
)
if err != nil {
log.Println(err)
return
}
toCreate := &dto.MessageToCreate{
Content: "默认私信回复",
}
_, err = p.API.PostDirectMessage(
context.Background(), dm, toCreate,
)
if err != nil {
log.Println(err)
return
}
}
func genReplyContent(data *dto.WSATMessageData) string {
var tpl = `你好%s
在子频道 %s 收到消息
收到的消息发送时时间为%s
当前本地时间为%s
`
msgTime, _ := data.Timestamp.Time()
return fmt.Sprintf(
tpl,
message.MentionUser(data.Author.ID),
message.MentionChannel(data.ChannelID),
msgTime, time.Now().Format(time.RFC3339),
)
}
func genReplyArk(data *dto.WSATMessageData) *dto.Ark {
return &dto.Ark{
TemplateID: 23,
KV: []*dto.ArkKV{
{
Key: "#DESC#",
Value: "这是 ark 的描述信息",
},
{
Key: "#PROMPT#",
Value: "这是 ark 的摘要信息",
},
{
Key: "#LIST#",
Obj: []*dto.ArkObj{
{
ObjKV: []*dto.ArkObjKV{
{
Key: "desc",
Value: "这里展示的是 23 号模板",
},
},
},
{
ObjKV: []*dto.ArkObjKV{
{
Key: "desc",
Value: "这是 ark 的列表项名称",
},
{
Key: "link",
Value: "https://www.qq.com",
},
},
},
},
},
},
}
}
func (p Processor) setEmoji(ctx context.Context, channelID string, messageID string) {
err := p.API.CreateMessageReaction(
ctx, channelID, messageID, dto.Emoji{
ID: "307",
Type: 1,
},
)
if err != nil {
log.Println(err)
}
}
func (p Processor) setPins(ctx context.Context, channelID, msgID string) {
_, err := p.API.AddPins(ctx, channelID, msgID)
if err != nil {
log.Println(err)
}
}
func (p Processor) setAnnounces(ctx context.Context, data *dto.WSATMessageData) {
if _, err := p.API.CreateChannelAnnounces(
ctx, data.ChannelID,
&dto.ChannelAnnouncesToCreate{MessageID: data.ID},
); err != nil {
log.Println(err)
}
}
func (p Processor) sendReply(ctx context.Context, channelID string, toCreate *dto.MessageToCreate) {
if _, err := p.API.PostMessage(ctx, channelID, toCreate); err != nil {
log.Println(err)
}
}

View File

@ -0,0 +1,77 @@
package captcha
import (
"bytes"
"could-work/backend/core/define"
"mime/multipart"
"net/http"
"net/url"
"strings"
"github.com/Fromsko/gouitls/knet"
"github.com/tidwall/gjson"
)
type VerifyRequest struct {
File *bytes.Buffer
Token string
Code string
TokenUrl string
CodeUrl string
Auth Auth
}
type Auth struct {
UserName string
PassWord string
}
func (r *VerifyRequest) GetToken() {
sendRequest := knet.SendRequest{
Method: "POST",
FetchURL: r.TokenUrl,
Data: strings.NewReader((url.Values{
"username": {r.Auth.UserName},
"password": {r.Auth.PassWord},
}).Encode()),
Headers: map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
},
}
sendRequest.Send(func(resp []byte, cookies []*http.Cookie, err error) {
if gjson.Get(string(resp), "status").Int() != 200 {
msg := gjson.Get(string(resp), "err").String()
define.Log.Errorf("获取Token失败: %v", msg)
}
r.Token = gjson.Get(string(resp), "token").String()
})
}
func (r *VerifyRequest) Recognize(content []byte) {
writer := multipart.NewWriter(r.File)
if part, err := writer.CreateFormFile("file", "code.png"); err == nil {
_, _ = part.Write(content)
_ = writer.Close()
}
sendRequest := knet.SendRequest{
Name: "Recognize",
Method: "POST",
FetchURL: r.CodeUrl,
Data: r.File,
Headers: map[string]string{
"accept": "application/json",
"Authorization": "Bearer " + r.Token,
"Content-Type": writer.FormDataContentType(),
},
}
sendRequest.Send(func(resp []byte, cookies []*http.Cookie, err error) {
if gjson.Get(string(resp), "status").Int() != 200 {
msg := gjson.Get(string(resp), "err").String()
define.Log.Errorf("识别失败: %s", msg)
}
r.Code = gjson.Get(string(resp), "msg").String()
define.Log.Infof("识别到验证码: %s", r.Code)
})
}

View File

@ -0,0 +1,27 @@
package captcha
import (
"bytes"
"could-work/backend/core/define"
"log"
"os"
)
func IdentifyCode() string {
req := VerifyRequest{
File: &bytes.Buffer{},
TokenUrl: define.TokenUrl,
CodeUrl: define.CodeUrl,
Auth: Auth{
UserName: "admin",
PassWord: "admin",
},
}
req.GetToken()
if content, err := os.ReadFile(define.ValidImg); err != nil {
log.Fatalf("验证码读取错误: %v", err)
} else {
req.Recognize(content)
}
return req.Code
}

171
backend/core/chat/chat.go Normal file
View File

@ -0,0 +1,171 @@
package chat
import (
"context"
"could-work/backend/core/define"
"fmt"
"net/http"
"net/url"
"os"
"time"
openai "github.com/sashabaranov/go-openai"
)
type WorkBot struct {
BaseKey string // 密钥
BaseURL string // api地址
Proxy string // 代理地址
MaxTokens int // token 数量
Client *openai.Client // 连接器
}
func NewWorkBot(opts ...func(*WorkBot)) *WorkBot {
w := &WorkBot{}
for _, opt := range opts {
opt(w)
}
if w.Proxy != "" {
NewProxyClient(w)
}
if w.BaseKey != "" {
w.Client = NewDefaultClient(w)
}
return w
}
func defaultConfig(key, url string) *openai.ClientConfig {
config := openai.DefaultConfig(key)
if url != "" {
config.BaseURL = url
}
return &config
}
func NewDefaultClient(wb *WorkBot) *openai.Client {
return openai.NewClientWithConfig(
*defaultConfig(wb.BaseKey, wb.BaseURL),
)
}
func NewProxyClient(client *WorkBot) *openai.Client {
proxyUrl, _ := url.Parse(client.Proxy)
config := defaultConfig(client.BaseKey, client.BaseURL)
config.HTTPClient = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxyUrl),
},
}
return openai.NewClientWithConfig(*config)
}
func LoadPrompt() string {
prompt, err := os.ReadFile(define.PromptPath)
if err != nil {
return ""
}
return string(prompt)
}
func WithBaseKey(key string) func(*WorkBot) {
return func(bot *WorkBot) {
bot.BaseKey = key
}
}
func WithBaseUrl(url string) func(*WorkBot) {
return func(bot *WorkBot) {
bot.BaseURL = url
}
}
func WithProxy(proxy string) func(*WorkBot) {
return func(wb *WorkBot) {
wb.Proxy = proxy
}
}
func WithClient(client *WorkBot) func(*WorkBot) {
return func(wb *WorkBot) {
wb.Client = client.Client
}
}
func WithMaxTokens(maxTokens int) func(*WorkBot) {
return func(wb *WorkBot) {
wb.MaxTokens = maxTokens
}
}
func (wb *WorkBot) Send(prompt string) (string, error) {
if wb.Client == nil {
return "", fmt.Errorf("client is not initialized")
}
req := &openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: "system",
Content: LoadPrompt(),
},
{
Role: "user",
Content: prompt,
},
},
}
if wb.MaxTokens != 0 {
req.MaxTokens = wb.MaxTokens
} else {
req.MaxTokens = 1024
}
reply, err := sendWithTimeout(wb.Client, req, time.Second*60)
return reply, err
}
func sendWithTimeout(client *openai.Client, req *openai.ChatCompletionRequest, timeout time.Duration) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
resultCh := make(chan string)
go func() {
resp, err := client.CreateChatCompletion(ctx, *req)
if err != nil {
resultCh <- fmt.Sprintf("completion error: %v", err)
return
}
resultCh <- resp.Choices[0].Message.Content
}()
select {
case <-ctx.Done():
fmt.Println("Send timeout")
return "", ctx.Err()
case result := <-resultCh:
return result, nil
}
}
func NewChatBot() *WorkBot {
workBot := NewWorkBot(
WithMaxTokens(2000),
WithBaseKey(define.CONFIG.OpenAI.Key),
WithBaseUrl(define.CONFIG.OpenAI.BaseUrl),
)
return workBot
}

View File

@ -0,0 +1,39 @@
package define
import (
"could-work/backend/util"
"os"
"path/filepath"
"github.com/Fromsko/gouitls/logs"
)
var (
Log = logs.InitLogger()
CONFIG, _ = util.InitConfig()
UserName = CONFIG.Username // "202127530334"
PassWord = CONFIG.Password // "Lyhqyh99@"
BaseUrl = CONFIG.HtmlURL.BaseUrl // "https://jy.hniu.cn/login"
TokenUrl = CONFIG.HtmlURL.TokenUrl // "http://localhost:20000/api/v1/login"
CodeUrl = CONFIG.HtmlURL.CodeUrl // "http://localhost:20000/api/v1/verify-code"
)
var (
WorkPath, _ = os.Getwd()
PromptPath = filepath.Join(WorkPath, "res", "prompt")
ImgPath = filepath.Join(WorkPath, "res", "img")
NotifyImg = filepath.Join(ImgPath, "气泡通知.png")
LoginImg = filepath.Join(ImgPath, "登录页.png")
LogoutImg = filepath.Join(ImgPath, "退出页")
ValidImg = filepath.Join(ImgPath, "验证码.png")
IndexImg = filepath.Join(ImgPath, "主页.png")
InternshipNotice = filepath.Join(ImgPath, "实习通知.png")
InternshipJournal = filepath.Join(ImgPath, "实习日志.png")
InternshipDiaryList = filepath.Join(ImgPath, "%s实习日志.png")
)
var (
Title = "云就业平台"
Version = "1.0"
)

View File

@ -0,0 +1,83 @@
package spider
import (
"could-work/backend/core/define"
"could-work/backend/util"
"fmt"
"strconv"
"time"
rod "github.com/Fromsko/rodPro"
)
// SearchLatestDiary 实习日志列表
func (w *Web) SearchLatestDiary(ele *rod.Element) *rod.Element {
log.Info("实习列表")
return ele.MustElement("#diaryListOne").MustWaitLoad()
}
func (w *Web) SearchDiaryList(ele *rod.Element) {
log.Info("遍历列表")
diaryList := ele.MustWaitStable().MustElements("div.item.clearfix")
if !diaryList.Empty() {
log.Info("找到了")
for index, diary := range diaryList {
diary.MustWaitStable().MustScreenshot(
fmt.Sprintf(define.InternshipDiaryList, strconv.Itoa(index)),
)
log.Info(index, diary)
}
}
}
func (w *Web) DiaryHandle(ele *rod.Element) DiaryHandler {
log.Info("准备处理日志")
// 实习日志数量记录
quantity := ele.MustWaitLoad().MustElement("div.grid-toolbar.clearfix")
quantity.MustWaitStable().MustScreenshot(define.InternshipJournal)
quantity.MustElement("#diary_submit").MustClick()
dialogPage := quantity.MustElementX(`/html/body/div[5]/div/table/tbody`)
iframe := dialogPage.MustElementX("tr[2]/td/div/iframe").MustFrame().MustWaitLoad()
log.Info(iframe)
return DiaryHandler{
"地点": iframe.MustElement("#address"),
"内容": iframe.MustElement("#content"),
"文件": iframe.MustElement("[type=file]"),
"提交": iframe.MustElement("#btn_info_submit"),
"取消": dialogPage.MustElementX("tr[1]/td/button"),
"截图": dialogPage,
}
}
func (diary DiaryHandler) WaitUserReceive(callBack func(DiaryHandler, *util.Message)) {
log.Info("处理日志", "等待用户输入")
timer := time.NewTimer(time.Second * 60)
defer timer.Stop()
for {
select {
case <-timer.C:
return
default:
if !util.TaskQueue.IsEmpty() {
message := util.TaskQueue.Pop()
switch message.Type {
case "client":
log.Info("正在对话|重置时间")
timer.Reset(time.Second * 60)
case "receive":
callBack(diary, message)
default:
log.Warnf("未知消息类型:> %s", message.Type)
}
}
}
}
}

View File

@ -0,0 +1,58 @@
package spider
import (
"could-work/backend/core/captcha"
"could-work/backend/core/define"
)
// SearchParams 查找参数
func (w *Web) SearchParams(text string) bool {
search, err := w.Page.Search(text)
if err != nil {
return false
}
return search.ResultCount != 0
}
// LoginPage 登陆页面
func (w *Web) LoginPage() bool {
page := w.Page
page.MustWaitLoad()
page.MustElement("#user_name").MustInput(define.UserName)
page.MustElement("#password").MustInput(define.PassWord)
page.MustElement("#img_valid_code").MustScreenshot(define.ValidImg)
page.MustElement("#valid_code").MustInput(captcha.IdentifyCode())
page.MustElement("#privacy-agreement").MustClick()
page.MustElement("#login_btn").MustClick()
page.MustScreenshot(define.LoginImg)
return !(w.SearchParams("请输入正确的验证码") || w.SearchParams("生源不存在"))
}
// LogOutPage 退出登录
func (w *Web) LogOutPage() {
w.Page.MustWaitLoad().MustSearch("退出").MustClick()
}
// IndexPage 主页面
func (w *Web) IndexPage(callBack CallBack) {
w.Page.MustWaitStable().MustScreenshot(define.IndexImg)
w.Page.MustWaitLoad().MustSearch("实习管理").MustClick()
elements := w.Page.MustWaitLoad().MustElements("#tab.nav.nav-tabs li")
for index, element := range elements {
//fmt.Println(index, element.MustText())
switch index {
case 1:
// TODO: 实习通知
log.Info("实习通知")
element.MustClick()
notice := w.Page.MustElement("#n_grid.grid").MustWaitLoad()
notice.MustWaitStable().MustScreenshot(define.InternshipNotice)
case 3:
// TODO: 实习日志
log.Info("实习日志")
element.MustClick()
journal := w.Page.MustElement("#tab_3").MustWaitLoad()
_ = callBack(w, journal)
}
}
}

View File

@ -0,0 +1,33 @@
package spider
import (
"could-work/backend/core/define"
rod "github.com/Fromsko/rodPro"
"github.com/Fromsko/rodPro/lib/launcher"
)
var log = define.Log
type (
// Web 自动化浏览器
Web struct {
Browser *rod.Browser
Page *rod.Page
}
// DiaryHandler 日志回调函数
DiaryHandler map[string]*rod.Element
// CallBack 回调函数
CallBack func(web *Web, ele *rod.Element) error
)
// InitWeb 初始化浏览器
func InitWeb(URL string) *Web {
u := launcher.New().Leakless(false).MustLaunch()
w := &Web{
Browser: rod.New().ControlURL(u).MustConnect(),
Page: nil,
}
w.Page = w.Browser.MustPage(URL)
return w
}

19
backend/docker/dockerfile Normal file
View File

@ -0,0 +1,19 @@
FROM python:3.8.5
# 设置清华源
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list && \
sed -i 's/security.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list
# 设置工作目录
RUN mkdir -p /app
WORKDIR /app
# 安装依赖包
RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --no-cache-dir fastapi[all] ddddocr loguru Pillow==9.5.0
RUN pip install --no-cache-dir --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple
# 暴露 FASTAPI 的默认端口
EXPOSE 20000
# 设置容器启动时的默认命令
CMD ["python", "main.py"]

124
backend/docker/ocr/main.py Normal file
View File

@ -0,0 +1,124 @@
# -*- encoding: utf-8 -*-
# @File : img-serve
# @Time : 2023-11-21 16:33:21
# @Docs : 验证码-API服务
import hashlib
from datetime import datetime
from fastapi.responses import RedirectResponse
from fastapi.security import OAuth2PasswordBearer
from fastapi import FastAPI, HTTPException, File, UploadFile, Request, Form, Depends, Header
from ddddocr import DdddOcr
from loguru import logger
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# 模拟数据库中的用户信息
fake_users_db = {
"admin": {
"username": "admin",
"password": "hashedpassword",
}
}
# 访问频率统计字典
ip_access_count = {}
# 日志配置
log = logger.bind(name="")
log.add("xxx.log", format="{time} | {level} | {message}", rotation="1 week")
# 设置重定向
@app.get("/", include_in_schema=False)
async def redirect_to_home():
return {"status": 200, "data": "欢迎来到主页 => https://github.com/Fromsko"}
@app.exception_handler(404)
async def redirect_to_login(request: Request, exc: Exception):
# 重定向到主页面
return RedirectResponse(url="/")
# @app.middleware("http")
# async def log_host(request: Request, call_next):
# log.bind(name=request.client.host).info("Request received")
# if request.url.path.startswith("/api/v1"):
# ip = request.client.host
# if ip not in ip_access_count:
# ip_access_count[ip] = {
# "次数": 1,
# "初次访问": datetime.now().strftime("%Y年%m月%d日 %H:%M:%S"),
# "最后一次": ""
# }
# else:
# ip_access_count[ip]["次数"] += 1
# ip_access_count[ip]["最后一次"] = datetime.now().strftime(
# "%Y年%m月%d日 %H:%M:%S"
# )
# response = await call_next(request)
# return response
class VerifyParams:
def __init__(self) -> None:
self.token = ""
async def create_token(self, username: str, password: str) -> str:
token_data = f"{username}{password}{datetime.now().timestamp()}"
hashed_token = hashlib.sha256(token_data.encode()).hexdigest()
self.token = hashed_token
return hashed_token
@staticmethod
async def get_token(authorization: str = Header(...)) -> str:
if not authorization.startswith("Bearer "):
raise HTTPException(
status_code=401,
detail="The interface is not accessible.",
)
return authorization.replace("Bearer ", "")
@staticmethod
def msg(code: int = 200, msg: str = "", err: str = ""):
return {"status": code, "msg": msg, "err": err}
verify = VerifyParams()
@app.post("/api/v1/login")
async def login(username: str = Form(...), password: str = Form(...)):
if username in fake_users_db and password == "admin":
return {"status": 200, "token": await verify.create_token(username, password)}
else:
return verify.msg(code=401, err="Invalid credentials")
@app.post("/api/v1/verify-code")
async def verify_code(file: UploadFile = File(...), token: str = Depends(verify.get_token)):
if token != verify.token:
return verify.msg(code=401, err="Invalid token")
image_bytes = await file.read()
try:
result = DdddOcr(show_ad=False).classification(image_bytes)
logger.info(f"Verification code recognized: {result}")
print(result)
return verify.msg(msg=result)
except Exception as err:
logger.error(f"Verification code recognition failed. Error: {err}")
return verify.msg(code=500, err=f"{err}")
if __name__ == '__main__':
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)

82
backend/event/auth.go Normal file
View File

@ -0,0 +1,82 @@
package event
import (
"could-work/backend/core/define"
"net/http"
"time"
"github.com/Fromsko/gouitls/auth"
"github.com/gin-gonic/gin"
)
var SubScriber *auth.SubscriberAuth
func init() {
SubScriber = &auth.SubscriberAuth{
Expiration: 5 * time.Hour,
SecretKey: "asdfghjkl5678vvm/.-",
}
}
func InitGinServer() {
engine := gin.Default()
engine.Use(Cors())
api := engine.Group("/api/v1")
{
userAPI := api.Group("/user")
{
userAPI.POST("/login", loginUser)
userAPI.POST("/register", registerUser)
userAPI.PUT("/:id", AuthHeader(), updateUser)
userAPI.DELETE("/:id", AuthHeader(), deleteUser)
}
api.GET("/ws", MonitorWS)
}
err := engine.Run(":7001")
if err != nil {
define.Log.Errorf("启动失败: %s", err)
}
}
func Cors() gin.HandlerFunc {
return func(context *gin.Context) {
method := context.Request.Method
context.Header("Access-Control-Allow-Origin", "*")
context.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
context.Header("Access-Control-Allow-Credentials", "true")
if method == "OPTIONS" {
context.AbortWithStatus(http.StatusNoContent)
}
context.Next()
}
}
func AuthHeader() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
MsgJson(c, &Reply{
Code: 20001,
Msg: "未携带Token",
})
c.Abort()
return
}
if validate, _ := SubScriber.ValidateToken(token); validate {
MsgJson(c, &Reply{
Code: 20002,
Msg: "Token过期",
})
c.Abort()
return
}
c.Next()
}
}

147
backend/event/handle.go Normal file
View File

@ -0,0 +1,147 @@
package event
import (
"could-work/backend/core/chat"
"could-work/backend/core/define"
"could-work/backend/core/spider"
"could-work/backend/util"
"fmt"
rod "github.com/Fromsko/rodPro"
"strings"
"time"
)
var log = define.Log
func handleChat(msg *WebSocketMessage) {
define.Log.Error("聊天:> ", msg)
cb := chat.NewChatBot()
reply, err := cb.Send(msg.Payload)
if err != nil {
reply = err.Error()
define.Log.Errorf("Chat bot err: %s", err)
} else {
define.Log.Infof("Chat bot err: %s", reply)
}
util.MsgQueue.Push(&util.Message{
Type: "chat",
Payload: reply,
})
}
// RegisterWorkTask 注册工作任务
func RegisterWorkTask() {
Web := spider.InitWeb(define.BaseUrl)
if !Web.LoginPage() {
log.Info("登录失败")
} else {
log.Info("登录成功")
}
Web.IndexPage(func(web *spider.Web, ele *rod.Element) error {
web.SearchDiaryList(web.SearchLatestDiary(ele))
handle := web.DiaryHandle(ele)
go util.MsgQueue.Push(&util.Message{
Type: "chat",
Payload: "请输入需要填写的日志, [格式: /task 内容|xxx哈哈哈]",
})
handle.WaitUserReceive(func(diaryHandler spider.DiaryHandler, message *util.Message) {
task := message.Payload.(util.H)["task"]
content := message.Payload.(util.H)["info"]
log.Info("正在执行任务: ", task, content)
go util.MsgQueue.Push(&util.Message{
Type: "chat",
Payload: fmt.Sprintf("正在执行任务: %s ", task),
})
switch task {
case "地点":
diaryHandler["地点"].MustInput(content)
case "内容":
diaryHandler["内容"].MustInput(content)
case "文件":
diaryHandler["文件"].MustSetFiles(define.ValidImg)
case "取消":
diaryHandler["取消"].MustClick()
case "提交":
// dh["提交"].MustClick() // TODO: 会提交日志
case "截图":
go diaryHandler["截图"].MustScreenshot("实时截图")
}
time.Sleep(time.Second * 5)
})
return nil
})
// time.Sleep(11 * time.Hour) // TODO: 调试
defer func() {
if r := recover(); r != nil {
define.Log.Errorf("Recover %s", r)
Web.Browser.MustClose()
} else {
define.Log.Info("关闭成功")
Web.LogOutPage()
Web.Browser.MustClose()
}
defer func() {
util.Toask(util.H{
"data": "运行结束",
"title": define.Title,
"logo": define.NotifyImg,
})
}()
}()
}
func ParseTaskArgs(message any) *util.H {
if taskMeta := strings.Split(message.(string), "|"); len(taskMeta) != 2 {
return nil
} else {
t := &util.H{
"task": taskMeta[0],
"info": taskMeta[1],
}
return t
}
}
func handleTask(msg *WebSocketMessage) {
log.Infof("接收到参数: %s %s", msg.Payload, msg.Type)
if msg.Payload == "启动任务" {
go RegisterWorkTask()
util.MsgQueue.Push(&util.Message{
Type: "chat",
Payload: "任务已经成功启动!",
})
} else {
log.Info("开始解析参数: ", msg.Payload)
if payload := ParseTaskArgs(msg.Payload); payload != nil {
util.TaskQueue.Push(&util.Message{
Type: "receive",
Payload: *payload,
})
log.Info(payload)
} else {
util.TaskQueue.Push(&util.Message{
Type: "client",
})
}
}
}
func handlePing(wb *WebSocketMessage) {
// define.Log.Info("WebSocket write error:", wb)
}

82
backend/event/stream.go Normal file
View File

@ -0,0 +1,82 @@
package event
import (
"could-work/backend/core/define"
"could-work/backend/util"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
type WebSocketMessage struct {
Type string `json:"type"`
Payload string `json:"payload"`
}
func MonitorWS(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
define.Log.Error("WebSocket upgrade error:", err)
return
}
defer func(conn *websocket.Conn) {
_ = conn.Close()
}(conn)
// 定义心跳间隔
heartbeatInterval := 3 * time.Second
// 创建一个定时器
heartbeatTimer := time.NewTicker(heartbeatInterval)
for {
select {
case <-heartbeatTimer.C:
if err := conn.WriteJSON(WebSocketMessage{Type: "ping"}); err != nil {
define.Log.Error("WebSocket write error:", err)
return
}
default:
if !util.MsgQueue.IsEmpty() {
message := util.MsgQueue.Pop()
if message.Type != "ping" {
if err := conn.WriteJSON(&message); err != nil {
define.Log.Error("WebSocket write error:", err)
return
}
}
}
var receive *WebSocketMessage
if err := conn.ReadJSON(&receive); err != nil {
define.Log.Error("WebSocket message parsing error:", err)
continue
}
switch receive.Type {
case "ping":
handlePing(receive)
case "chat":
handleChat(receive)
case "task":
handleTask(receive)
default:
define.Log.Warn("Unknown message type:", receive.Type)
}
}
time.Sleep(time.Millisecond * 100)
}
}

180
backend/event/user.go Normal file
View File

@ -0,0 +1,180 @@
package event
import (
"crypto/md5"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
var db *gorm.DB
type User struct {
ID string `json:"user_id" gorm:"primaryKey"`
Username string `json:"username"`
Password string `json:"password"`
}
// Reply 响应
type Reply struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data any `json:"data"`
}
// MsgJson 响应数据
func MsgJson(c *gin.Context, reply *Reply) {
if reply.Code == 0 {
reply.Code = 200
}
c.JSON(http.StatusOK, reply)
}
func InitUserDB() {
var err error
db, err = gorm.Open(sqlite.Open("MUS.db"), &gorm.Config{})
if err != nil {
panic("Failed to connect database")
}
_ = db.AutoMigrate(&User{})
}
func loginUser(c *gin.Context) {
var userInfo struct {
Username string `json:"username"`
Password string `json:"password"`
}
c.GetHeader("")
if err := c.ShouldBindJSON(&userInfo); err != nil {
MsgJson(c, &Reply{
Code: 400,
Msg: err.Error(),
})
return
}
// 查找用户
var user User
if db.Where("username = ? AND password = ?", userInfo.Username, userInfo.Password).First(&user).Error != nil {
MsgJson(c, &Reply{
Code: 400,
Msg: "登录失败",
})
return
}
token, _ := SubScriber.GenToken(user.Username)
MsgJson(c, &Reply{
Msg: "登录成功",
Data: gin.H{"token": token, "user_id": user.ID},
})
}
func registerUser(c *gin.Context) {
var newUser User
if err := c.ShouldBindJSON(&newUser); err != nil {
MsgJson(c, &Reply{
Code: 400,
Msg: err.Error(),
})
return
}
// 检查用户名是否已经存在
var user User
if db.Where("username = ?", newUser.Username).First(&user).Error == nil {
MsgJson(c, &Reply{
Code: 400,
Msg: "用户已存在",
})
return
}
genMd5 := func(s string) string {
sum := md5.Sum([]byte(s))
return fmt.Sprintf("%x", sum)
}
// 为新用户生成唯一的 ID
newUser.ID = genMd5(newUser.Username)
// 创建用户记录
result := db.Create(&newUser)
if result.Error != nil {
MsgJson(c, &Reply{
Code: 400,
Msg: "用户注册失败",
})
return
}
// 返回成功响应
MsgJson(c, &Reply{
Msg: "用户注册成功",
Data: newUser.ID,
})
}
func deleteUser(c *gin.Context) {
// 获取用户 ID 参数
userID := c.Param("user_id")
// 删除用户记录
result := db.Delete(&User{}, userID)
if result.Error != nil {
MsgJson(c, &Reply{
Code: 400,
Msg: "用户不存在",
})
return
}
// 返回成功响应
MsgJson(c, &Reply{Msg: "用户删除成功"})
}
func updateUser(c *gin.Context) {
// 获取用户 ID 参数
userID := c.Param("user_id")
// 解析请求中的 JSON 数据,更新用户信息
var updatedUser User
if err := c.ShouldBindJSON(&updatedUser); err != nil {
MsgJson(c, &Reply{
Code: 400,
Msg: err.Error(),
})
return
}
// 更新用户记录
result := db.Model(&User{}).Where("user_id = ?", userID).Updates(updatedUser)
if result.Error != nil || result.RowsAffected == 0 {
MsgJson(c, &Reply{
Code: 400,
Msg: "用户不存在",
})
return
}
// 返回成功响应
MsgJson(c, &Reply{Msg: "用户更新成功"})
}
// func generateToken(username string) string {
// hashed := sha256.New()
// timestamp := strconv.Itoa(int(time.Now().Unix()))
// hashed.Write([]byte(username + timestamp))
// hashedBytes := hashed.Sum(nil)
// return hex.EncodeToString(hashedBytes)
// }

52
backend/util/quene.go Normal file
View File

@ -0,0 +1,52 @@
package util
import "sync"
// MessageQueue 定义消息队列结构体
type MessageQueue struct {
messages []*Message
mutex sync.Mutex
}
type Message struct {
Type string `json:"type"`
Payload any `json:"payload"`
}
// NewMessageQueue 初始化消息队列
func NewMessageQueue() *MessageQueue {
return &MessageQueue{
messages: make([]*Message, 0),
}
}
// IsEmpty 判断消息队列是否为空
func (mq *MessageQueue) IsEmpty() bool {
mq.mutex.Lock()
defer mq.mutex.Unlock()
return len(mq.messages) == 0
}
// Push 向消息队列中加入消息
func (mq *MessageQueue) Push(message *Message) {
mq.mutex.Lock()
defer mq.mutex.Unlock()
mq.messages = append(mq.messages, message)
}
// Pop 从消息队列中取出消息
func (mq *MessageQueue) Pop() *Message {
mq.mutex.Lock()
defer mq.mutex.Unlock()
if len(mq.messages) == 0 {
return nil
}
message := mq.messages[0]
mq.messages = mq.messages[1:]
return message
}
var MsgQueue = &MessageQueue{}
var TaskQueue = &MessageQueue{}

150
backend/util/util.go Normal file
View File

@ -0,0 +1,150 @@
package util
import (
"encoding/json"
"errors"
"fmt"
"os"
"github.com/electricbubble/go-toast"
"github.com/panjf2000/ants/v2"
)
type (
H map[string]string
Config struct { // NOTE: 配置
Username string `json:"username"`
Password string `json:"password"`
Proxy string `json:"proxy"`
Email Email `json:"email"`
HtmlURL HtmlURL `json:"html_url"`
OpenAI OpenAI `json:"open-ai"`
QBot QBot `json:"qq-bot"`
}
Email struct { // NOTE: 邮箱
Host string `json:"host"`
Sender string `json:"sender"`
Key string `json:"key"`
}
HtmlURL struct { // NOTE: html
BaseUrl string `json:"base_url"`
TokenUrl string `json:"token_url"`
CodeUrl string `json:"code_url"`
}
OpenAI struct { // NOTE: open-ai
Key string `json:"key"`
BaseUrl string `json:"url"`
}
QBot struct { // NOTE: q-bot
AppID uint64 `json:"appid"`
Token string `json:"token"`
}
Proxy struct {
HTTPProxy string `json:"http_proxy"`
HTTPSProxy string `json:"https_proxy"`
}
)
func InitConfig() (*Config, error) {
var (
f *os.File
err error
conf = &Config{}
confName = "config.json"
)
if FileExists(confName) {
f, err = os.Open(confName)
if err != nil {
return nil, fmt.Errorf("failed to open config file: %w", err)
}
err = json.NewDecoder(f).Decode(&conf)
if err != nil {
return nil, fmt.Errorf("failed to decode config file: %w", err)
}
} else {
f, err = os.Create(confName)
if err != nil {
return nil, fmt.Errorf("failed to create config file: %w", err)
}
err = json.NewEncoder(f).Encode(&Config{})
if err != nil {
return nil, fmt.Errorf("failed to write config file: %w", err)
} else {
os.Exit(0)
}
}
if conf.Proxy != "" {
SetProxy(&Proxy{
HTTPProxy: conf.Proxy,
HTTPSProxy: conf.Proxy,
})
}
defer func(f *os.File) {
_ = f.Close()
}(f)
return conf, nil
}
func FileExists(filename string) bool {
_, err := os.Stat(filename)
if err == nil {
return true
}
if errors.Is(err, os.ErrNotExist) {
return false
}
return false
}
func TaskRunner(tasks ...func()) {
pool, _ := ants.NewPool(len(tasks))
for _, t := range tasks {
_ = pool.Submit(t)
}
defer pool.Release()
}
func SetProxy(config *Proxy) {
if config.HTTPProxy != "" {
_ = os.Setenv("HTTP_PROXY", config.HTTPProxy)
} else {
_ = os.Unsetenv("HTTP_PROXY")
}
if config.HTTPSProxy != "" {
_ = os.Setenv("HTTPS_PROXY", config.HTTPSProxy)
} else {
_ = os.Unsetenv("HTTPS_PROXY")
}
}
func Toask(msg H) {
_ = toast.Push(
msg["data"],
toast.WithIcon(msg["logo"]),
toast.WithTitle(msg["title"]),
toast.WithAppID(msg["app_id"]),
toast.WithProtocolAction("🎉 Finished"),
)
}

23
config.json Normal file
View File

@ -0,0 +1,23 @@
{
"username": "202127530334",
"password": "Lyhqyh99@",
"proxy": "http://localhost:7890",
"email": {
"host": "",
"sender": "",
"key": ""
},
"html_url": {
"base_url": "https://jy.hniu.cn/login",
"token_url": "http://localhost:20000/api/v1/login",
"code_url": "http://localhost:20000/api/v1/verify-code"
},
"open-ai": {
"key": "sk-cE4uiHEWgrmOyD1XCe651276FbF840EbB2A956538fE440B6",
"url": "https://api.aigcbest.top/v1"
},
"qq-bot": {
"appid": 102078287,
"token": "67o6t92E2hGLOlYPAKtLJ79ug0hyUhbq"
}
}

13
frontend/index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>cloud-work-ui</title>
</head>
<body style="--wails-draggable:drag">
<div id="root"></div>
<script src="./src/main.jsx" type="module"></script>
</body>
</html>

5062
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

38
frontend/package.json Normal file
View File

@ -0,0 +1,38 @@
{
"name": "frontend",
"private": true,
"version": "5.2.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@codemirror/lang-markdown": "^6.2.3",
"@uiw/react-codemirror": "^4.21.21",
"antd": "^5.12.1",
"axios": "^1.6.2",
"dotenv": "^16.3.1",
"html2canvas": "^1.4.1",
"jinrishici": "^1.0.6",
"localforage": "^1.10.0",
"match-sorter": "^6.3.1",
"node-fetch": "^3.3.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-monaco-editor": "^0.54.0",
"react-router-dom": "^6.20.1",
"sort-by": "^1.2.0"
},
"devDependencies": {
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@vitejs/plugin-react": "2.2.0",
"eslint": "^8.53.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4"
}
}

View File

@ -0,0 +1 @@
900286a9a6401086e100e3f441b23a56

4505
frontend/pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

BIN
frontend/src/assets/404.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

BIN
frontend/src/assets/ct.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,28 @@
import React, { useState } from 'react'
import { Button } from 'antd'
const AnimatedButton = ({ buttonText, buttonType, onClick, showFlag }) => {
const [isHovered, setIsHovered] = useState(false)
return (
<Button
block
shape="round"
type={buttonType}
htmlType="submit"
style={{
flex: 1,
marginRight: '8px',
}}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onClick={onClick}
>
{buttonText}
{isHovered && showFlag ? '🥕' : ''}
</Button>
)
}
export default AnimatedButton

View File

@ -0,0 +1,71 @@
import React from 'react'
import {Button, Form, Input, message, Space} from 'antd'
const ChangeConfig = ({onClose}) => {
const [form] = Form.useForm()
const onFinish = async (values) => {
console.log(values)
message.success('Submit success!')
onClose() //
}
const onFinishFailed = async () => {
message.error('Submit failed!')
}
const onFill = () => {
form.setFieldsValue({
url: 'https://api.aigcbest.top/',
})
}
return (
<Form
form={form}
layout="vertical"
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="代理地址"
name="url"
rules={[
{required: false},
{type: 'url', initialValues: true},
{type: 'string'}
]}
>
<Input
allowClear
defaultValue="https://api.aigcbest.top/"
/>
</Form.Item>
<Form.Item
label="API KEY"
name='api_key'
rules={[{required: true}, {type: 'string'}]}
>
<Input.Password
allowClear
/>
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit">
Submit
</Button>
<Button htmlType="button" onClick={onFill}>
Fill
</Button>
</Space>
</Form.Item>
</Form>
)
}
export default ChangeConfig

View File

@ -0,0 +1,269 @@
import React, {useEffect, useRef, useState} from 'react'
import {Avatar, Button, Input, List, message, Modal} from 'antd'
import {CheckCircleTwoTone, CloseCircleFilled, CloseCircleTwoTone} from '@ant-design/icons'
import html2canvas from 'html2canvas'
import ChangeConfig from "../components/ChatConfig"
import WS from "../shared/stream.js"
import '../css/ChatWindow.css'
import {SDB} from "../shared/token.js"
const {TextArea} = Input
const ChatWindow = ({onClose, onDisconnect}) => {
const [messages, setMessages] = useState([])
const [inputMessage, setInputMessage] = useState('')
const [connected, setConnected] = useState(true)
const [isModalVisible, setIsModalVisible] = useState(false)
const [contextMenu, setContextMenu] = useState({visible: false, x: 0, y: 0})
const messageContainerRef = useRef(null)
//
const scrollToBottom = () => {
if (messageContainerRef.current) {
messageContainerRef.current.scrollTop = messageContainerRef.current.scrollHeight
}
}
//
const handleContextMenu = (event) => {
event.preventDefault()
setContextMenu({
visible: true,
x: event.clientX,
y: event.clientY
})
}
//
const closeContextMenu = () => {
setContextMenu({visible: false, x: 0, y: 0})
}
const showChangeAIDialog = () => {
setIsModalVisible(true)
}
const handleCancel = () => {
setIsModalVisible(false)
}
const changeAI = () => {
closeContextMenu()
showChangeAIDialog()
}
const clearChat = () => {
closeContextMenu()
setMessages([])
SDB.set('chatMessages', JSON.stringify([]))
}
const exportChatAsImage = () => {
closeContextMenu()
html2canvas(document.querySelector(".ant-list-items")).then(canvas => {
//
const image = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream")
const link = document.createElement('a')
link.download = 'chat.png'
link.href = image
link.click()
})
}
useEffect(() => {
const handleDocumentClick = (event) => {
//
if (contextMenu.visible && !event.target.closest('.context-menu')) {
closeContextMenu()
}
}
//
document.addEventListener('click', handleDocumentClick)
//
return () => {
document.removeEventListener('click', handleDocumentClick)
}
}, [contextMenu.visible])
useEffect(() => {
// localStorage
const savedMessages = SDB.get('chatMessages')
if (savedMessages) {
setMessages(JSON.parse(savedMessages))
}
}, [])
useEffect(() => {
//
WS.setChatCallback((msg) => handleAgentMsg(msg))
WS.setTaskCallback((msg) => handleAgentMsg(msg))
//
WS.setLogCallback(() => setConnected(false))
}, [onDisconnect, messages])
const handleUserSendMsg = () => {
if (inputMessage.trim() !== '') {
const newMessage = {
text: inputMessage,
fromUser: true,
}
const newMessages = [...messages, newMessage]
setMessages(newMessages)
SDB.set('chatMessages', JSON.stringify(newMessages)) // 使SimpleDB
setInputMessage('')
ParseSendMessage(inputMessage)
}
}
//
const handleAgentMsg = (msg) => {
const chatMsg = {
text: msg,
fromUser: false,
}
const newMessages = [...messages, chatMsg]
setMessages(newMessages)
SDB.set('chatMessages', JSON.stringify(newMessages)) // 使SimpleDB
setTimeout(() => {
scrollToBottom() //
}, 0)
}
const handleKeyDown = (e) => {
if (e.key === 'Enter' && e.ctrlKey) {
handleUserSendMsg()
scrollToBottom()
}
}
const copyMessageToClipboard = async (text) => {
try {
await navigator.clipboard.writeText(text)
message.success('消息已复制')
} catch (err) {
console.error('复制失败', err)
}
}
let chatName = 'Kbot'
return (
<div className="chat-window">
<div className="chat-header">
<div style={{flex: 1}}>
<Avatar className="avatar-ct" size={64}/>
</div>
<div style={{flex: 3}}>
<span className="chat-username">{chatName}</span>
<ConStatus flag={connected}/>
</div>
<Button type='default' onClick={onClose} icon={<CloseCircleFilled/>}/>
</div>
<div className="message-container" ref={messageContainerRef} onContextMenu={handleContextMenu}>
<List
dataSource={messages}
renderItem={(message, index) => (
<List.Item
key={index}
className={message.fromUser ? 'user' : 'agent'}
onDoubleClick={() => copyMessageToClipboard(message.text)} //
>
<div className='chat-line-box-style'>
{message.text}
</div>
</List.Item>
)}
/>
</div>
<div className="chat-input">
<TextArea
placeholder="输入你的消息..."
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
onKeyDown={handleKeyDown}
autoSize={{minRows: 1, maxRows: 6}}
/>
<Button
type="primary"
shape='round'
onClick={handleUserSendMsg}
disabled={inputMessage.trim() === ''}
className={inputMessage.trim() !== '' ? 'btn-active' : ''}
>
发送
</Button>
{/* 右键菜单 */}
<div
className={`context-menu ${contextMenu.visible ? 'active' : ''}`}
style={{top: contextMenu.y, left: contextMenu.x}}
onClick={closeContextMenu}
>
<div className="context-menu-item" onClick={changeAI}>更换GPT</div>
<div className="context-menu-item" onClick={clearChat}>清空聊天</div>
<div className="context-menu-item" onClick={exportChatAsImage}>导出图片</div>
</div>
</div>
{/* 更换AI的模态框 */}
<Modal
title="更换AI配置"
style={{textAlign: "center"}}
open={isModalVisible}
onCancel={handleCancel}
footer={null}
>
<ChangeConfig onClose={handleCancel}/>
</Modal>
</div>
)
}
const ConStatus = ({flag}) => {
return flag ? (
<span className="chat-status">
<CheckCircleTwoTone twoToneColor="#52c41a"/> 在线
</span>
) : (
<span className="chat-status">
<CloseCircleTwoTone twoToneColor="#eb2f96"/> 不在线
</span>
)
}
const ParseSendMessage = (message) => {
let sendMsg = {
type: 'chat',
payload: message,
}
let extract = (str) => {
const regex = /\/task\s+(.*)/
const matches = regex.exec(str);
return matches && matches.length > 1 ? matches[1] : null
}
let info = extract(message)
if (info !== null) {
console.log(info)
sendMsg.type = 'task'
sendMsg.payload = info
}else{
sendMsg.payload = message
}
//
WS.socket.send(JSON.stringify(sendMsg))
}
export default ChatWindow

View File

@ -0,0 +1,53 @@
import React, {useState} from 'react'
import {Avatar, Popover} from 'antd'
import ChatWindow from './ChatWindow'
import '../css/ChatWindow.css'
const FloatingChatButton = () => {
const [chatVisible, setChatVisible] = useState(false)
const [connected, setConnected] = useState(false)
const handleButtonClick = () => {
setChatVisible(!chatVisible)
}
const chatFloatStyle = {
right: 40,
bottom: 30,
position: 'fixed',
}
const dotStyle = {
bottom: 30,
width: '15x',
height: '15px',
position: 'fixed',
borderRadius: '50%',
display: 'inline-block',
backgroundColor: connected ? 'red' : 'green',
}
const buttonContent = (
<Popover>
<div style={chatFloatStyle}>
<Avatar
className="avatar"
size={64}
onClick={handleButtonClick}
/>
<div style={dotStyle}></div>
{chatVisible && (
<ChatWindow
onClose={() => setChatVisible(false)}
onConnect={() => setConnected(true)}
onDisconnect={() => setConnected(false)}
/>
)}
</div>
</Popover>
)
return <div>{buttonContent}</div>
}
export default FloatingChatButton

View File

@ -0,0 +1,252 @@
/* ChatWindow.css */
/* 设置聊天窗口的样式,包括位置、大小、边框、圆角、阴影等 */
.chat-window {
bottom: 75%;
/* 距离底部75%的位置 */
right: 50%;
/* 距离右侧50%的位置 */
width: 400px;
/* 宽度为400像素 */
border: 1px solid #ccc;
/* 边框为1像素实线颜色为#ccc */
border-radius: 4px;
/* 圆角半径为4像素 */
margin: 20px auto;
/* 上下边距为20像素左右居中 */
font-family: Arial, sans-serif;
/* 字体为Arial或无衬线字体 */
position: absolute;
/* 绝对定位 */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
/* 盒子阴影效果 */
}
/* 设置聊天头部的样式,包括布局、背景颜色、圆角 */
.chat-header {
display: flex;
/* 使用弹性盒子布局 */
align-items: center;
/* 垂直居中对齐项目 */
padding: 10px;
/* 内填充为16像素 */
background-color: #f5f5f5;
/* 背景颜色 */
border-top-left-radius: 4px;
/* 左上角圆角 */
border-top-right-radius: 4px;
/* 右上角圆角 */
}
/* 设置用户名显示样式 */
.chat-username {
margin-left: 10px;
/* 左边距为10像素 */
font-weight: bold;
/* 字体加粗 */
font-size: 18px;
/* 字体大小为18像素 */
}
/* 设置在线状态的样式 */
.chat-status {
margin-left: 5px;
/* 左边距为5像素 */
color: green;
/* 文字颜色为绿色 */
}
/* 设置消息容器的样式,限制高度和启用滚动条 */
.message-container {
max-height: 300px;
/* 最大高度为300像素超过则显示滚动条 */
overflow-y: auto;
/* 启用垂直滚动条 */
flex-direction: column;
/* 子项垂直排列 */
}
/* 设置滚动条的样式 */
.message-container::-webkit-scrollbar {
width: 8px;
/* 滚动条宽度为8像素 */
}
/* 设置滚动条轨道的样式 */
.message-container::-webkit-scrollbar-track {
background: #f1f1f1;
/* 轨道背景颜色 */
}
/* 设置滚动条滑块的样式 */
.message-container::-webkit-scrollbar-thumb {
background: #888;
/* 滑块颜色 */
border-radius: 4px;
/* 滑块圆角 */
}
/* 设置滚动条鼠标悬停时滑块的样式 */
.message-container::-webkit-scrollbar-thumb:hover {
background: #555;
/* 滑块悬停颜色 */
}
/* 覆盖原有Ant Design的列表项样式 */
:where(.css-dev-only-do-not-override-6j9yrn).ant-list .ant-list-item {
display: flex;
/* 使用弹性盒子布局 */
align-items: center;
/* 垂直居中对齐 */
justify-content: space-between;
/* 两端对齐,项目之间的间隔都相等 */
padding: 0 0;
/* 内填充设为0 */
color: rgba(0, 0, 0, 0.88);
/* 字体颜色 */
}
/* 设置用户和代理消息框的公共样式 */
.user,
.agent {
border-radius: 5px;
/* 圆角为5像素 */
margin: 5px 15px;
/* 边距为上下5像素左右15像素 */
width: fit-content;
/* 宽度根据内容调整 */
display: inline-block;
/* 内联块级布局 */
}
/* 设置用户消息框的特定样式 */
.user {
background-color: #e2f7fe;
/* 背景颜色 */
margin-left: auto;
/* 自动左边距,使其靠右 */
}
/* 设置代理消息框的特定样式 */
.agent {
background-color: #d3ffd3;
/* 背景颜色 */
margin-right: auto;
/* 自动右边距,使其靠左 */
}
/* 设置聊天行框的最小宽度和边距 */
.chat-line-box-style {
min-width: 20px;
/* 最小宽度为20像素 */
margin: 5px;
/* 边距为5像素 */
user-select: none;
/* 不允许用户选择 */
}
/* 设置聊天输入区的样式 */
.chat-input {
display: flex;
/* 使用弹性盒子布局 */
align-items: center;
/* 垂直居中对齐项目 */
padding: 16px;
/* 内填充为16像素 */
border-top: 1px solid #ccc;
/* 上边框为1像素实线颜色为#ccc */
}
/* 设置Ant Design输入框的样式 */
.ant-input {
flex: 1;
/* 占据剩余空间 */
margin-right: 16px;
/* 右边距为16像素 */
}
/* 设置Ant Design按钮的宽度 */
.ant-btn {
width: 80px;
/* 宽度为80像素 */
}
/* 设置用户头像的样式 */
.avatar {
width: 64px;
/* 宽度为64像素 */
height: 64px;
/* 高度为64像素 */
cursor: pointer;
/* 鼠标悬停时为指针形状 */
background-image: url(../assets/chatlogo.jpg);
/* 背景图片 */
background-size: cover;
/* 背景图片覆盖整个元素 */
}
/* 设置代理头像的样式 */
.avatar-ct {
width: 64px;
/* 宽度为64像素 */
height: 64px;
/* 高度为64像素 */
cursor: pointer;
/* 鼠标悬停时为指针形状 */
background-image: url(../assets/ct.jpg);
/* 背景图片 */
background-size: cover;
/* 背景图片覆盖整个元素 */
}
/* 当输入框非空时按钮的背景颜色 */
.btn-active {
background-image: linear-gradient(to right, rgb(60, 160, 37) 50%, green 50%);
background-size: 200% 100%;
}
/* 设置鼠标悬停时按钮的背景颜色过渡效果 */
.ant-btn-primary:hover {
background-position: left bottom;
}
/* 右键菜单样式 */
.context-menu {
position: fixed;
z-index: 1000;
width: 100px;
background: white;
border: 1px solid #ccc;
border-radius: 8px;
/* 增加圆角 */
box-shadow: 0 2px 5px rgba(80, 74, 74, 0.2);
display: none;
overflow: hidden;
/* 确保子项不超出圆角边界 */
}
.context-menu.active {
display: block;
}
/* 右键菜单项样式 */
.context-menu-item {
padding: 10px 15px;
text-align: center;
/* 文字居中 */
font-size: smaller;
cursor: pointer;
transition: background-color 0.3s;
}
/* 鼠标悬停在菜单项上时的样式 */
.context-menu-item:hover {
background-color: #f5f5f5;
}
/* 菜单项之间的分割线 */
.context-menu-item:not(:last-child) {
border-bottom: 1px solid #ddd;
/* 分割线 */
}

View File

@ -0,0 +1,57 @@
/* NotFoundPage.css */
.not-body {
display: flex;
width: 100vw;
height: 100vh;
justify-content: center;
/* 水平居中 */
align-items: center;
/* 垂直居中 */
background-size: cover;
background-image: url(../assets/404.jpg);
}
.not-found-container {
text-align: center;
font-family: Arial, sans-serif;
}
.not-found-description {
font-size: 1.2rem;
color: #333;
margin-bottom: 20px;
}
.not-found-description {
font-size: 1.2rem;
color: #333;
margin-bottom: 20px;
}
.not-found-text {
font-size: 3rem;
font-weight: bold;
color: #ff5722;
/* 橙色 */
margin-bottom: 10px;
}
.back-to-home {
display: inline-block;
padding: 10px 20px;
background-color: #007bff;
/* 蓝色 */
color: #fff;
/* 白色 */
text-decoration: none;
font-size: 1.2rem;
border-radius: 5px;
transition: background-color 0.3s ease-in-out;
}
.back-to-home:hover {
background-color: #0056b3;
/* 鼠标悬停时改变背景颜色 */
}

View File

@ -0,0 +1,19 @@
/* avatar.css */
.login-bk {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
background-image: url(../assets/background.jpg);
background-size: cover;
background-position: center;
}
.card-bk {
border-radius: 25px;
backdrop-filter: blur(3px);
-webkit-backdrop-filter: blur(3px);
background: rgba(255, 255, 255, 0.5);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

71
frontend/src/css/demo.css Normal file
View File

@ -0,0 +1,71 @@
/* demo.css */
/* 设置卡片后的界面 */
.login-div {
padding: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-size: cover;
background-position: center;
background-image: url("../assets/background.jpg");
}
/* 设置登录卡片框 */
.login-card {
border-radius: 30px;
backdrop-filter: blur(3px);
-webkit-backdrop-filter: blur(3px);
background: rgba(255, 255, 255, 0.5);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
/* 头部展示 */
.base-header-div {
height: 64px;
padding: 0 50px;
line-height: 64px;
background: #f5f5f5;
}
/* 头部展示古诗词 */
.base-header-div img {
display: flex;
align-content: center;
margin: auto;
}
.running-log {
height: 50%;
display: flex;
justify-content: center;
align-items: center;
}
/* 日志容器 */
.running-log #running-info {
margin: 40px;
width: 100px;
}
/* 配置容器 */
.config-container {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-size: cover;
background-image: url("../assets/404.jpg");
:is(Button) {
margin: 20px;
}
}
.index-container {
justify-content: center;
text-align: center;
margin-top: 20%;
}

414
frontend/src/css/index.css Normal file
View File

@ -0,0 +1,414 @@
html {
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
html,
body {
height: 100%;
margin: 0;
line-height: 1.5;
color: #121212;
}
textarea,
input,
button {
font-size: 1rem;
font-family: inherit;
border: none;
border-radius: 8px;
padding: 0.5rem 0.75rem;
box-shadow: 0 0px 1px hsla(0, 0%, 0%, 0.2), 0 1px 2px hsla(0, 0%, 0%, 0.2);
background-color: white;
line-height: 1.5;
margin: 0;
}
button {
color: #3992ff;
font-weight: 500;
}
textarea:hover,
input:hover,
button:hover {
box-shadow: 0 0px 1px hsla(0, 0%, 0%, 0.6), 0 1px 2px hsla(0, 0%, 0%, 0.2);
}
button:active {
box-shadow: 0 0px 1px hsla(0, 0%, 0%, 0.4);
transform: translateY(1px);
}
#contact h1 {
display: flex;
align-items: flex-start;
gap: 1rem;
}
#contact h1 form {
display: flex;
align-items: center;
margin-top: 0.25rem;
}
#contact h1 form button {
box-shadow: none;
font-size: 1.5rem;
font-weight: 400;
padding: 0;
}
#contact h1 form button[value='true'] {
color: #a4a4a4;
}
#contact h1 form button[value='true']:hover,
#contact h1 form button[value='false'] {
color: #eeb004;
}
form[action$='destroy'] button {
color: #f44250;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
#root {
display: flex;
/* flex-direction: row; */
height: 100%;
/* width: 100%; */
margin: 0;
padding: 0;
overflow: hidden;
}
:root {
--sidebar-width: 22rem;
/* 侧边栏宽度变量 */
}
#sidebar {
background-color: #f7f7f7;
border-right: solid 1px #e3e3e3;
display: flex;
flex-direction: column;
width: var(--sidebar-width);
transition: transform 0.3s ease-in-out;
}
/* 实现了点击后隐藏侧边栏 */
#sidebar.collapsed {
transform: translateX(-100%);
/* 隐藏sidebar */
}
/* 当sidebar隐藏时#detail 占据所有可用空间 */
#root #sidebar.collapsed+#detail {
margin-left: 0;
width: 100%;
}
#sidebar>* {
padding-left: 2rem;
padding-right: 2rem;
}
#sidebar h1 #goHome {
margin-left: auto;
}
#sidebar h1 {
font-size: 1rem;
font-weight: 500;
display: flex;
align-items: center;
margin: 0;
padding: 1rem 2rem;
border-top: 1px solid #e3e3e3;
order: 1;
line-height: 1;
}
#sidebar h1::before {
content: url("data:image/svg+xml,%3Csvg width='25' height='18' viewBox='0 0 25 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M19.4127 6.4904C18.6984 6.26581 18.3295 6.34153 17.5802 6.25965C16.4219 6.13331 15.9604 5.68062 15.7646 4.51554C15.6551 3.86516 15.7844 2.9129 15.5048 2.32334C14.9699 1.19921 13.7183 0.695046 12.461 0.982805C11.3994 1.22611 10.516 2.28708 10.4671 3.37612C10.4112 4.61957 11.1197 5.68054 12.3363 6.04667C12.9143 6.22097 13.5284 6.3087 14.132 6.35315C15.2391 6.43386 15.3241 7.04923 15.6236 7.55574C15.8124 7.87508 15.9954 8.18975 15.9954 9.14193C15.9954 10.0941 15.8112 10.4088 15.6236 10.7281C15.3241 11.2334 14.9547 11.5645 13.8477 11.6464C13.244 11.6908 12.6288 11.7786 12.0519 11.9528C10.8353 12.3201 10.1268 13.3799 10.1828 14.6234C10.2317 15.7124 11.115 16.7734 12.1766 17.0167C13.434 17.3056 14.6855 16.8003 15.2204 15.6762C15.5013 15.0866 15.6551 14.4187 15.7646 13.7683C15.9616 12.6032 16.423 12.1505 17.5802 12.0242C18.3295 11.9423 19.1049 12.0242 19.8071 11.6253C20.5491 11.0832 21.212 10.2696 21.212 9.14192C21.212 8.01428 20.4976 6.83197 19.4127 6.4904Z' fill='%23F44250'/%3E%3Cpath d='M7.59953 11.7459C6.12615 11.7459 4.92432 10.5547 4.92432 9.09441C4.92432 7.63407 6.12615 6.44287 7.59953 6.44287C9.0729 6.44287 10.2747 7.63407 10.2747 9.09441C10.2747 10.5536 9.07172 11.7459 7.59953 11.7459Z' fill='black'/%3E%3Cpath d='M2.64217 17.0965C1.18419 17.093 -0.0034949 15.8971 7.72743e-06 14.4356C0.00352588 12.9765 1.1994 11.7888 2.66089 11.7935C4.12004 11.797 5.30772 12.9929 5.30306 14.4544C5.29953 15.9123 4.10366 17.1 2.64217 17.0965Z' fill='black'/%3E%3Cpath d='M22.3677 17.0965C20.9051 17.1046 19.7046 15.9217 19.6963 14.4649C19.6882 13.0023 20.8712 11.8017 22.3279 11.7935C23.7906 11.7854 24.9911 12.9683 24.9993 14.4251C25.0075 15.8866 23.8245 17.0883 22.3677 17.0965Z' fill='black'/%3E%3C/svg%3E%0A");
margin-right: 0.5rem;
position: relative;
top: 1px;
}
#sidebar>div {
display: flex;
align-items: center;
gap: 0.5rem;
padding-top: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid #e3e3e3;
}
#sidebar>div form {
position: relative;
}
#sidebar>div form input[type='search'] {
width: 100%;
padding-left: 2rem;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='%23999' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z' /%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: 0.625rem 0.75rem;
background-size: 1rem;
position: relative;
}
#sidebar>div form input[type='search'].loading {
background-image: none;
}
#search-spinner {
width: 1rem;
height: 1rem;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'%3E%3Cpath stroke='%23000' strokeLinecap='round' strokeLinejoin='round' strokeWidth='2' d='M20 4v5h-.582m0 0a8.001 8.001 0 00-15.356 2m15.356-2H15M4 20v-5h.581m0 0a8.003 8.003 0 0015.357-2M4.581 15H9' /%3E%3C/svg%3E");
animation: spin 1s infinite linear;
position: absolute;
left: 0.625rem;
top: 0.75rem;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
#sidebar nav {
flex: 1;
overflow: auto;
padding-top: 1rem;
}
#sidebar nav a span {
float: right;
color: #eeb004;
}
#sidebar nav a.active span {
color: inherit;
}
i {
color: #818181;
}
#sidebar nav .active i {
color: inherit;
}
#sidebar ul {
padding: 0;
margin: 0;
list-style: none;
}
#sidebar li {
margin: 0.25rem 0;
}
#sidebar nav a {
display: flex;
align-items: center;
justify-content: space-between;
overflow: hidden;
white-space: pre;
padding: 0.5rem;
border-radius: 8px;
color: inherit;
text-decoration: none;
gap: 1rem;
}
#sidebar nav a:hover {
background: #e3e3e3;
}
#sidebar nav a.active {
background: hsl(224, 98%, 58%);
color: white;
}
#sidebar nav a.pending {
color: hsl(224, 98%, 58%);
}
#detail {
flex-grow: 1;
/* 允许 #detail 占据所有剩余空间 */
transition: margin-left 0.3s ease-in-out, width 0.3s ease-in-out;
/* 平滑的过渡效果 */
}
/* 按钮样式,确保它总是可见 */
.toggle-sidebar-btn {
position: fixed;
top: 10px;
z-index: 100;
}
#detail.loading {
opacity: 0.25;
transition: opacity 200ms;
transition-delay: 200ms;
}
#contact {
max-width: 40rem;
display: flex;
}
#contact h1 {
font-size: 2rem;
font-weight: 700;
margin: 0;
line-height: 1.2;
}
#contact h1+p {
margin: 0;
}
#contact h1+p+p {
white-space: break-spaces;
}
#contact h1:focus {
outline: none;
color: hsl(224, 98%, 58%);
}
#contact a[href*='twitter'] {
display: flex;
font-size: 1.5rem;
color: #3992ff;
text-decoration: none;
}
#contact a[href*='twitter']:hover {
text-decoration: underline;
}
#contact img {
width: 12rem;
height: 12rem;
background: #c8c8c8;
margin-right: 2rem;
border-radius: 1.5rem;
object-fit: cover;
}
#contact h1~div {
display: flex;
gap: 0.5rem;
margin: 1rem 0;
}
#contact-form {
display: flex;
max-width: 40rem;
flex-direction: column;
gap: 1rem;
}
#contact-form>p:first-child {
margin: 0;
padding: 0;
}
#contact-form>p:first-child> :nth-child(2) {
margin-right: 1rem;
}
#contact-form>p:first-child,
#contact-form label {
display: flex;
}
#contact-form p:first-child span,
#contact-form label span {
width: 8rem;
}
#contact-form p:first-child input,
#contact-form label input,
#contact-form label textarea {
flex-grow: 2;
}
#contact-form-avatar {
margin-right: 2rem;
}
#contact-form-avatar img {
width: 12rem;
height: 12rem;
background: hsla(0, 0%, 0%, 0.2);
border-radius: 1rem;
}
#contact-form-avatar input {
box-sizing: border-box;
width: 100%;
}
#contact-form p:last-child {
display: flex;
gap: 0.5rem;
margin: 0 0 0 8rem;
}
#contact-form p:last-child button[type='button'] {
color: inherit;
}
#zero-state {
margin: 2rem auto;
text-align: center;
color: #818181;
}
#error-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
}

36
frontend/src/main.jsx Normal file
View File

@ -0,0 +1,36 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import {createBrowserRouter, RouterProvider} from 'react-router-dom'
import './css/index.css'
import RouteGuard from "./routes/guard/guard.jsx"
import ManageLayout from './routes/base/BasePage'
import LoginPage from "./routes/base/LoginPage.jsx";
import NotFoundPage from "./routes/guard/404.jsx";
const router = createBrowserRouter([
{
path: "/",
element: <RouteGuard/>,
children: [
{
path: "/",
element: <ManageLayout/>,
},
]
},
{
path: "/login",
element: <LoginPage/>,
},
{
path: "*",
element: <NotFoundPage/>,
},
]);
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<RouterProvider router={router}/>
</React.StrictMode>
)

View File

@ -0,0 +1,70 @@
import React, {useMemo, useState} from 'react';
import {CloudFilled, MenuFoldOutlined, MenuUnfoldOutlined, RobotOutlined, SettingFilled} from '@ant-design/icons';
import {Button, Layout, Menu} from 'antd';
import '../../css/demo.css'
import TaskPage from "./TaskPage.jsx";
import CodeEditor from "./CodePage.jsx";
import ConfigPage from "./ConfigPage.jsx";
import Index from "./IndexPage.jsx";
const {Header, Sider, Content} = Layout;
const menuItems = [
{key: '1', icon: <SettingFilled/>, label: '主页'},
{key: '2', icon: <RobotOutlined/>, label: '任务集'},
{key: '3', icon: <CloudFilled/>, label: '编辑器'},
{key: '4', icon: <SettingFilled/>, label: '配置'},
];
const ManageLayout = () => {
const [collapsed, setCollapsed] = useState(false);
const [selectedMenuItem, setSelectedMenuItem] = useState('1');
const renderContent = useMemo(() => {
switch (selectedMenuItem) {
case '1':
return <Index/>
case '2':
return <TaskPage/>;
case '3':
return <CodeEditor/>;
case '4':
return <ConfigPage/>;
default:
return <div>请选择一个菜单项</div>;
}
}, [selectedMenuItem]);
return (
<Layout>
<Sider trigger={null} collapsible collapsed={collapsed}>
<Menu
theme="dark"
mode="vertical"
defaultSelectedKeys={['1']}
items={menuItems.map(item => ({
...item,
onClick: () => setSelectedMenuItem(item.key),
}))}
/>
</Sider>
<Layout>
<Header className='base-header-div' style={{padding: 0}}>
<Button
type="text"
icon={collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>}
onClick={() => setCollapsed(!collapsed)}
style={{fontSize: '16px', width: 32, height: 32}}
/>
<img src='https://v2.jinrishici.com/one.svg' alt='loading...'/>
</Header>
<Content style={{margin: '24px 16px', minHeight: 280}}>
{renderContent}
</Content>
</Layout>
</Layout>
);
}
export default ManageLayout;

View File

@ -0,0 +1,47 @@
import React from 'react';
import MonacoEditor from 'react-monaco-editor';
class CodeEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
code: '// write your code...',
};
//
this.editorDidMount = this.editorDidMount.bind(this);
this.onChange = this.onChange.bind(this);
}
editorDidMount(editor, monaco) {
// console.log('editorDidMount', editor);
editor.focus();
}
onChange(newValue, e) {
// console.log('onChange', newValue, e);
this.setState({code: newValue});
}
render() {
const {code} = this.state;
const options = {
selectOnLineNumbers: true
};
return (
<MonacoEditor
width="100%"
height="100%" //
language="golang"
theme="vs-dark"
value={code}
options={options}
onChange={this.onChange}
editorDidMount={this.editorDidMount}
/>
);
}
}
export default CodeEditor;

View File

@ -0,0 +1,131 @@
import React, {useState} from 'react';
import {Button, Form, Input, Modal} from 'antd';
import '../../css/demo.css'
const SectionFormModal = ({sectionName, visible, onCreate, onCancel}) => {
const [form] = Form.useForm();
//
const renderFormFields = (section) => {
switch (section) {
case 'Email':
return (
<>
<Form.Item name={['email', 'host']} label="主机" rules={[{required: true}]}>
<Input/>
</Form.Item>
<Form.Item name={['email', 'sender']} label="发送者" rules={[{required: true}]}>
<Input/>
</Form.Item>
<Form.Item name={['email', 'key']} label="秘钥" rules={[{required: true}]}>
<Input/>
</Form.Item>
</>
);
case 'HTML URL':
return (
<>
<Form.Item name={['html_url', 'base_url']} label="就业平台地址" rules={[{required: true}]}>
<Input/>
</Form.Item>
<Form.Item name={['html_url', 'token_url']} label="验证码 Token 地址"
rules={[{required: true}]}>
<Input/>
</Form.Item>
<Form.Item name={['html_url', 'code_url']} label="验证码识别地址" rules={[{required: true}]}>
<Input/>
</Form.Item>
</>
);
case 'Open-AI':
return (
<>
<Form.Item name={['open_ai', 'url']} label="代理地址">
<Input
allowClear
defaultValue="https://api.aigcbest.top/"/>
</Form.Item>
<Form.Item name={['open_ai', 'key']} label="API Key" rules={[{required: true}]}>
<Input.Password/>
</Form.Item>
</>
);
case 'QQ-Bot':
return (
<>
<Form.Item name={['qq_bot', 'appid']} label="AppID" rules={[{required: true}]}>
<Input.Password/>
</Form.Item>
<Form.Item name={['qq_bot', 'token']} label="Token" rules={[{required: true}]}>
<Input.Password/>
</Form.Item>
</>
);
default:
return null;
}
};
return (
<Modal
open={visible}
style={{textAlign: "center"}}
title={sectionName}
okText="提交"
cancelText="取消"
onCancel={onCancel}
onOk={() => {
form.validateFields()
.then((values) => {
form.resetFields();
onCreate(values);
})
.catch((info) => {
console.log('Validate Failed:', info);
});
}}
>
<Form
form={form}
layout="vertical"
name={`form_in_modal_${sectionName}`}
>
{renderFormFields(sectionName)}
</Form>
</Modal>
);
};
const ConfigPage = () => {
const [visible, setVisible] = useState(false);
const [currentSection, setCurrentSection] = useState('');
const onCreate = (values) => {
console.log(`${currentSection} Received values:`, values);
setVisible(false);
};
const openModal = (sectionName) => {
setCurrentSection(sectionName);
setVisible(true);
};
return (
<div className='config-container'>
<Button onClick={() => openModal('Email')}>邮箱</Button>
<Button onClick={() => openModal('HTML URL')}>页面</Button>
<Button onClick={() => openModal('Open-AI')}>Open-AI</Button>
<Button onClick={() => openModal('QQ-Bot')}>QQ-Bot</Button>
<SectionFormModal
sectionName={currentSection}
visible={visible}
onCreate={onCreate}
onCancel={() => setVisible(false)}
/>
</div>
);
};
export default ConfigPage;

View File

@ -0,0 +1,86 @@
import React from 'react'
import {Button, notification} from "antd"
import request from "../../shared/request.js"
import {SDB} from "../../shared/token.js";
import '../../css/demo.css'
import {useNavigate} from "react-router-dom";
export default function Index() {
const navigation = useNavigate()
const [api, contextHolder] = notification.useNotification();
const openNotify = (title, desc) => {
api.open({
message: title,
description: desc,
duration: 2.5,
});
};
return (
<div>
<div className={'index-container'}>
<h3>
<strong>功能测试</strong>
</h3>
<span>
Github地址: <a href="https://github.com/Fromsko">FromSko</a>
</span>
</div>
<div className={'config-container'}>
<CustomButton text='注册' onClick={async () => {
openNotify("dd", "ddd")
await authRegister()
}}/>
<CustomButton text='登录' onClick={() => authLogin()}/>
<CustomButton text='更新' onClick={() => authUpdate()}/>
<CustomButton text='删除' onClick={() => authDelete()}/>
<CustomButton text='刷新' onClick={() => {
SDB.remove('token')
navigation("/login")
}}/>
</div>
</div>
)
}
let CustomButton = ({text, onClick}) => {
return (
<Button type='default' onClick={onClick}>
{text}
</Button>
)
}
const authRegister = async () => {
let userInfo = {
username: 'admin',
password: 'admin'
}
let resp = await request.registerUser(userInfo)
console.log(resp)
}
const authLogin = async () => {
let userInfo = {
username: 'admin',
password: 'admin'
}
let resp = await request.loginUser(userInfo)
console.log(resp)
}
const authUpdate = async () => {
let userInfo = {
username: 'admin',
password: 'admin'
}
let resp = await request.updateUser(SDB.get('user_id'), userInfo)
console.log(resp)
}
const authDelete = async () => {
let resp = await request.deleteUser(SDB.get('user_id'))
console.log(resp)
}

View File

@ -0,0 +1,132 @@
import React, {useState} from 'react'
import {Button, Card, Form, Input, message} from "antd";
import "../../css/demo.css"
import {LockOutlined, UserOutlined} from "@ant-design/icons";
import request from "../../shared/request.js";
import {SDB} from "../../shared/token.js";
import {useNavigate} from "react-router-dom";
const LoginPage = () => {
//
const navigation = useNavigate()
//
const [clickedButton, setClickedButton] = useState(null)
const handleLogin = () => setClickedButton(true)
const handleSignup = () => setClickedButton(false)
const handleFormSubmit = async (values) => {
let resp = {msg: ''}
if (clickedButton) {
resp = await request.loginUser(values)
if (resp.code === 200) {
SDB.set("token", resp.data.token);
setTimeout(() => navigation('/'), 0)
message.success(resp.msg); // await
} else {
message.error(resp.msg);
}
} else {
resp = await request.registerUser(values)
if (resp.code === 200) {
message.success(resp.msg)
SDB.set("user_id", resp.data.token)
} else {
message.error(resp.msg)
}
}
}
return (
<>
<div className='login-div'>
<Card className='login-card'>
<FormLayout
Finish={handleFormSubmit}
Login={handleLogin}
Signup={handleSignup}
/>
</Card>
</div>
</>
)
}
let FormLayout = ({Finish, Login, Signup}) => {
return (
<Form
name="normal_login"
className="login-form"
initialValues={{remember: true}}
onFinish={Finish}
>
<Form.Item style={{textAlign: 'center'}}>
<strong style={{fontSize: '20px'}}>
登录
</strong>
</Form.Item>
<Form.Item name="username" rules={[{required: true, message: '请输入账号'}]}>
<Input
prefix={<UserOutlined className="site-form-item-icon"/>}
placeholder="Username"
allowClear="true"
/>
</Form.Item>
<Form.Item name="password" rules={[{required: true, message: '请输入密码'}]}>
<Input
prefix={<LockOutlined className="site-form-item-icon"/>}
type="password"
placeholder="admin"
/>
</Form.Item>
<Form.Item>
<div style={{display: 'flex', justifyContent: 'space-between'}}>
<CustomButton
buttonText="登录"
buttonType="primary"
onClick={Login} //
showFlag="true"
/>
<CustomButton
buttonText="注册"
buttonType="default"
onClick={Signup} //
/>
</div>
</Form.Item>
</Form>
)
}
let CustomButton = ({buttonText, buttonType, onClick, showFlag}) => {
const [isHovered, setIsHovered] = useState(false)
return (
<Button
block
shape="round"
type={buttonType}
htmlType="submit"
style={{flex: 1, marginRight: '8px',}}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onClick={onClick}
>
{buttonText}
{isHovered && showFlag ? '🥕' : ''}
</Button>
)
}
export default LoginPage

View File

@ -0,0 +1,56 @@
import FloatChat from "../../components/FloatChat.jsx";
import {Card, Image} from "antd";
const TaskPage = () => {
const cardData = [
{
title: 'Title 1',
data: {
info: '运行成功',
href: 'https://picss.sunbangyan.cn/2023/12/05/4d37ff1c004f5640c47395ce3a067904.jpeg',
}
},
{
title: 'Title 2',
data: {
info: '',
href: '',
}
},
{
title: 'Title 3',
data: {
info: '',
href: '',
}
},
{
title: 'Title 4',
data: {
info: '',
href: '',
}
},
];
return (
<>
<div className='running-log'>
<Card title="运行状态图" style={{textAlign: 'center'}}>
{cardData.map((item, index) => (
<Card.Grid id={'running-info'} key={index} title={item.title}>
{item.data.info}
{item.data.href
? <Image src={item.data.href}/>
: <span>loading</span>
}
</Card.Grid>
))}
</Card>
</div>
<FloatChat/>
</>
);
}
export default TaskPage

View File

@ -0,0 +1,30 @@
import React, {useEffect} from 'react';
import {Link} from 'react-router-dom';
import "../../css/NotFoundPage.css"
const NotFoundPage = () => {
// JS
useEffect(() => {
const animateText = () => {
const text = document.querySelector('.not-found-text');
text.classList.add('animated', 'bounce')
};
animateText();
}, []);
return (
<div className='not-body'>
<div className="not-found-container">
<h1 className="not-found-text">404 - Page Not Found</h1>
<p className="not-found-description">
Oops! The page you are looking for doesn't exist.
</p>
<Link to="/" className="back-to-home">
Back to Home
</Link>
</div>
</div>
);
};
export default NotFoundPage;

View File

@ -0,0 +1,18 @@
import React from 'react';
import {Navigate, Outlet, useLocation} from 'react-router-dom';
import {SDB} from "../../shared/token.js";
function RouteGuard() {
const location = useLocation();
const token = SDB.get("token");
if (!token) {
// 访
return <Navigate to="/login" state={{from: location}} replace/>;
}
// ManageLayout
return <Outlet/>;
}
export default RouteGuard;

View File

@ -0,0 +1,52 @@
/**
* 请求接口
*/
import axios from 'axios'
const request = new (class ApiClient {
constructor(baseURL) {
this.baseURL = baseURL
}
// 登录用户
async loginUser (userInfo) {
try {
const response = await axios.post(`${this.baseURL}/user/login`, userInfo)
return response.data
} catch (error) {
throw error
}
}
// 注册用户
async registerUser (newUser) {
try {
const response = await axios.post(`${this.baseURL}/user/register`, newUser)
return response.data
} catch (error) {
throw error
}
}
// 更新用户
async updateUser (userID, updatedUser) {
try {
const response = await axios.put(`${this.baseURL}/user/${userID}`, updatedUser)
return response.data
} catch (error) {
throw error
}
}
// 删除用户
async deleteUser (userID) {
try {
const response = await axios.delete(`${this.baseURL}/user/${userID}`)
return response.data
} catch (error) {
throw error
}
}
})("http://localhost:7001/api/v1")
export default request

View File

@ -0,0 +1,99 @@
class WebSocketClient {
constructor(url) {
this.url = url
this.socket = null
this.heartbeatInterval = 3000 // 心跳间隔,默认为 3 秒
this.logCallback = null
this.chatCallback = null
this.taskCallback = null
this.heartbeatTimer = null
}
connect() {
this.socket = new WebSocket(this.url)
this.socket.onopen = () => {
console.log('WebSocket connection opened.')
this.startHeartbeat()
}
this.socket.onmessage = (event) => {
const message = JSON.parse(event.data)
switch (message.type) {
case 'log':
if (this.logCallback) {
this.logCallback(message.payload)
}
break
case 'chat':
if (this.chatCallback) {
this.chatCallback(message.payload)
}
break
case 'task':
if (this.taskCallback) {
this.taskCallback(message.payload)
}
break
default:
break
}
}
this.socket.onclose = () => {
console.log('WebSocket connection closed.')
this.stopHeartbeat()
}
}
// 日志回调
setLogCallback(callback) {
this.logCallback = callback
}
// 聊天回调
setChatCallback(callback) {
this.chatCallback = callback
}
// 任务回调
setTaskCallback(callback) {
this.taskCallback = callback
}
// 开始心跳
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
this.sendPing()
}, this.heartbeatInterval)
}
// 停止心跳
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer)
}
}
// 设置ping
sendPing() {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({type: 'ping', payload: 'pong'}))
}
}
// 关闭连接
close() {
if (this.socket) {
this.socket.close()
}
}
}
const WS = new WebSocketClient("ws://localhost:7001/api/v1/ws")
// 连接WebSocket
WS.connect()
export default WS

View File

@ -0,0 +1,9 @@
/*
* 浏览器存储
* */
export const SDB = new (class SimpleDB {
set = (key, value) => localStorage.setItem(key, value)
get = (key) => localStorage.getItem(key)
remove = (key) => localStorage.removeItem(key)
})

20
frontend/vite.config.js Normal file
View File

@ -0,0 +1,20 @@
import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
// 服务器地址
port: 7000,
// 代理地址
// proxy: {
// '/api/v1': {
// target: 'http://localhost:50010',
// ws: false,
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api\/v1/, '')
// },
// },
},
})

View File

@ -0,0 +1,24 @@
{
"name": "@wailsapp/runtime",
"version": "2.0.0",
"description": "Wails Javascript runtime library",
"main": "runtime.js",
"types": "runtime.d.ts",
"scripts": {
},
"repository": {
"type": "git",
"url": "git+https://github.com/wailsapp/wails.git"
},
"keywords": [
"Wails",
"Javascript",
"Go"
],
"author": "Lea Anthony <lea.anthony@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/wailsapp/wails/issues"
},
"homepage": "https://github.com/wailsapp/wails#readme"
}

235
frontend/wailsjs/runtime/runtime.d.ts vendored Normal file
View File

@ -0,0 +1,235 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
export interface Position {
x: number;
y: number;
}
export interface Size {
w: number;
h: number;
}
export interface Screen {
isCurrent: boolean;
isPrimary: boolean;
width : number
height : number
}
// Environment information such as platform, buildtype, ...
export interface EnvironmentInfo {
buildType: string;
platform: string;
arch: string;
}
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
// emits the given event. Optional data may be passed with the event.
// This will trigger any event listeners.
export function EventsEmit(eventName: string, ...data: any): void;
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
// sets up a listener for the given event name, but will only trigger a given number times.
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
// sets up a listener for the given event name, but will only trigger once.
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
// unregisters the listener for the given event name.
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
// unregisters all listeners.
export function EventsOffAll(): void;
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
// logs the given message as a raw message
export function LogPrint(message: string): void;
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
// logs the given message at the `trace` log level.
export function LogTrace(message: string): void;
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
// logs the given message at the `debug` log level.
export function LogDebug(message: string): void;
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
// logs the given message at the `error` log level.
export function LogError(message: string): void;
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
// logs the given message at the `fatal` log level.
// The application will quit after calling this method.
export function LogFatal(message: string): void;
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
// logs the given message at the `info` log level.
export function LogInfo(message: string): void;
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
// logs the given message at the `warning` log level.
export function LogWarning(message: string): void;
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
// Forces a reload by the main application as well as connected browsers.
export function WindowReload(): void;
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
// Reloads the application frontend.
export function WindowReloadApp(): void;
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
// Sets the window AlwaysOnTop or not on top.
export function WindowSetAlwaysOnTop(b: boolean): void;
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
// *Windows only*
// Sets window theme to system default (dark/light).
export function WindowSetSystemDefaultTheme(): void;
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
// *Windows only*
// Sets window to light theme.
export function WindowSetLightTheme(): void;
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
// *Windows only*
// Sets window to dark theme.
export function WindowSetDarkTheme(): void;
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
// Centers the window on the monitor the window is currently on.
export function WindowCenter(): void;
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
// Sets the text in the window title bar.
export function WindowSetTitle(title: string): void;
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
// Makes the window full screen.
export function WindowFullscreen(): void;
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
// Restores the previous window dimensions and position prior to full screen.
export function WindowUnfullscreen(): void;
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
export function WindowIsFullscreen(): Promise<boolean>;
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
// Sets the width and height of the window.
export function WindowSetSize(width: number, height: number): Promise<Size>;
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
// Gets the width and height of the window.
export function WindowGetSize(): Promise<Size>;
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
// Setting a size of 0,0 will disable this constraint.
export function WindowSetMaxSize(width: number, height: number): void;
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
// Setting a size of 0,0 will disable this constraint.
export function WindowSetMinSize(width: number, height: number): void;
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
// Sets the window position relative to the monitor the window is currently on.
export function WindowSetPosition(x: number, y: number): void;
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
// Gets the window position relative to the monitor the window is currently on.
export function WindowGetPosition(): Promise<Position>;
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
// Hides the window.
export function WindowHide(): void;
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
// Shows the window, if it is currently hidden.
export function WindowShow(): void;
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
// Maximises the window to fill the screen.
export function WindowMaximise(): void;
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
// Toggles between Maximised and UnMaximised.
export function WindowToggleMaximise(): void;
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
// Restores the window to the dimensions and position prior to maximising.
export function WindowUnmaximise(): void;
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
// Returns the state of the window, i.e. whether the window is maximised or not.
export function WindowIsMaximised(): Promise<boolean>;
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
// Minimises the window.
export function WindowMinimise(): void;
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
// Restores the window to the dimensions and position prior to minimising.
export function WindowUnminimise(): void;
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
// Returns the state of the window, i.e. whether the window is minimised or not.
export function WindowIsMinimised(): Promise<boolean>;
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
// Returns the state of the window, i.e. whether the window is normal or not.
export function WindowIsNormal(): Promise<boolean>;
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
export function ScreenGetAll(): Promise<Screen[]>;
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
// Opens the given URL in the system browser.
export function BrowserOpenURL(url: string): void;
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
// Returns information about the environment
export function Environment(): Promise<EnvironmentInfo>;
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
// Quits the application.
export function Quit(): void;
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
// Hides the application.
export function Hide(): void;
// [Show](https://wails.io/docs/reference/runtime/intro#show)
// Shows the application.
export function Show(): void;
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
// Returns the current text stored on clipboard
export function ClipboardGetText(): Promise<string>;
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
// Sets a text on the clipboard
export function ClipboardSetText(text: string): Promise<boolean>;

View File

@ -0,0 +1,202 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
export function LogPrint(message) {
window.runtime.LogPrint(message);
}
export function LogTrace(message) {
window.runtime.LogTrace(message);
}
export function LogDebug(message) {
window.runtime.LogDebug(message);
}
export function LogInfo(message) {
window.runtime.LogInfo(message);
}
export function LogWarning(message) {
window.runtime.LogWarning(message);
}
export function LogError(message) {
window.runtime.LogError(message);
}
export function LogFatal(message) {
window.runtime.LogFatal(message);
}
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
}
export function EventsOn(eventName, callback) {
return EventsOnMultiple(eventName, callback, -1);
}
export function EventsOff(eventName, ...additionalEventNames) {
return window.runtime.EventsOff(eventName, ...additionalEventNames);
}
export function EventsOnce(eventName, callback) {
return EventsOnMultiple(eventName, callback, 1);
}
export function EventsEmit(eventName) {
let args = [eventName].slice.call(arguments);
return window.runtime.EventsEmit.apply(null, args);
}
export function WindowReload() {
window.runtime.WindowReload();
}
export function WindowReloadApp() {
window.runtime.WindowReloadApp();
}
export function WindowSetAlwaysOnTop(b) {
window.runtime.WindowSetAlwaysOnTop(b);
}
export function WindowSetSystemDefaultTheme() {
window.runtime.WindowSetSystemDefaultTheme();
}
export function WindowSetLightTheme() {
window.runtime.WindowSetLightTheme();
}
export function WindowSetDarkTheme() {
window.runtime.WindowSetDarkTheme();
}
export function WindowCenter() {
window.runtime.WindowCenter();
}
export function WindowSetTitle(title) {
window.runtime.WindowSetTitle(title);
}
export function WindowFullscreen() {
window.runtime.WindowFullscreen();
}
export function WindowUnfullscreen() {
window.runtime.WindowUnfullscreen();
}
export function WindowIsFullscreen() {
return window.runtime.WindowIsFullscreen();
}
export function WindowGetSize() {
return window.runtime.WindowGetSize();
}
export function WindowSetSize(width, height) {
window.runtime.WindowSetSize(width, height);
}
export function WindowSetMaxSize(width, height) {
window.runtime.WindowSetMaxSize(width, height);
}
export function WindowSetMinSize(width, height) {
window.runtime.WindowSetMinSize(width, height);
}
export function WindowSetPosition(x, y) {
window.runtime.WindowSetPosition(x, y);
}
export function WindowGetPosition() {
return window.runtime.WindowGetPosition();
}
export function WindowHide() {
window.runtime.WindowHide();
}
export function WindowShow() {
window.runtime.WindowShow();
}
export function WindowMaximise() {
window.runtime.WindowMaximise();
}
export function WindowToggleMaximise() {
window.runtime.WindowToggleMaximise();
}
export function WindowUnmaximise() {
window.runtime.WindowUnmaximise();
}
export function WindowIsMaximised() {
return window.runtime.WindowIsMaximised();
}
export function WindowMinimise() {
window.runtime.WindowMinimise();
}
export function WindowUnminimise() {
window.runtime.WindowUnminimise();
}
export function WindowSetBackgroundColour(R, G, B, A) {
window.runtime.WindowSetBackgroundColour(R, G, B, A);
}
export function ScreenGetAll() {
return window.runtime.ScreenGetAll();
}
export function WindowIsMinimised() {
return window.runtime.WindowIsMinimised();
}
export function WindowIsNormal() {
return window.runtime.WindowIsNormal();
}
export function BrowserOpenURL(url) {
window.runtime.BrowserOpenURL(url);
}
export function Environment() {
return window.runtime.Environment();
}
export function Quit() {
window.runtime.Quit();
}
export function Hide() {
window.runtime.Hide();
}
export function Show() {
window.runtime.Show();
}
export function ClipboardGetText() {
return window.runtime.ClipboardGetText();
}
export function ClipboardSetText(text) {
return window.runtime.ClipboardSetText(text);
}

90
go.mod Normal file
View File

@ -0,0 +1,90 @@
module could-work
go 1.21
toolchain go1.21.0
require (
github.com/Fromsko/gouitls v1.2.0
github.com/Fromsko/rodPro v0.114.7
github.com/electricbubble/go-toast v0.3.0
github.com/gin-gonic/gin v1.9.1
github.com/gorilla/websocket v1.5.1
github.com/panjf2000/ants/v2 v2.9.0
github.com/sashabaranov/go-openai v1.17.9
github.com/tencent-connect/botgo v0.1.6
github.com/tidwall/gjson v1.17.0
github.com/wailsapp/wails/v2 v2.6.0
gorm.io/driver/sqlite v1.5.4
gorm.io/gorm v1.25.5
)
require (
github.com/bep/debounce v1.2.1 // indirect
github.com/bytedance/sonic v1.10.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.5 // indirect
github.com/go-resty/resty/v2 v2.6.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.4.0 // 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/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/labstack/echo/v4 v4.10.2 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.0 // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/samber/lo v1.38.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tkrajina/go-reflector v0.5.6 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/go-webview2 v1.0.10 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/ysmood/fetchup v0.2.3 // indirect
github.com/ysmood/goob v0.4.0 // indirect
github.com/ysmood/got v0.34.1 // indirect
github.com/ysmood/gson v0.7.3 // indirect
github.com/ysmood/leakless v0.8.0 // indirect
golang.org/x/arch v0.5.0 // indirect
golang.org/x/crypto v0.16.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
// replace github.com/wailsapp/wails/v2 v2.6.0 => C:\Users\skong\go\pkg\mod

304
go.sum Normal file
View File

@ -0,0 +1,304 @@
github.com/Fromsko/gouitls v1.2.0 h1:nlw+z3ZwrWq6YPlo80pgPocm0Yhz77DHJmOlxwSpIFc=
github.com/Fromsko/gouitls v1.2.0/go.mod h1:pnx9wA17MZUcP8T93DL+CH++RPCVX/SVByCJjW5mJO0=
github.com/Fromsko/rodPro v0.114.7 h1:c8/eE4tkKAanqqv+bPD50F10BNf/0BRjE365uHKKmqY=
github.com/Fromsko/rodPro v0.114.7/go.mod h1:AAALAFUGslxJd9Gvjmg67HVDIZ6BYNa2jHKmir3ktWY=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/electricbubble/go-toast v0.3.0 h1:e/PtFkpYNdLANI/utTXzoYQuMYG/N8oHT0Rwal4z/sI=
github.com/electricbubble/go-toast v0.3.0/go.mod h1:6k4ufXmV/AS32EugdLeIXa+2jv6oYbWzv2+UZhB49FI=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
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/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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.6.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg=
github.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/panjf2000/ants/v2 v2.9.0 h1:SztCLkVxBRigbg+vt0S5QvF5vxAbxbKt09/YfAJ0tEo=
github.com/panjf2000/ants/v2 v2.9.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/sashabaranov/go-openai v1.17.9 h1:QEoBiGKWW68W79YIfXWEFZ7l5cEgZBV4/Ow3uy+5hNY=
github.com/sashabaranov/go-openai v1.17.9/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tencent-connect/botgo v0.1.6 h1:7sGdUYptJ7CB/rVEIULW+h9VdApB+wguv7aA1u2QmvQ=
github.com/tencent-connect/botgo v0.1.6/go.mod h1:+++Vgx3ai3lYpg1N+32IMVn0KiXfbxT7dXzoRnLq7OM=
github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE=
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/wailsapp/go-webview2 v1.0.10 h1:PP5Hug6pnQEAhfRzLCoOh2jJaPdrqeRgJKZhyYyDV/w=
github.com/wailsapp/go-webview2 v1.0.10/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.6.0 h1:EyH0zR/EO6dDiqNy8qU5spaXDfkluiq77xrkabPYD4c=
github.com/wailsapp/wails/v2 v2.6.0/go.mod h1:WBG9KKWuw0FKfoepBrr/vRlyTmHaMibWesK3yz6nNiM=
github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=
github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18=
github.com/ysmood/gop v0.0.2 h1:VuWweTmXK+zedLqYufJdh3PlxDNBOfFHjIZlPT2T5nw=
github.com/ysmood/gop v0.0.2/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk=
github.com/ysmood/got v0.34.1 h1:IrV2uWLs45VXNvZqhJ6g2nIhY+pgIG1CUoOcqfXFl1s=
github.com/ysmood/got v0.34.1/go.mod h1:yddyjq/PmAf08RMLSwDjPyCvHvYed+WjHnQxpH851LM=
github.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY=
github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM=
github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE=
github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=
github.com/ysmood/leakless v0.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak=
github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

46
main.go Normal file
View File

@ -0,0 +1,46 @@
package main
import (
"could-work/backend"
"embed"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v2/pkg/options/windows"
)
//go:embed all:frontend/dist
var assets embed.FS
func main() {
// Create an instance of the app structure
app := backend.NewApp()
// Create application with options
err := wails.Run(&options.App{
Title: "cloud-work-ui",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255},
OnStartup: app.Startup,
OnShutdown: app.Shutdown,
Bind: []interface{}{
app,
},
Frameless: false, // 无边框模式(True)
DisableResize: true,
Windows: &windows.Options{
WebviewIsTransparent: false,
WindowIsTranslucent: false,
DisableWindowIcon: false,
},
})
if err != nil {
println("Error:", err.Error())
}
}

BIN
res/img/0实习日志.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

BIN
res/img/1实习日志.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
res/img/2实习日志.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
res/img/主页.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
res/img/实习日志.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
res/img/实习通知.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
res/img/登录页.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
res/img/验证码.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

11
res/prompt Normal file
View File

@ -0,0 +1,11 @@
我需要你充当一个{xx公司}的实习生角色, 你需要参考用户提供的{prompt}中包含的背景信息和关键词。
你是一个实习角色的智能写手,可以考虑以下要素和关键词,以便给出适用于公司实习生实习报告的写手。输出格式参考如下:
```work
1. 公司背景信息:提供公司的基本信息,包括公司名称、行业、使命和愿景等。例如:"你好我是一名在XYZ公司实习的实习生XYZ公司是一家领先的科技公司致力于开发创新的软件解决方案为客户提供卓越的服务。"
2.实习目标和任务:描述你在实习期间的主要目标和任务。这可以包括你参与的具体项目、与团队合作的经验、技能发展和学习机会等。例如:"在我的实习期间我有幸参与了ABC项目负责进行市场调研和竞争分析。我与团队密切合作学习了如何制定营销策略并与客户进行沟通。"
3.成果和贡献:强调你在实习期间取得的成果和对公司的贡献。这可以是你提出的创新想法、解决的问题、提高的效率或质量等方面的成就。例如:"通过我的努力,我成功提出了一个新的市场推广策略,帮助公司吸引了更多的潜在客户,并提高了销售额。"
4.技能和经验:强调你在实习期间获得的技能和经验,以及如何将它们应用到实际工作中。这可以包括技术技能、沟通能力、问题解决能力、团队合作等。例如:"在实习期间,我通过学习和实践,掌握了数据分析工具和营销软件的使用,并提高了我的沟通和演示技巧。"
5.学习和发展:强调你在实习期间的学习和个人发展。这可以包括你通过实践经验学到的教训、克服的挑战以及如何应对反馈和改进自己等方面。例如:"我在实习期间遇到了一些挑战,但通过团队的支持和我的努力,我学会了灵活适应变化,并不断改进自己的工作方法和技能。"
6.感谢和展望:表达对公司和团队的感谢,并展望未来。这可以包括你对实习经历的总结和感悟,以及对未来发展的期望和计划。例如:"我非常感谢XYZ公司给我这个实习的机会并与优秀的团队一起工作。我相信这次实习经历将对我未来的职业发展产生积极的影响并期待能够继续为公司做出更大的贡献。"
```
你需要匹配用户的{prompt}给出的关键词,并给出实习合适的实习日志。

14
wails.json Normal file
View File

@ -0,0 +1,14 @@
{
"$schema": "https://wails.io/schemas/config.v2.json",
"name": "cloud-work-ui",
"outputfilename": "cloud-work-ui",
"frontend:install": "npm install",
"frontend:build": "npm run build",
"frontend:dev:watcher": "npm run dev",
"frontend:dev:serverUrl": "auto",
"author": {
"name": "Fromsko",
"email": "99723642+Fromsko@users.noreply.github.com"
},
"resources": ["frontend/src/assets"]
}