5c807d76a0
Extracted from Cyrene main repo (backend/pkg/plugins + backend/plugin-manager). Contains SDK interfaces (Plugin/Tool/HostAPI), 13 built-in plugins, ToolRegistry with call log ring buffer, and Plugin Manager REST API service. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
178 lines
5.6 KiB
Go
178 lines
5.6 KiB
Go
package text
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene-Plugins/sdk"
|
|
)
|
|
|
|
type TextPlugin struct{ sdk.BasePlugin }
|
|
|
|
func (p *TextPlugin) Metadata() sdk.PluginMetadata {
|
|
return sdk.PluginMetadata{
|
|
Name: "text", DisplayName: "Text Processing", Version: "1.0.0",
|
|
Description: "Text processing: count stats, summarize, regex extract",
|
|
Category: "utility", Author: sdk.PluginAuthor{Name: "Cyrene Team"},
|
|
}
|
|
}
|
|
|
|
func (p *TextPlugin) Tools() []sdk.Tool { return []sdk.Tool{&TextTool{}} }
|
|
|
|
type TextTool struct{ sdk.BaseTool }
|
|
|
|
func (t *TextTool) Definition() sdk.ToolDefinition {
|
|
return sdk.ToolDefinition{
|
|
ID: "text", Name: "text", DisplayName: "Text Processing",
|
|
Description: "Text processing. Count stats, summarize, translate, regex extract.",
|
|
Category: "utility", Complexity: sdk.ComplexitySimple,
|
|
Parameters: map[string]interface{}{
|
|
"type": "object",
|
|
"properties": map[string]interface{}{
|
|
"action": map[string]interface{}{"type": "string", "enum": []string{"count", "summarize", "translate", "extract"}},
|
|
"text": map[string]interface{}{"type": "string"},
|
|
"target_lang": map[string]interface{}{"type": "string", "enum": []string{"en", "zh", "ja", "ko", "fr", "de"}},
|
|
"pattern": map[string]interface{}{"type": "string"},
|
|
},
|
|
"required": []string{"action", "text"},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (t *TextTool) Validate(args map[string]interface{}) error {
|
|
for _, k := range []string{"action", "text"} {
|
|
if _, ok := args[k]; !ok {
|
|
return fmt.Errorf("missing required parameter: %s", k)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *TextTool) Execute(_ context.Context, args map[string]interface{}) (*sdk.ToolResult, error) {
|
|
action, _ := args["action"].(string)
|
|
txt, _ := args["text"].(string)
|
|
|
|
switch action {
|
|
case "count":
|
|
charsNoSpace := 0
|
|
chineseChars := 0
|
|
for _, r := range txt {
|
|
if !unicode.IsSpace(r) {
|
|
charsNoSpace++
|
|
}
|
|
if unicode.Is(unicode.Han, r) {
|
|
chineseChars++
|
|
}
|
|
}
|
|
words := strings.Fields(txt)
|
|
lines := strings.Split(txt, "\n")
|
|
paragraphs := regexp.MustCompile(`\n\s*\n`).Split(txt, -1)
|
|
return &sdk.ToolResult{ToolName: "text", Success: true, Output: fmt.Sprintf(
|
|
"Characters: %d (no spaces: %d, Chinese: %d)\nBytes: %d\nWords: %d\nLines: %d\nParagraphs: %d",
|
|
len([]rune(txt)), charsNoSpace, chineseChars, len(txt), len(words), len(lines), len(paragraphs))}, nil
|
|
|
|
case "summarize":
|
|
paragraphs := regexp.MustCompile(`\n\s*\n`).Split(txt, -1)
|
|
firstPara := ""
|
|
if len(paragraphs) > 0 {
|
|
runes := []rune(paragraphs[0])
|
|
if len(runes) > 300 {
|
|
runes = runes[:300]
|
|
}
|
|
firstPara = string(runes)
|
|
}
|
|
sentences := regexp.MustCompile(`[。!?.!?]+`).Split(txt, -1)
|
|
keywords := []string{"重要", "关键", "因此", "总结", "important", "key", "conclusion", "therefore"}
|
|
type scored struct {
|
|
text string
|
|
score int
|
|
}
|
|
var scoredSents []scored
|
|
for _, s := range sentences {
|
|
s = strings.TrimSpace(s)
|
|
if len([]rune(s)) < 10 {
|
|
continue
|
|
}
|
|
score := len([]rune(s))
|
|
for _, kw := range keywords {
|
|
if strings.Contains(strings.ToLower(s), strings.ToLower(kw)) {
|
|
score += 20
|
|
}
|
|
}
|
|
scoredSents = append(scoredSents, scored{s, score})
|
|
}
|
|
var out strings.Builder
|
|
out.WriteString(fmt.Sprintf("First paragraph: %s\n\nKey sentences:\n", firstPara))
|
|
count := 0
|
|
for i := 0; i < len(scoredSents) && count < 5; i++ {
|
|
out.WriteString(fmt.Sprintf("- %s\n", scoredSents[i].text))
|
|
count++
|
|
}
|
|
return &sdk.ToolResult{ToolName: "text", Success: true, Output: out.String()}, nil
|
|
|
|
case "translate":
|
|
targetLang, _ := args["target_lang"].(string)
|
|
if targetLang == "" {
|
|
targetLang = "en"
|
|
}
|
|
return &sdk.ToolResult{ToolName: "text", Success: true, Output: fmt.Sprintf(
|
|
"[Translation request] Please translate the following text to %s.\n\nOriginal text:\n%s", targetLang, txt)}, nil
|
|
|
|
case "extract":
|
|
pattern, _ := args["pattern"].(string)
|
|
var out strings.Builder
|
|
extracted := false
|
|
if pattern == "" || pattern == "email" {
|
|
re := regexp.MustCompile(`[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`)
|
|
if matches := re.FindAllString(txt, -1); len(matches) > 0 {
|
|
out.WriteString("Emails:\n")
|
|
for _, m := range matches {
|
|
out.WriteString(fmt.Sprintf("- %s\n", m))
|
|
}
|
|
extracted = true
|
|
}
|
|
}
|
|
if pattern == "" || pattern == "phone" {
|
|
re := regexp.MustCompile(`1[3-9]\d{9}`)
|
|
if matches := re.FindAllString(txt, -1); len(matches) > 0 {
|
|
out.WriteString("Phone numbers:\n")
|
|
for _, m := range matches {
|
|
out.WriteString(fmt.Sprintf("- %s\n", m))
|
|
}
|
|
extracted = true
|
|
}
|
|
}
|
|
if pattern == "" || pattern == "url" {
|
|
re := regexp.MustCompile(`https?://[^\s<>"{}|\\^` + "`" + `\[\]]+`)
|
|
if matches := re.FindAllString(txt, -1); len(matches) > 0 {
|
|
out.WriteString("URLs:\n")
|
|
for _, m := range matches {
|
|
out.WriteString(fmt.Sprintf("- %s\n", m))
|
|
}
|
|
extracted = true
|
|
}
|
|
}
|
|
if !extracted && pattern != "" && pattern != "email" && pattern != "phone" && pattern != "url" {
|
|
re, err := regexp.Compile(pattern)
|
|
if err != nil {
|
|
return &sdk.ToolResult{ToolName: "text", Success: false, Error: "Invalid regex: " + err.Error()}, nil
|
|
}
|
|
if matches := re.FindAllString(txt, -1); len(matches) > 0 {
|
|
out.WriteString(fmt.Sprintf("Pattern matches (%s):\n", pattern))
|
|
for _, m := range matches {
|
|
out.WriteString(fmt.Sprintf("- %s\n", m))
|
|
}
|
|
extracted = true
|
|
}
|
|
}
|
|
if !extracted {
|
|
return &sdk.ToolResult{ToolName: "text", Success: true, Output: "No matches found"}, nil
|
|
}
|
|
return &sdk.ToolResult{ToolName: "text", Success: true, Output: out.String()}, nil
|
|
}
|
|
return &sdk.ToolResult{ToolName: "text", Success: false, Error: "unknown action: " + action}, nil
|
|
}
|