mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 12:15:16 +00:00
feat: 内置 ai_image 插件接入 llmsproxy 生图端点
- 新增 internal/plugins/ai_image(开箱即用,纯 Go,无外部依赖): - 注册 ai_image_generate 工具,POST base_url + /images/generations - 配置项 base_url(默认 http://127.0.0.1:8081/v1)/api_key(留空回落 core.llm.api_key)/model/size - 兼容 OpenAI /v1/images/generations 响应(url 与 b64_json) - plugins/all.go 注册 ai_image - 生图上游由 llmsproxy 侧后续配置(HomeAgent 直达其端点) 验证: go build/test 全绿;部署后 ai_image 加载、工具注册、agent 可调用
This commit is contained in:
219
internal/plugins/ai_image/plugin.go
Normal file
219
internal/plugins/ai_image/plugin.go
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
package aiimage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitcode.com/JianFeeeee/HomeAgent/internal/plugin"
|
||||||
|
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
plugin.RegisterPluginMeta("ai_image", "AI 生图", "AI Image")
|
||||||
|
plugin.RegisterFactory("ai_image", func(name string, config map[string]interface{}) (sdk.Plugin, error) {
|
||||||
|
return New(name), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Plugin struct {
|
||||||
|
name string
|
||||||
|
client *http.Client
|
||||||
|
apiKey string
|
||||||
|
baseURL string
|
||||||
|
model string
|
||||||
|
size string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(name string) *Plugin {
|
||||||
|
return &Plugin{
|
||||||
|
name: name,
|
||||||
|
client: &http.Client{Timeout: 120 * time.Second},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) Name() string { return p.name }
|
||||||
|
|
||||||
|
func getSettingString(s sdk.SettingsAPI, key, def string) string {
|
||||||
|
if s == nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
v, err := s.Get(key)
|
||||||
|
if err != nil || v == nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
if sv, ok := v.(string); ok && sv != "" {
|
||||||
|
return sv
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||||
|
s.SetAutoRestart(true)
|
||||||
|
|
||||||
|
s.Settings().RegisterDef(sdk.ConfigDef{
|
||||||
|
Key: "base_url", Default: "http://127.0.0.1:8081/v1", Type: "string",
|
||||||
|
DisplayName: "Base URL", Description: "OpenAI 兼容生图网关地址(默认本机 llmsproxy)",
|
||||||
|
Category: "ai_image",
|
||||||
|
})
|
||||||
|
s.Settings().RegisterDef(sdk.ConfigDef{
|
||||||
|
Key: "api_key", Default: "", Type: "password",
|
||||||
|
DisplayName: "API Key", Description: "生图网关 API Key(默认使用内核 LLM key,留空则取 core.llm.api_key)",
|
||||||
|
Category: "ai_image",
|
||||||
|
})
|
||||||
|
s.Settings().RegisterDef(sdk.ConfigDef{
|
||||||
|
Key: "model", Default: "", Type: "string",
|
||||||
|
DisplayName: "Model", Description: "生图模型 ID,留空由网关 AUTO 决定(如 flux-1)",
|
||||||
|
Category: "ai_image",
|
||||||
|
})
|
||||||
|
s.Settings().RegisterDef(sdk.ConfigDef{
|
||||||
|
Key: "size", Default: "1024x1024", Type: "string",
|
||||||
|
DisplayName: "Size", Description: "默认图片尺寸,如 1024x1024",
|
||||||
|
Category: "ai_image",
|
||||||
|
})
|
||||||
|
|
||||||
|
p.apiKey = getSettingString(s.Settings(), "api_key", "")
|
||||||
|
p.baseURL = strings.TrimRight(getSettingString(s.Settings(), "base_url", "http://127.0.0.1:8081/v1"), "/")
|
||||||
|
p.model = getSettingString(s.Settings(), "model", "")
|
||||||
|
p.size = getSettingString(s.Settings(), "size", "1024x1024")
|
||||||
|
|
||||||
|
if p.apiKey == "" {
|
||||||
|
if v, _ := s.Settings().GetCore("llm.api_key"); v != nil {
|
||||||
|
if sv, ok := v.(string); ok && sv != "" {
|
||||||
|
p.apiKey = sv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.RegisterTool("ai_image_generate", sdk.ToolDef{
|
||||||
|
Name: "ai_image_generate", Description: "Generate image from text prompt using AI. Returns image URL.",
|
||||||
|
Parameters: map[string]interface{}{
|
||||||
|
"type": "object",
|
||||||
|
"properties": map[string]interface{}{
|
||||||
|
"prompt": map[string]interface{}{"type": "string", "description": "Text description of the image to generate"},
|
||||||
|
"size": map[string]interface{}{"type": "string", "description": "Image size (1024x1024, etc.), default from config"},
|
||||||
|
"model": map[string]interface{}{"type": "string", "description": "Model override (e.g. flux-1)"},
|
||||||
|
"n": map[string]interface{}{"type": "integer", "description": "Number of images to generate (1-10), default 1"},
|
||||||
|
},
|
||||||
|
"required": []string{"prompt"},
|
||||||
|
},
|
||||||
|
}, p.handleGenerate)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) Stop() error { return nil }
|
||||||
|
|
||||||
|
type genRequest struct {
|
||||||
|
Model string `json:"model"`
|
||||||
|
Prompt string `json:"prompt"`
|
||||||
|
N int `json:"n"`
|
||||||
|
Size string `json:"size"`
|
||||||
|
ResponseFormat string `json:"response_format"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type genResp struct {
|
||||||
|
Created int64 `json:"created"`
|
||||||
|
Data []struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
B64JSON string `json:"b64_json"`
|
||||||
|
RevisedPrompt string `json:"revised_prompt"`
|
||||||
|
} `json:"data"`
|
||||||
|
Error *struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
} `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) handleGenerate(args map[string]interface{}) (interface{}, error) {
|
||||||
|
prompt, _ := args["prompt"].(string)
|
||||||
|
if strings.TrimSpace(prompt) == "" {
|
||||||
|
return map[string]interface{}{"isError": true, "content": "prompt is required"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
key := p.apiKey
|
||||||
|
if key == "" {
|
||||||
|
return map[string]interface{}{"isError": true, "content": "生图 API key 未配置(plugin.ai_image.api_key 或 core.llm.api_key)"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
model := p.model
|
||||||
|
if m, ok := args["model"].(string); ok && m != "" {
|
||||||
|
model = m
|
||||||
|
}
|
||||||
|
size := p.size
|
||||||
|
if sz, ok := args["size"].(string); ok && sz != "" {
|
||||||
|
size = sz
|
||||||
|
}
|
||||||
|
n := 1
|
||||||
|
if nv, ok := args["n"].(float64); ok {
|
||||||
|
n = int(nv)
|
||||||
|
if n < 1 {
|
||||||
|
n = 1
|
||||||
|
}
|
||||||
|
if n > 10 {
|
||||||
|
n = 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body := genRequest{Model: model, Prompt: prompt, N: n, Size: size, ResponseFormat: "url"}
|
||||||
|
raw, _ := json.Marshal(body)
|
||||||
|
|
||||||
|
url := p.baseURL + "/images/generations"
|
||||||
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(raw))
|
||||||
|
if err != nil {
|
||||||
|
return map[string]interface{}{"isError": true, "content": "构造请求失败: " + err.Error()}, nil
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Bearer "+key)
|
||||||
|
|
||||||
|
resp, err := p.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return map[string]interface{}{"isError": true, "content": "生图请求失败: " + err.Error()}, nil
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, _ := io.ReadAll(resp.Body)
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return map[string]interface{}{"isError": true, "content": fmt.Sprintf("生图 API error (status %d): %s", resp.StatusCode, string(respBody))}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result genResp
|
||||||
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||||
|
return map[string]interface{}{"isError": true, "content": "解析生图响应失败: " + err.Error()}, nil
|
||||||
|
}
|
||||||
|
if result.Error != nil {
|
||||||
|
return map[string]interface{}{"isError": true, "content": "生图 API error: " + result.Error.Message}, nil
|
||||||
|
}
|
||||||
|
if len(result.Data) == 0 {
|
||||||
|
return map[string]interface{}{"isError": true, "content": "no images returned"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var urls []string
|
||||||
|
var saved []string
|
||||||
|
for i, d := range result.Data {
|
||||||
|
imgURL := d.URL
|
||||||
|
if imgURL == "" && d.B64JSON != "" {
|
||||||
|
imgURL = "data:image/png;base64," + d.B64JSON
|
||||||
|
}
|
||||||
|
if imgURL != "" {
|
||||||
|
urls = append(urls, imgURL)
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
saved = append(saved, imgURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(urls) == 0 {
|
||||||
|
return map[string]interface{}{"isError": true, "content": "生图响应中没有可用图片"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"content": fmt.Sprintf("Generated %d image(s) with model %s:\n%s", len(urls), model, strings.Join(urls, "\n")),
|
||||||
|
"images": urls,
|
||||||
|
"prompt": prompt,
|
||||||
|
"model": model,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@ -2,13 +2,14 @@ package plugins
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/agentcli"
|
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/agentcli"
|
||||||
|
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/ai_image"
|
||||||
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/cfgmgr"
|
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/cfgmgr"
|
||||||
|
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/clawhubadapter"
|
||||||
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/cli"
|
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/cli"
|
||||||
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/cmd"
|
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/cmd"
|
||||||
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/files"
|
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/files"
|
||||||
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/healthcheck"
|
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/healthcheck"
|
||||||
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/mcp"
|
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/mcp"
|
||||||
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/clawhubadapter"
|
|
||||||
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/pluginmgr"
|
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/pluginmgr"
|
||||||
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/timer"
|
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/timer"
|
||||||
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/webui"
|
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/webui"
|
||||||
|
|||||||
Reference in New Issue
Block a user