mirror of
https://github.com/siyuan-note/siyuan.git
synced 2026-07-03 14:09:06 +02:00
c63fec9632
Signed-off-by: Daniel <845765@qq.com>
252 lines
8.6 KiB
Go
252 lines
8.6 KiB
Go
// SiYuan - Refactor your thinking
|
|
// Copyright (c) 2020-present, b3log.org
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package tools
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/siyuan-note/siyuan/kernel/model"
|
|
"github.com/siyuan-note/siyuan/kernel/sql"
|
|
"github.com/siyuan-note/siyuan/kernel/util"
|
|
)
|
|
|
|
var NotebookTool = &Tool{
|
|
Name: "notebook",
|
|
Description: "Notebook management. Actions: list(), open(id), close(id), create(name), rename(id, name), remove(id), set_icon(id, icon), random_icon(id?).",
|
|
InputSchema: ToolSchema{
|
|
Type: "object",
|
|
Properties: map[string]Property{
|
|
"action": {Type: "string", Description: "Operation", Enum: []string{"list", "open", "close", "create", "rename", "remove", "set_icon", "random_icon"}},
|
|
"id": {Type: "string", Description: "Notebook ID (for open, close, rename, remove, set_icon, random_icon)"},
|
|
"name": {Type: "string", Description: "Notebook name (for create, rename)"},
|
|
"icon": {Type: "string", Description: "Notebook icon (for set_icon). Emoji hex codepoint like \"1f4ca\", emoji character like \"📊\", custom image path like \"1/b3log.png\", or dynamic icon URL like \"api/icon/getDynamicIcon?type=8&color=%23d23f31&content=SiYuan&id=xxx\""},
|
|
},
|
|
Required: []string{"action"},
|
|
},
|
|
Handler: notebookHandler,
|
|
}
|
|
|
|
func init() {
|
|
register(NotebookTool)
|
|
}
|
|
|
|
func notebookHandler(args map[string]interface{}) (CallToolResult, error) {
|
|
action, _ := args["action"].(string)
|
|
switch action {
|
|
case "list":
|
|
return notebookList(args)
|
|
case "open":
|
|
return notebookOpen(args)
|
|
case "close":
|
|
return notebookClose(args)
|
|
case "create":
|
|
return notebookCreate(args)
|
|
case "rename":
|
|
return notebookRename(args)
|
|
case "remove":
|
|
return notebookRemove(args)
|
|
case "set_icon":
|
|
return notebookSetIcon(args)
|
|
case "random_icon":
|
|
return notebookRandomIcon(args)
|
|
}
|
|
return CallToolResult{
|
|
Content: []ContentItem{{Type: "text", Text: "unknown action '" + action + "', expected one of: [list, open, close, create, rename, remove, set_icon, random_icon]"}},
|
|
IsError: true,
|
|
}, nil
|
|
}
|
|
|
|
func notebookList(args map[string]interface{}) (CallToolResult, error) {
|
|
notebooks, err := model.ListNotebooks()
|
|
if err != nil {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "list notebooks failed: " + err.Error()}}, IsError: true}, nil
|
|
}
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString(fmt.Sprintf("Notebooks (%d):\n\n", len(notebooks)))
|
|
for _, nb := range notebooks {
|
|
sb.WriteString(fmt.Sprintf("- %s (id: %s, icon: %s, closed: %v)\n", nb.Name, nb.ID, nb.Icon, nb.Closed))
|
|
}
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: sb.String()}}}, nil
|
|
}
|
|
|
|
func notebookCreate(args map[string]interface{}) (CallToolResult, error) {
|
|
name, _ := args["name"].(string)
|
|
if name == "" {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "name is required"}}, IsError: true}, nil
|
|
}
|
|
|
|
id, err := model.CreateBox(name)
|
|
if err != nil {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "create notebook failed: " + err.Error()}}, IsError: true}, nil
|
|
}
|
|
|
|
if box := model.Conf.Box(id); nil != box {
|
|
evt := util.NewCmdResult("createnotebook", 0, util.PushModeBroadcast)
|
|
evt.Data = map[string]any{"box": box}
|
|
util.PushEvent(evt)
|
|
}
|
|
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "notebook created: " + name + " (id: " + id + ")"}}}, nil
|
|
}
|
|
|
|
func notebookRename(args map[string]interface{}) (CallToolResult, error) {
|
|
id, _ := args["id"].(string)
|
|
name, _ := args["name"].(string)
|
|
if id == "" || name == "" {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "id and name are required"}}, IsError: true}, nil
|
|
}
|
|
|
|
if err := model.RenameBox(id, name); err != nil {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "rename notebook failed: " + err.Error()}}, IsError: true}, nil
|
|
}
|
|
|
|
evt := util.NewCmdResult("renamenotebook", 0, util.PushModeBroadcast)
|
|
evt.Data = map[string]any{"box": id}
|
|
util.PushEvent(evt)
|
|
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "notebook renamed: " + id + " -> " + name}}}, nil
|
|
}
|
|
|
|
func notebookRemove(args map[string]interface{}) (CallToolResult, error) {
|
|
id, _ := args["id"].(string)
|
|
if id == "" {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "id is required"}}, IsError: true}, nil
|
|
}
|
|
|
|
if err := model.RemoveBox(id); err != nil {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "remove notebook failed: " + err.Error()}}, IsError: true}, nil
|
|
}
|
|
|
|
evt := util.NewCmdResult("removeBox", 0, util.PushModeBroadcast)
|
|
evt.Data = map[string]any{"box": id}
|
|
util.PushEvent(evt)
|
|
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "notebook removed: " + id}}}, nil
|
|
}
|
|
|
|
func notebookOpen(args map[string]interface{}) (CallToolResult, error) {
|
|
id, _ := args["id"].(string)
|
|
if id == "" {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "id is required"}}, IsError: true}, nil
|
|
}
|
|
|
|
existed, err := model.Mount(id)
|
|
if err != nil {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "open notebook failed: " + err.Error()}}, IsError: true}, nil
|
|
}
|
|
|
|
if box := model.Conf.Box(id); nil != box {
|
|
evt := util.NewCmdResult("mount", 0, util.PushModeBroadcast)
|
|
evt.Data = map[string]any{
|
|
"box": box,
|
|
"existed": existed,
|
|
}
|
|
util.PushEvent(evt)
|
|
}
|
|
|
|
time.Sleep(1 * time.Second)
|
|
sql.FlushQueue()
|
|
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "notebook opened: " + id}}}, nil
|
|
}
|
|
|
|
func notebookClose(args map[string]interface{}) (CallToolResult, error) {
|
|
id, _ := args["id"].(string)
|
|
if id == "" {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "id is required"}}, IsError: true}, nil
|
|
}
|
|
|
|
model.Unmount(id)
|
|
|
|
time.Sleep(1 * time.Second)
|
|
sql.FlushQueue()
|
|
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "notebook closed: " + id}}}, nil
|
|
}
|
|
|
|
func notebookSetIcon(args map[string]interface{}) (CallToolResult, error) {
|
|
id, _ := args["id"].(string)
|
|
icon, _ := args["icon"].(string)
|
|
if id == "" {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "id is required"}}, IsError: true}, nil
|
|
}
|
|
|
|
// 校验笔记本存在,避免对一个不存在的 id 静默写入图标。
|
|
exists := false
|
|
if notebooks, err := model.ListNotebooks(); err == nil {
|
|
for _, nb := range notebooks {
|
|
if nb.ID == id {
|
|
exists = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !exists {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "notebook not found: " + id}}, IsError: true}, nil
|
|
}
|
|
|
|
// icon 取值格式与 attr 工具一致;SetBoxIcon 内部对自定义图片名做 XSS 过滤。
|
|
model.SetBoxIcon(id, icon)
|
|
util.PushReloadFiletree()
|
|
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "notebook icon set: " + id + " -> " + icon}}}, nil
|
|
}
|
|
|
|
func notebookRandomIcon(args map[string]interface{}) (CallToolResult, error) {
|
|
id, _ := args["id"].(string)
|
|
|
|
notebooks, err := model.ListNotebooks()
|
|
if err != nil {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "list notebooks failed: " + err.Error()}}, IsError: true}, nil
|
|
}
|
|
|
|
// 目标范围:传 id 仅换该笔记本;不传 id 则对全部笔记本各随机换一个。
|
|
targets := notebooks
|
|
if id != "" {
|
|
var found bool
|
|
for _, nb := range notebooks {
|
|
if nb.ID == id {
|
|
targets = []*model.Box{nb}
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "notebook not found: " + id}}, IsError: true}, nil
|
|
}
|
|
}
|
|
if len(targets) == 0 {
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "no notebooks to update"}}, IsError: true}, nil
|
|
}
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString(fmt.Sprintf("Randomized icons for %d notebook(s):\n", len(targets)))
|
|
for _, nb := range targets {
|
|
oldIcon := nb.Icon
|
|
newIcon := randomEmoji()
|
|
model.SetBoxIcon(nb.ID, newIcon)
|
|
sb.WriteString(fmt.Sprintf("- %s (id: %s): %s -> %s\n", nb.Name, nb.ID, oldIcon, newIcon))
|
|
}
|
|
|
|
util.PushReloadFiletree()
|
|
|
|
return CallToolResult{Content: []ContentItem{{Type: "text", Text: sb.String()}}}, nil
|
|
}
|