ai服务状态更新
This commit is contained in:
@@ -0,0 +1,203 @@
|
|||||||
|
package ai
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"meshtastic_mqtt_server/internal/autoreply"
|
||||||
|
"meshtastic_mqtt_server/internal/llm"
|
||||||
|
storepkg "meshtastic_mqtt_server/internal/store"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AIServiceStatus reports the current state of the AI service
|
||||||
|
type AIServiceStatus struct {
|
||||||
|
Running bool `json:"running"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
ProviderCount int `json:"provider_count"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AIManager manages the lifecycle of the AI service, supporting restart
|
||||||
|
type AIManager struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
service *Service
|
||||||
|
cfg Config
|
||||||
|
db *gorm.DB
|
||||||
|
botSender autoreply.BotSender
|
||||||
|
ctx context.Context
|
||||||
|
store *storepkg.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAIManager creates a new AIManager
|
||||||
|
func NewAIManager(cfg Config, db *gorm.DB, botSender autoreply.BotSender, ctx context.Context, store *storepkg.Store) *AIManager {
|
||||||
|
return &AIManager{
|
||||||
|
cfg: cfg,
|
||||||
|
db: db,
|
||||||
|
botSender: botSender,
|
||||||
|
ctx: ctx,
|
||||||
|
store: store,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfigEnabled sets the enabled flag on the config
|
||||||
|
func (m *AIManager) SetConfigEnabled(enabled bool) {
|
||||||
|
m.cfg.Enabled = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProviderConfigs sets the LLM provider configs
|
||||||
|
func (m *AIManager) SetProviderConfigs(configs []llm.ProviderConfig) {
|
||||||
|
m.cfg.LLMProviders = configs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init creates and starts the AI service
|
||||||
|
func (m *AIManager) Init() error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
svc, err := NewService(m.cfg, m.db, m.botSender)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create AI service: %w", err)
|
||||||
|
}
|
||||||
|
if err := svc.Start(m.ctx); err != nil {
|
||||||
|
return fmt.Errorf("failed to start AI service: %w", err)
|
||||||
|
}
|
||||||
|
m.service = svc
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the currently running AI service
|
||||||
|
func (m *AIManager) Stop() {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
if m.service != nil {
|
||||||
|
m.service.Stop()
|
||||||
|
m.service = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status returns the current AI service status
|
||||||
|
func (m *AIManager) Status() AIServiceStatus {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
providerCount := len(m.cfg.LLMProviders)
|
||||||
|
if m.service == nil {
|
||||||
|
msg := "AI 服务未运行,可在配置提供商后点击重启"
|
||||||
|
if providerCount == 0 {
|
||||||
|
msg = "尚未配置 AI 提供商,请先添加提供商配置"
|
||||||
|
}
|
||||||
|
return AIServiceStatus{
|
||||||
|
Running: false,
|
||||||
|
Enabled: m.cfg.Enabled,
|
||||||
|
ProviderCount: providerCount,
|
||||||
|
Message: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enabled := m.service.Enabled()
|
||||||
|
if !enabled {
|
||||||
|
return AIServiceStatus{
|
||||||
|
Running: false,
|
||||||
|
Enabled: false,
|
||||||
|
ProviderCount: providerCount,
|
||||||
|
Message: "AI 服务未启用",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AIServiceStatus{
|
||||||
|
Running: true,
|
||||||
|
Enabled: true,
|
||||||
|
ProviderCount: providerCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart stops the current AI service and creates a new one from DB
|
||||||
|
func (m *AIManager) Restart() error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if m.service != nil {
|
||||||
|
m.service.Stop()
|
||||||
|
m.service = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
providers, err := m.store.ListLLMProviders(true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("加载 LLM 提供商列表失败: %w", err)
|
||||||
|
}
|
||||||
|
if len(providers) == 0 {
|
||||||
|
return fmt.Errorf("没有配置任何 LLM 提供商,请先添加配置")
|
||||||
|
}
|
||||||
|
|
||||||
|
providerConfigs := make([]llm.ProviderConfig, 0, len(providers))
|
||||||
|
for _, p := range providers {
|
||||||
|
providerConfigs = append(providerConfigs, llm.ProviderConfig{
|
||||||
|
Name: p.Name,
|
||||||
|
Active: p.Active,
|
||||||
|
APIKey: p.APIKey,
|
||||||
|
BaseURL: p.BaseURL,
|
||||||
|
Model: p.Model,
|
||||||
|
Timeout: p.Timeout,
|
||||||
|
ContextWindowTokens: p.ContextWindowTokens,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
m.cfg.LLMProviders = providerConfigs
|
||||||
|
|
||||||
|
svc, err := NewService(m.cfg, m.db, m.botSender)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("创建 AI 服务失败: %w", err)
|
||||||
|
}
|
||||||
|
if err := svc.Start(m.ctx); err != nil {
|
||||||
|
return fmt.Errorf("启动 AI 服务失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.service = svc
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReloadLLMProvider delegates to the current service
|
||||||
|
func (m *AIManager) ReloadLLMProvider(config interface{}) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
svc := m.service
|
||||||
|
m.mu.Unlock()
|
||||||
|
|
||||||
|
if svc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return svc.ReloadLLMProvider(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLLMProvider delegates to the current service
|
||||||
|
func (m *AIManager) AddLLMProvider(config interface{}) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
svc := m.service
|
||||||
|
m.mu.Unlock()
|
||||||
|
|
||||||
|
if svc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return svc.AddLLMProvider(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveLLMProvider delegates to the current service
|
||||||
|
func (m *AIManager) RemoveLLMProvider(name string) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
svc := m.service
|
||||||
|
m.mu.Unlock()
|
||||||
|
|
||||||
|
if svc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return svc.RemoveLLMProvider(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AIServiceStatus returns the AI service status for the web interface
|
||||||
|
func (m *AIManager) AIServiceStatus() AIServiceStatus {
|
||||||
|
return m.Status()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestartAIService restarts the AI service for the web interface
|
||||||
|
func (m *AIManager) RestartAIService() error {
|
||||||
|
return m.Restart()
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
aipkg "meshtastic_mqtt_server/internal/ai"
|
||||||
storepkg "meshtastic_mqtt_server/internal/store"
|
storepkg "meshtastic_mqtt_server/internal/store"
|
||||||
"meshtastic_mqtt_server/internal/webutil"
|
"meshtastic_mqtt_server/internal/webutil"
|
||||||
)
|
)
|
||||||
@@ -18,6 +19,8 @@ type LLMProviderReloader interface {
|
|||||||
ReloadLLMProvider(config interface{}) error
|
ReloadLLMProvider(config interface{}) error
|
||||||
AddLLMProvider(config interface{}) error
|
AddLLMProvider(config interface{}) error
|
||||||
RemoveLLMProvider(name string) error
|
RemoveLLMProvider(name string) error
|
||||||
|
AIServiceStatus() aipkg.AIServiceStatus
|
||||||
|
RestartAIService() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterRoutes(r *gin.RouterGroup, store *storepkg.Store, aiService LLMProviderReloader) {
|
func RegisterRoutes(r *gin.RouterGroup, store *storepkg.Store, aiService LLMProviderReloader) {
|
||||||
@@ -49,6 +52,10 @@ func RegisterRoutes(r *gin.RouterGroup, store *storepkg.Store, aiService LLMProv
|
|||||||
// LLM Primary Config - 主 AI 回复配置
|
// LLM Primary Config - 主 AI 回复配置
|
||||||
group.GET("/primary-config", handleGetLLMPrimaryConfig(store))
|
group.GET("/primary-config", handleGetLLMPrimaryConfig(store))
|
||||||
group.PUT("/primary-config", handleUpdateLLMPrimaryConfig(store))
|
group.PUT("/primary-config", handleUpdateLLMPrimaryConfig(store))
|
||||||
|
|
||||||
|
// AI Service Status
|
||||||
|
group.GET("/status", handleGetAIServiceStatus(aiService))
|
||||||
|
group.POST("/restart", handleRestartAIService(aiService))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -828,3 +835,49 @@ func llmPrimaryConfigDTO(row storepkg.LLMPrimaryConfigRecord) map[string]any {
|
|||||||
"updated_at": row.UpdatedAt,
|
"updated_at": row.UpdatedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// AI Service Status & Restart Handlers
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
func handleGetAIServiceStatus(aiService LLMProviderReloader) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if aiService == nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"running": false,
|
||||||
|
"enabled": false,
|
||||||
|
"provider_count": 0,
|
||||||
|
"message": "AI 服务未初始化",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
status := aiService.AIServiceStatus()
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"running": status.Running,
|
||||||
|
"enabled": status.Enabled,
|
||||||
|
"provider_count": status.ProviderCount,
|
||||||
|
"message": status.Message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRestartAIService(aiService LLMProviderReloader) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if aiService == nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "AI 服务未初始化,无法重启"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := aiService.RestartAIService(); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "重启 AI 服务失败: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
status := aiService.AIServiceStatus()
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": "ok",
|
||||||
|
"running": status.Running,
|
||||||
|
"enabled": status.Enabled,
|
||||||
|
"provider_count": status.ProviderCount,
|
||||||
|
"message": "AI 服务已重启",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
aipkg "meshtastic_mqtt_server/internal/ai"
|
||||||
"meshtastic_mqtt_server/internal/auth"
|
"meshtastic_mqtt_server/internal/auth"
|
||||||
blockingpkg "meshtastic_mqtt_server/internal/blocking"
|
blockingpkg "meshtastic_mqtt_server/internal/blocking"
|
||||||
botpkg "meshtastic_mqtt_server/internal/bot"
|
botpkg "meshtastic_mqtt_server/internal/bot"
|
||||||
@@ -32,6 +33,8 @@ type LLMProviderReloader interface {
|
|||||||
ReloadLLMProvider(config interface{}) error
|
ReloadLLMProvider(config interface{}) error
|
||||||
AddLLMProvider(config interface{}) error
|
AddLLMProvider(config interface{}) error
|
||||||
RemoveLLMProvider(name string) error
|
RemoveLLMProvider(name string) error
|
||||||
|
AIServiceStatus() aipkg.AIServiceStatus
|
||||||
|
RestartAIService() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPServer(cfg configpkg.WebConfig, consoleLog bool, store *storepkg.Store, sessions *auth.Manager, mqttStatus MQTTStatusProvider, blocking *blockingpkg.Cache, forwarder mqttforwardpkg.Reloader, settings *rspkg.Cache, botSender botpkg.TextSender, aiService LLMProviderReloader) *http.Server {
|
func NewHTTPServer(cfg configpkg.WebConfig, consoleLog bool, store *storepkg.Store, sessions *auth.Manager, mqttStatus MQTTStatusProvider, blocking *blockingpkg.Cache, forwarder mqttforwardpkg.Reloader, settings *rspkg.Cache, botSender botpkg.TextSender, aiService LLMProviderReloader) *http.Server {
|
||||||
|
|||||||
@@ -398,16 +398,48 @@ func run(cfg *configpkg.Config) error {
|
|||||||
defer forwardManager.StopAll()
|
defer forwardManager.StopAll()
|
||||||
|
|
||||||
// Initialize AI Service
|
// Initialize AI Service
|
||||||
var aiService *ai.Service
|
// Create bot sender adapter - 支持频道消息和私聊消息两种发送方式
|
||||||
|
botSenderAdapter := autoreply.NewBotServiceAdapter(
|
||||||
|
// SendDirectText: 发送私聊消息
|
||||||
|
func(ctx context.Context, botID uint64, toNodeNum int64, text string) error {
|
||||||
|
_, err := botSender.SendText(ctx, botpkg.SendTextRequest{
|
||||||
|
BotID: botID,
|
||||||
|
MessageType: "direct",
|
||||||
|
ToNodeNum: &toNodeNum,
|
||||||
|
Text: text,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
// SendChannelText: 发送频道消息
|
||||||
|
func(ctx context.Context, botID uint64, channelID string, text string) error {
|
||||||
|
_, err := botSender.SendText(ctx, botpkg.SendTextRequest{
|
||||||
|
BotID: botID,
|
||||||
|
MessageType: "channel",
|
||||||
|
ChannelID: channelID,
|
||||||
|
Text: text,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
aiManager := ai.NewAIManager(ai.Config{
|
||||||
|
DataDir: cfg.AI.DataDir,
|
||||||
|
Enabled: cfg.AI.Enabled,
|
||||||
|
ConsoleLog: cfg.ConsoleLog.LLM,
|
||||||
|
ToolConfigStore: store,
|
||||||
|
ToolRouterStore: store,
|
||||||
|
TopicRouterStore: store,
|
||||||
|
Store: store,
|
||||||
|
}, store.DB(), botSenderAdapter, botCtx, store)
|
||||||
|
|
||||||
if cfg.AI.Enabled {
|
if cfg.AI.Enabled {
|
||||||
// Get LLM providers from database
|
aiManager.SetConfigEnabled(true)
|
||||||
llmProviders, err := store.ListLLMProviders(true)
|
providers, err := store.ListLLMProviders(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Warning: failed to load LLM providers: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Warning: failed to load LLM providers: %v\n", err)
|
||||||
} else if len(llmProviders) > 0 {
|
} else if len(providers) > 0 {
|
||||||
// Convert database records to provider configs
|
providerConfigs := make([]llm.ProviderConfig, 0, len(providers))
|
||||||
providerConfigs := make([]llm.ProviderConfig, 0, len(llmProviders))
|
for _, p := range providers {
|
||||||
for _, p := range llmProviders {
|
|
||||||
providerConfigs = append(providerConfigs, llm.ProviderConfig{
|
providerConfigs = append(providerConfigs, llm.ProviderConfig{
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
Active: p.Active,
|
Active: p.Active,
|
||||||
@@ -418,48 +450,11 @@ func run(cfg *configpkg.Config) error {
|
|||||||
ContextWindowTokens: p.ContextWindowTokens,
|
ContextWindowTokens: p.ContextWindowTokens,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
aiManager.SetProviderConfigs(providerConfigs)
|
||||||
// Create bot sender adapter - 支持频道消息和私聊消息两种发送方式
|
if err := aiManager.Init(); err != nil {
|
||||||
botSenderAdapter := autoreply.NewBotServiceAdapter(
|
|
||||||
// SendDirectText: 发送私聊消息
|
|
||||||
func(ctx context.Context, botID uint64, toNodeNum int64, text string) error {
|
|
||||||
_, err := botSender.SendText(ctx, botpkg.SendTextRequest{
|
|
||||||
BotID: botID,
|
|
||||||
MessageType: "direct",
|
|
||||||
ToNodeNum: &toNodeNum,
|
|
||||||
Text: text,
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
// SendChannelText: 发送频道消息
|
|
||||||
func(ctx context.Context, botID uint64, channelID string, text string) error {
|
|
||||||
_, err := botSender.SendText(ctx, botpkg.SendTextRequest{
|
|
||||||
BotID: botID,
|
|
||||||
MessageType: "channel",
|
|
||||||
ChannelID: channelID,
|
|
||||||
Text: text,
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
aiService, err = ai.NewService(ai.Config{
|
|
||||||
LLMProviders: providerConfigs,
|
|
||||||
DataDir: cfg.AI.DataDir,
|
|
||||||
Enabled: cfg.AI.Enabled,
|
|
||||||
ConsoleLog: cfg.ConsoleLog.LLM,
|
|
||||||
ToolConfigStore: store,
|
|
||||||
ToolRouterStore: store,
|
|
||||||
TopicRouterStore: store,
|
|
||||||
Store: store,
|
|
||||||
}, store.DB(), botSenderAdapter)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Warning: failed to initialize AI service: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Warning: failed to initialize AI service: %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
if err := aiService.Start(botCtx); err != nil {
|
defer aiManager.Stop()
|
||||||
fmt.Fprintf(os.Stderr, "Warning: failed to start AI service: %v\n", err)
|
|
||||||
}
|
|
||||||
defer aiService.Stop()
|
|
||||||
printJSON(map[string]any{"event": "ai_service_started", "providers": len(providerConfigs)})
|
printJSON(map[string]any{"event": "ai_service_started", "providers": len(providerConfigs)})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -475,11 +470,7 @@ func run(cfg *configpkg.Config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
mqttStatus := webpkg.MQTTRuntimeStatus{Server: server, Address: mqttAddr, TLS: cfg.MQTT.TLS.Enabled, Stats: messageStats, ClientStats: clientStats, DBQueue: dbQueue, DedupQueue: mqttHook.dedupQueue}
|
mqttStatus := webpkg.MQTTRuntimeStatus{Server: server, Address: mqttAddr, TLS: cfg.MQTT.TLS.Enabled, Stats: messageStats, ClientStats: clientStats, DBQueue: dbQueue, DedupQueue: mqttHook.dedupQueue}
|
||||||
var llmReloader webpkg.LLMProviderReloader
|
handler := webpkg.NewRouter(cfg.Web, cfg.ConsoleLog.Web, store, sessions, mqttStatus, blocking, forwardManager, settings, botSender, aiManager)
|
||||||
if aiService != nil {
|
|
||||||
llmReloader = aiService
|
|
||||||
}
|
|
||||||
handler := webpkg.NewRouter(cfg.Web, cfg.ConsoleLog.Web, store, sessions, mqttStatus, blocking, forwardManager, settings, botSender, llmReloader)
|
|
||||||
webAddresses := []string{}
|
webAddresses := []string{}
|
||||||
if cfg.Web.PortEnabled {
|
if cfg.Web.PortEnabled {
|
||||||
httpServer := &http.Server{
|
httpServer := &http.Server{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type {
|
|||||||
AdminRuntimeSettingsPayload,
|
AdminRuntimeSettingsPayload,
|
||||||
AdminRuntimeSettingsResponse,
|
AdminRuntimeSettingsResponse,
|
||||||
AdminUsersResponse,
|
AdminUsersResponse,
|
||||||
|
AIServiceStatus,
|
||||||
BotMessage,
|
BotMessage,
|
||||||
BotMessageMutationResponse,
|
BotMessageMutationResponse,
|
||||||
BotNode,
|
BotNode,
|
||||||
@@ -519,6 +520,15 @@ export function deleteLLMProvider(name: string): Promise<{ status: string; warni
|
|||||||
return deleteJSON<{ status: string; warning?: string }>(`/api/admin/llm/providers/${encodeURIComponent(name)}`)
|
return deleteJSON<{ status: string; warning?: string }>(`/api/admin/llm/providers/${encodeURIComponent(name)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AI Service Status API
|
||||||
|
export function getAIServiceStatus(): Promise<AIServiceStatus> {
|
||||||
|
return getJSON<AIServiceStatus>('/api/admin/llm/status')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function restartAIService(): Promise<AIServiceStatus & { status: string; message?: string }> {
|
||||||
|
return postJSON<AIServiceStatus & { status: string; message?: string }>('/api/admin/llm/restart')
|
||||||
|
}
|
||||||
|
|
||||||
// LLM Tool Router API
|
// LLM Tool Router API
|
||||||
export function getLLMToolRouter(): Promise<LLMPlatformRouterResponse> {
|
export function getLLMToolRouter(): Promise<LLMPlatformRouterResponse> {
|
||||||
return getJSON<LLMPlatformRouterResponse>('/api/admin/llm/tool-router')
|
return getJSON<LLMPlatformRouterResponse>('/api/admin/llm/tool-router')
|
||||||
|
|||||||
@@ -11,14 +11,20 @@ import {
|
|||||||
updateLLMToolRouter,
|
updateLLMToolRouter,
|
||||||
updateLLMTopicConfig,
|
updateLLMTopicConfig,
|
||||||
updateLLMPrimaryConfig,
|
updateLLMPrimaryConfig,
|
||||||
|
getAIServiceStatus,
|
||||||
|
restartAIService,
|
||||||
} from '../api'
|
} from '../api'
|
||||||
import type { LLMPlatformRouter, LLMTopicConfig, LLMProvider, LLMPrimaryConfig } from '../types'
|
import type { LLMPlatformRouter, LLMTopicConfig, LLMProvider, LLMPrimaryConfig, AIServiceStatus } from '../types'
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
const success = ref('')
|
const success = ref('')
|
||||||
const showWarning = ref(false)
|
const showWarning = ref(false)
|
||||||
|
|
||||||
|
// AI Service Status
|
||||||
|
const aiStatus = ref<AIServiceStatus>({ running: false, enabled: false, provider_count: 0 })
|
||||||
|
const restarting = ref(false)
|
||||||
|
|
||||||
// LLM Provider 相关
|
// LLM Provider 相关
|
||||||
const providers = ref<LLMProvider[]>([])
|
const providers = ref<LLMProvider[]>([])
|
||||||
const editingProvider = ref<LLMProvider | null>(null)
|
const editingProvider = ref<LLMProvider | null>(null)
|
||||||
@@ -299,7 +305,31 @@ async function savePrimaryConfig() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadAIStatus() {
|
||||||
|
try {
|
||||||
|
aiStatus.value = await getAIServiceStatus()
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Failed to load AI service status', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRestartAI() {
|
||||||
|
if (!confirm('确定要重启 AI 服务吗?')) return
|
||||||
|
restarting.value = true
|
||||||
|
try {
|
||||||
|
const result = await restartAIService()
|
||||||
|
aiStatus.value = result
|
||||||
|
success.value = result.message || 'AI 服务已重启'
|
||||||
|
clearSuccess()
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err instanceof Error ? err.message : String(err)
|
||||||
|
} finally {
|
||||||
|
restarting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
loadAIStatus()
|
||||||
loadProviders()
|
loadProviders()
|
||||||
loadToolRouter()
|
loadToolRouter()
|
||||||
loadTopicConfig()
|
loadTopicConfig()
|
||||||
@@ -311,6 +341,21 @@ onMounted(() => {
|
|||||||
<div class="admin-llm-api">
|
<div class="admin-llm-api">
|
||||||
<h2>LLM API 配置管理</h2>
|
<h2>LLM API 配置管理</h2>
|
||||||
|
|
||||||
|
<div class="ai-status-bar">
|
||||||
|
<span class="status-indicator" :class="{ running: aiStatus.running, stopped: !aiStatus.running }"></span>
|
||||||
|
<span class="status-text">
|
||||||
|
<template v-if="aiStatus.running">AI 服务运行中,{{ aiStatus.provider_count }} 个提供商</template>
|
||||||
|
<template v-else>{{ aiStatus.message || 'AI 服务未运行' }}</template>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="admin-button admin-button-small"
|
||||||
|
:disabled="restarting"
|
||||||
|
@click="handleRestartAI"
|
||||||
|
>
|
||||||
|
{{ restarting ? '重启中...' : '重启 AI 服务' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p v-if="error" class="error">{{ error }}</p>
|
<p v-if="error" class="error">{{ error }}</p>
|
||||||
<p v-if="success" :class="showWarning ? 'warning' : 'success'">{{ success }}</p>
|
<p v-if="success" :class="showWarning ? 'warning' : 'success'">{{ success }}</p>
|
||||||
|
|
||||||
@@ -703,13 +748,49 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.admin-llm-api h2 {
|
.admin-llm-api h2 {
|
||||||
margin: 0 0 2rem;
|
margin: 0 0 1rem;
|
||||||
font-size: 1.75rem;
|
font-size: 1.75rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #1e293b;
|
color: #1e293b;
|
||||||
letter-spacing: -0.02em;
|
letter-spacing: -0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ai-status-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.75rem 1.25rem;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator.running {
|
||||||
|
background: #22c55e;
|
||||||
|
box-shadow: 0 0 6px rgba(34, 197, 94, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator.stopped {
|
||||||
|
background: #ef4444;
|
||||||
|
box-shadow: 0 0 6px rgba(239, 68, 68, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #475569;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.admin-section {
|
.admin-section {
|
||||||
background: white;
|
background: white;
|
||||||
padding: 1.75rem;
|
padding: 1.75rem;
|
||||||
|
|||||||
@@ -647,6 +647,13 @@ export interface LLMProviderResponse {
|
|||||||
warning?: string
|
warning?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AIServiceStatus {
|
||||||
|
running: boolean
|
||||||
|
enabled: boolean
|
||||||
|
provider_count: number
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
|
||||||
// LLM Tool Router 相关类型
|
// LLM Tool Router 相关类型
|
||||||
export interface LLMPlatformRouter {
|
export interface LLMPlatformRouter {
|
||||||
id: number
|
id: number
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
|
db_config.py
|
||||||
@@ -26,27 +26,7 @@ from typing import Any
|
|||||||
import pymysql
|
import pymysql
|
||||||
import pymysql.cursors
|
import pymysql.cursors
|
||||||
|
|
||||||
# ============================================================
|
from db_config import SOURCE_CONFIG, TARGET_CONFIG
|
||||||
# 硬编码数据库配置
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
SOURCE_CONFIG = {
|
|
||||||
"host": "127.0.0.1",
|
|
||||||
"port": 3306,
|
|
||||||
"user": "root",
|
|
||||||
"password": "PLEASE_CHANGE_ME",
|
|
||||||
"database": "meshtastic",
|
|
||||||
"charset": "utf8mb4",
|
|
||||||
}
|
|
||||||
|
|
||||||
TARGET_CONFIG = {
|
|
||||||
"host": "127.0.0.1",
|
|
||||||
"port": 3307,
|
|
||||||
"user": "root",
|
|
||||||
"password": "PLEASE_CHANGE_ME",
|
|
||||||
"database": "meshtastic",
|
|
||||||
"charset": "utf8mb4",
|
|
||||||
}
|
|
||||||
|
|
||||||
BATCH_SIZE = 5000
|
BATCH_SIZE = 5000
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user