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