mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-23 17:57:28 +08:00
hide unavailable channels in webui
This commit is contained in:
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -83,10 +83,7 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: release-artifacts
|
name: release-artifacts
|
||||||
path: |
|
path: build
|
||||||
build/*.tar.gz
|
|
||||||
build/*.zip
|
|
||||||
build/checksums.txt
|
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
publish-release:
|
publish-release:
|
||||||
@@ -100,6 +97,9 @@ jobs:
|
|||||||
name: release-artifacts
|
name: release-artifacts
|
||||||
path: build
|
path: build
|
||||||
|
|
||||||
|
- name: List downloaded artifacts
|
||||||
|
run: find build -maxdepth 4 -type f | sort
|
||||||
|
|
||||||
- name: Resolve tag
|
- name: Resolve tag
|
||||||
id: tag
|
id: tag
|
||||||
run: |
|
run: |
|
||||||
@@ -116,6 +116,6 @@ jobs:
|
|||||||
name: ${{ steps.tag.outputs.name }}
|
name: ${{ steps.tag.outputs.name }}
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
build/*.tar.gz
|
build/**/*.tar.gz
|
||||||
build/*.zip
|
build/**/*.zip
|
||||||
build/checksums.txt
|
build/**/checksums.txt
|
||||||
|
|||||||
@@ -1188,9 +1188,10 @@ func (s *Server) handleWebUIVersion(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"gateway_version": firstNonEmptyString(s.gatewayVersion, gatewayBuildVersion()),
|
"gateway_version": firstNonEmptyString(s.gatewayVersion, gatewayBuildVersion()),
|
||||||
"webui_version": firstNonEmptyString(s.webuiVersion, detectWebUIVersion(strings.TrimSpace(s.webUIDir))),
|
"webui_version": firstNonEmptyString(s.webuiVersion, detectWebUIVersion(strings.TrimSpace(s.webUIDir))),
|
||||||
|
"compiled_channels": channels.CompiledChannelKeys(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1523,8 +1524,9 @@ func (s *Server) webUISubagentsRuntimePayload(ctx context.Context) map[string]in
|
|||||||
|
|
||||||
func (s *Server) webUIVersionPayload() map[string]interface{} {
|
func (s *Server) webUIVersionPayload() map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"gateway_version": firstNonEmptyString(s.gatewayVersion, gatewayBuildVersion()),
|
"gateway_version": firstNonEmptyString(s.gatewayVersion, gatewayBuildVersion()),
|
||||||
"webui_version": firstNonEmptyString(s.webuiVersion, detectWebUIVersion(strings.TrimSpace(s.webUIDir))),
|
"webui_version": firstNonEmptyString(s.webuiVersion, detectWebUIVersion(strings.TrimSpace(s.webUIDir))),
|
||||||
|
"compiled_channels": channels.CompiledChannelKeys(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
30
pkg/channels/compiled_channels.go
Normal file
30
pkg/channels/compiled_channels.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package channels
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
|
func CompiledChannelKeys() []string {
|
||||||
|
out := make([]string, 0, 7)
|
||||||
|
if telegramCompiled {
|
||||||
|
out = append(out, "telegram")
|
||||||
|
}
|
||||||
|
if whatsappCompiled {
|
||||||
|
out = append(out, "whatsapp")
|
||||||
|
}
|
||||||
|
if discordCompiled {
|
||||||
|
out = append(out, "discord")
|
||||||
|
}
|
||||||
|
if feishuCompiled {
|
||||||
|
out = append(out, "feishu")
|
||||||
|
}
|
||||||
|
if qqCompiled {
|
||||||
|
out = append(out, "qq")
|
||||||
|
}
|
||||||
|
if dingtalkCompiled {
|
||||||
|
out = append(out, "dingtalk")
|
||||||
|
}
|
||||||
|
if maixcamCompiled {
|
||||||
|
out = append(out, "maixcam")
|
||||||
|
}
|
||||||
|
sort.Strings(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
@@ -30,6 +30,8 @@ type DingTalkChannel struct {
|
|||||||
sessionWebhooks sync.Map // chatID -> sessionWebhook
|
sessionWebhooks sync.Map // chatID -> sessionWebhook
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dingtalkCompiled = true
|
||||||
|
|
||||||
// NewDingTalkChannel creates a new DingTalk channel instance
|
// NewDingTalkChannel creates a new DingTalk channel instance
|
||||||
func NewDingTalkChannel(cfg config.DingTalkConfig, messageBus *bus.MessageBus) (*DingTalkChannel, error) {
|
func NewDingTalkChannel(cfg config.DingTalkConfig, messageBus *bus.MessageBus) (*DingTalkChannel, error) {
|
||||||
if cfg.ClientID == "" || cfg.ClientSecret == "" {
|
if cfg.ClientID == "" || cfg.ClientSecret == "" {
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
|
|
||||||
type DingTalkChannel struct{ disabledChannel }
|
type DingTalkChannel struct{ disabledChannel }
|
||||||
|
|
||||||
|
const dingtalkCompiled = false
|
||||||
|
|
||||||
func NewDingTalkChannel(cfg config.DingTalkConfig, bus *bus.MessageBus) (*DingTalkChannel, error) {
|
func NewDingTalkChannel(cfg config.DingTalkConfig, bus *bus.MessageBus) (*DingTalkChannel, error) {
|
||||||
return nil, errChannelDisabled("dingtalk")
|
return nil, errChannelDisabled("dingtalk")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ type DiscordChannel struct {
|
|||||||
config config.DiscordConfig
|
config config.DiscordConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const discordCompiled = true
|
||||||
|
|
||||||
func NewDiscordChannel(cfg config.DiscordConfig, bus *bus.MessageBus) (*DiscordChannel, error) {
|
func NewDiscordChannel(cfg config.DiscordConfig, bus *bus.MessageBus) (*DiscordChannel, error) {
|
||||||
session, err := discordgo.New("Bot " + cfg.Token)
|
session, err := discordgo.New("Bot " + cfg.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
|
|
||||||
type DiscordChannel struct{ disabledChannel }
|
type DiscordChannel struct{ disabledChannel }
|
||||||
|
|
||||||
|
const discordCompiled = false
|
||||||
|
|
||||||
func NewDiscordChannel(cfg config.DiscordConfig, bus *bus.MessageBus) (*DiscordChannel, error) {
|
func NewDiscordChannel(cfg config.DiscordConfig, bus *bus.MessageBus) (*DiscordChannel, error) {
|
||||||
return nil, errChannelDisabled("discord")
|
return nil, errChannelDisabled("discord")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ type FeishuChannel struct {
|
|||||||
tenantTokenErr error
|
tenantTokenErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const feishuCompiled = true
|
||||||
|
|
||||||
func (c *FeishuChannel) SupportsAction(action string) bool {
|
func (c *FeishuChannel) SupportsAction(action string) bool {
|
||||||
switch strings.ToLower(strings.TrimSpace(action)) {
|
switch strings.ToLower(strings.TrimSpace(action)) {
|
||||||
case "", "send":
|
case "", "send":
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
|
|
||||||
type FeishuChannel struct{ disabledChannel }
|
type FeishuChannel struct{ disabledChannel }
|
||||||
|
|
||||||
|
const feishuCompiled = false
|
||||||
|
|
||||||
func NewFeishuChannel(cfg config.FeishuConfig, bus *bus.MessageBus) (*FeishuChannel, error) {
|
func NewFeishuChannel(cfg config.FeishuConfig, bus *bus.MessageBus) (*FeishuChannel, error) {
|
||||||
return nil, errChannelDisabled("feishu")
|
return nil, errChannelDisabled("feishu")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ type MaixCamChannel struct {
|
|||||||
clientsMux sync.RWMutex
|
clientsMux sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maixcamCompiled = true
|
||||||
|
|
||||||
type MaixCamMessage struct {
|
type MaixCamMessage struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Tips string `json:"tips"`
|
Tips string `json:"tips"`
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
|
|
||||||
type MaixCamChannel struct{ disabledChannel }
|
type MaixCamChannel struct{ disabledChannel }
|
||||||
|
|
||||||
|
const maixcamCompiled = false
|
||||||
|
|
||||||
func NewMaixCamChannel(cfg config.MaixCamConfig, bus *bus.MessageBus) (*MaixCamChannel, error) {
|
func NewMaixCamChannel(cfg config.MaixCamConfig, bus *bus.MessageBus) (*MaixCamChannel, error) {
|
||||||
return nil, errChannelDisabled("maixcam")
|
return nil, errChannelDisabled("maixcam")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ type QQChannel struct {
|
|||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const qqCompiled = true
|
||||||
|
|
||||||
func NewQQChannel(cfg config.QQConfig, messageBus *bus.MessageBus) (*QQChannel, error) {
|
func NewQQChannel(cfg config.QQConfig, messageBus *bus.MessageBus) (*QQChannel, error) {
|
||||||
base := NewBaseChannel("qq", cfg, messageBus, cfg.AllowFrom)
|
base := NewBaseChannel("qq", cfg, messageBus, cfg.AllowFrom)
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
|
|
||||||
type QQChannel struct{ disabledChannel }
|
type QQChannel struct{ disabledChannel }
|
||||||
|
|
||||||
|
const qqCompiled = false
|
||||||
|
|
||||||
func NewQQChannel(cfg config.QQConfig, bus *bus.MessageBus) (*QQChannel, error) {
|
func NewQQChannel(cfg config.QQConfig, bus *bus.MessageBus) (*QQChannel, error) {
|
||||||
return nil, errChannelDisabled("qq")
|
return nil, errChannelDisabled("qq")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ const (
|
|||||||
telegramStreamMaxRetries = 4
|
telegramStreamMaxRetries = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const telegramCompiled = true
|
||||||
|
|
||||||
type TelegramChannel struct {
|
type TelegramChannel struct {
|
||||||
*BaseChannel
|
*BaseChannel
|
||||||
bot *telego.Bot
|
bot *telego.Bot
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
|
|
||||||
type TelegramChannel struct{ disabledChannel }
|
type TelegramChannel struct{ disabledChannel }
|
||||||
|
|
||||||
|
const telegramCompiled = false
|
||||||
|
|
||||||
func NewTelegramChannel(cfg config.TelegramConfig, bus *bus.MessageBus) (*TelegramChannel, error) {
|
func NewTelegramChannel(cfg config.TelegramConfig, bus *bus.MessageBus) (*TelegramChannel, error) {
|
||||||
return nil, errChannelDisabled("telegram")
|
return nil, errChannelDisabled("telegram")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ type WhatsAppChannel struct {
|
|||||||
connected bool
|
connected bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const whatsappCompiled = true
|
||||||
|
|
||||||
func NewWhatsAppChannel(cfg config.WhatsAppConfig, bus *bus.MessageBus) (*WhatsAppChannel, error) {
|
func NewWhatsAppChannel(cfg config.WhatsAppConfig, bus *bus.MessageBus) (*WhatsAppChannel, error) {
|
||||||
base := NewBaseChannel("whatsapp", cfg, bus, cfg.AllowFrom)
|
base := NewBaseChannel("whatsapp", cfg, bus, cfg.AllowFrom)
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
|
|
||||||
type WhatsAppChannel struct{ disabledChannel }
|
type WhatsAppChannel struct{ disabledChannel }
|
||||||
|
|
||||||
|
const whatsappCompiled = false
|
||||||
|
|
||||||
func NewWhatsAppChannel(cfg config.WhatsAppConfig, bus *bus.MessageBus) (*WhatsAppChannel, error) {
|
func NewWhatsAppChannel(cfg config.WhatsAppConfig, bus *bus.MessageBus) (*WhatsAppChannel, error) {
|
||||||
return nil, errChannelDisabled("whatsapp")
|
return nil, errChannelDisabled("whatsapp")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import NavItem from './NavItem';
|
|||||||
|
|
||||||
const Sidebar: React.FC = () => {
|
const Sidebar: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { token, setToken, sidebarOpen, sidebarCollapsed, setSidebarCollapsed } = useAppContext();
|
const { token, setToken, sidebarOpen, sidebarCollapsed, setSidebarCollapsed, compiledChannels } = useAppContext();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [expandedSections, setExpandedSections] = React.useState<Record<string, boolean>>({
|
const [expandedSections, setExpandedSections] = React.useState<Record<string, boolean>>({
|
||||||
main: true,
|
main: true,
|
||||||
@@ -19,6 +19,16 @@ const Sidebar: React.FC = () => {
|
|||||||
channels: false,
|
channels: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const channelChildren = [
|
||||||
|
{ id: 'whatsapp', icon: <Smartphone className="w-4 h-4" />, label: t('whatsappBridge'), to: '/channels/whatsapp' },
|
||||||
|
{ id: 'telegram', icon: <Radio className="w-4 h-4" />, label: t('telegram'), to: '/channels/telegram' },
|
||||||
|
{ id: 'discord', icon: <MonitorSmartphone className="w-4 h-4" />, label: t('discord'), to: '/channels/discord' },
|
||||||
|
{ id: 'feishu', icon: <MonitorSmartphone className="w-4 h-4" />, label: t('feishu'), to: '/channels/feishu' },
|
||||||
|
{ id: 'qq', icon: <MonitorSmartphone className="w-4 h-4" />, label: t('qq'), to: '/channels/qq' },
|
||||||
|
{ id: 'dingtalk', icon: <MonitorSmartphone className="w-4 h-4" />, label: t('dingtalk'), to: '/channels/dingtalk' },
|
||||||
|
{ id: 'maixcam', icon: <MonitorSmartphone className="w-4 h-4" />, label: t('maixcam'), to: '/channels/maixcam' },
|
||||||
|
].filter((item) => compiledChannels.includes(item.id));
|
||||||
|
|
||||||
const sections = [
|
const sections = [
|
||||||
{
|
{
|
||||||
id: 'main',
|
id: 'main',
|
||||||
@@ -54,15 +64,7 @@ const Sidebar: React.FC = () => {
|
|||||||
icon: <Smartphone className="w-5 h-5" />,
|
icon: <Smartphone className="w-5 h-5" />,
|
||||||
label: t('channelsGroup'),
|
label: t('channelsGroup'),
|
||||||
childrenId: 'channels',
|
childrenId: 'channels',
|
||||||
children: [
|
children: channelChildren,
|
||||||
{ icon: <Smartphone className="w-4 h-4" />, label: t('whatsappBridge'), to: '/channels/whatsapp' },
|
|
||||||
{ icon: <Radio className="w-4 h-4" />, label: t('telegram'), to: '/channels/telegram' },
|
|
||||||
{ icon: <MonitorSmartphone className="w-4 h-4" />, label: t('discord'), to: '/channels/discord' },
|
|
||||||
{ icon: <MonitorSmartphone className="w-4 h-4" />, label: t('feishu'), to: '/channels/feishu' },
|
|
||||||
{ icon: <MonitorSmartphone className="w-4 h-4" />, label: t('qq'), to: '/channels/qq' },
|
|
||||||
{ icon: <MonitorSmartphone className="w-4 h-4" />, label: t('dingtalk'), to: '/channels/dingtalk' },
|
|
||||||
{ icon: <MonitorSmartphone className="w-4 h-4" />, label: t('maixcam'), to: '/channels/maixcam' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{ icon: <Plug className="w-5 h-5" />, label: t('mcpServices'), to: '/mcp' },
|
{ icon: <Plug className="w-5 h-5" />, label: t('mcpServices'), to: '/mcp' },
|
||||||
{ icon: <Clock className="w-5 h-5" />, label: t('cronJobs'), to: '/cron' },
|
{ icon: <Clock className="w-5 h-5" />, label: t('cronJobs'), to: '/cron' },
|
||||||
@@ -86,6 +88,10 @@ const Sidebar: React.FC = () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
const normalizedSections = sections.map((sec) => ({
|
||||||
|
...sec,
|
||||||
|
items: sec.items.filter((item: any) => !item.children || item.children.length > 0),
|
||||||
|
}));
|
||||||
|
|
||||||
const toggle = (id: string) => setExpandedSections((prev) => ({ ...prev, [id]: !prev[id] }));
|
const toggle = (id: string) => setExpandedSections((prev) => ({ ...prev, [id]: !prev[id] }));
|
||||||
const isSubmenuActive = (items: Array<{ to: string }>) => items.some((item) => location.pathname === item.to);
|
const isSubmenuActive = (items: Array<{ to: string }>) => items.some((item) => location.pathname === item.to);
|
||||||
@@ -93,7 +99,7 @@ const Sidebar: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<aside className={`sidebar-shell fixed md:static inset-y-14 md:inset-y-16 left-0 z-40 ${sidebarCollapsed ? 'md:w-20' : 'md:w-64'} w-[86vw] max-w-72 border-r border-zinc-800 backdrop-blur-xl flex flex-col shrink-0 transform transition-all duration-200 ${sidebarOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}`}>
|
<aside className={`sidebar-shell fixed md:static inset-y-14 md:inset-y-16 left-0 z-40 ${sidebarCollapsed ? 'md:w-20' : 'md:w-64'} w-[86vw] max-w-72 border-r border-zinc-800 backdrop-blur-xl flex flex-col shrink-0 transform transition-all duration-200 ${sidebarOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}`}>
|
||||||
<nav className={`flex-1 ${sidebarCollapsed ? 'px-2' : 'px-3'} py-3 space-y-2 overflow-y-auto`}>
|
<nav className={`flex-1 ${sidebarCollapsed ? 'px-2' : 'px-3'} py-3 space-y-2 overflow-y-auto`}>
|
||||||
{sections.map((sec) => (
|
{normalizedSections.map((sec) => (
|
||||||
<div key={sec.title} className={`sidebar-section rounded-2xl border border-zinc-800/60 ${sidebarCollapsed ? 'p-2' : 'p-2'}`}>
|
<div key={sec.title} className={`sidebar-section rounded-2xl border border-zinc-800/60 ${sidebarCollapsed ? 'p-2' : 'p-2'}`}>
|
||||||
{!sidebarCollapsed && (
|
{!sidebarCollapsed && (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ type RuntimeSnapshot = {
|
|||||||
version?: {
|
version?: {
|
||||||
gateway_version?: string;
|
gateway_version?: string;
|
||||||
webui_version?: string;
|
webui_version?: string;
|
||||||
|
compiled_channels?: string[];
|
||||||
};
|
};
|
||||||
nodes?: {
|
nodes?: {
|
||||||
nodes?: any[];
|
nodes?: any[];
|
||||||
@@ -84,6 +85,7 @@ interface AppContextType {
|
|||||||
loadConfig: (force?: boolean) => Promise<void>;
|
loadConfig: (force?: boolean) => Promise<void>;
|
||||||
gatewayVersion: string;
|
gatewayVersion: string;
|
||||||
webuiVersion: string;
|
webuiVersion: string;
|
||||||
|
compiledChannels: string[];
|
||||||
hotReloadFields: string[];
|
hotReloadFields: string[];
|
||||||
hotReloadFieldDetails: Array<{ path: string; name?: string; description?: string }>;
|
hotReloadFieldDetails: Array<{ path: string; name?: string; description?: string }>;
|
||||||
q: string;
|
q: string;
|
||||||
@@ -131,6 +133,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
const [subagentStreamItems, setSubagentStreamItems] = useState<any[]>([]);
|
const [subagentStreamItems, setSubagentStreamItems] = useState<any[]>([]);
|
||||||
const [gatewayVersion, setGatewayVersion] = useState('unknown');
|
const [gatewayVersion, setGatewayVersion] = useState('unknown');
|
||||||
const [webuiVersion, setWebuiVersion] = useState('unknown');
|
const [webuiVersion, setWebuiVersion] = useState('unknown');
|
||||||
|
const [compiledChannels, setCompiledChannels] = useState<string[]>(['telegram', 'whatsapp', 'discord', 'feishu', 'qq', 'dingtalk', 'maixcam']);
|
||||||
const [hotReloadFields, setHotReloadFields] = useState<string[]>([]);
|
const [hotReloadFields, setHotReloadFields] = useState<string[]>([]);
|
||||||
const [hotReloadFieldDetails, setHotReloadFieldDetails] = useState<Array<{ path: string; name?: string; description?: string }>>([]);
|
const [hotReloadFieldDetails, setHotReloadFieldDetails] = useState<Array<{ path: string; name?: string; description?: string }>>([]);
|
||||||
|
|
||||||
@@ -237,6 +240,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
const j = await r.json();
|
const j = await r.json();
|
||||||
setGatewayVersion(j.gateway_version || 'unknown');
|
setGatewayVersion(j.gateway_version || 'unknown');
|
||||||
setWebuiVersion(j.webui_version || 'unknown');
|
setWebuiVersion(j.webui_version || 'unknown');
|
||||||
|
setCompiledChannels(Array.isArray(j.compiled_channels) ? j.compiled_channels : []);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
@@ -281,6 +285,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
if (snapshot.version) {
|
if (snapshot.version) {
|
||||||
setGatewayVersion(snapshot.version.gateway_version || 'unknown');
|
setGatewayVersion(snapshot.version.gateway_version || 'unknown');
|
||||||
setWebuiVersion(snapshot.version.webui_version || 'unknown');
|
setWebuiVersion(snapshot.version.webui_version || 'unknown');
|
||||||
|
setCompiledChannels(Array.isArray(snapshot.version.compiled_channels) ? snapshot.version.compiled_channels : []);
|
||||||
}
|
}
|
||||||
if (snapshot.nodes) {
|
if (snapshot.nodes) {
|
||||||
setNodes(JSON.stringify(Array.isArray(snapshot.nodes.nodes) ? snapshot.nodes.nodes : [], null, 2));
|
setNodes(JSON.stringify(Array.isArray(snapshot.nodes.nodes) ? snapshot.nodes.nodes : [], null, 2));
|
||||||
@@ -373,7 +378,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
taskQueueItems, setTaskQueueItems, ekgSummary, setEkgSummary,
|
taskQueueItems, setTaskQueueItems, ekgSummary, setEkgSummary,
|
||||||
subagentRuntimeItems, setSubagentRuntimeItems, subagentRegistryItems, setSubagentRegistryItems, subagentStreamItems, setSubagentStreamItems,
|
subagentRuntimeItems, setSubagentRuntimeItems, subagentRegistryItems, setSubagentRegistryItems, subagentStreamItems, setSubagentStreamItems,
|
||||||
refreshAll, refreshCron, refreshNodes, refreshSkills, refreshSessions, refreshTaskQueue, refreshEKGSummary, refreshVersion, loadConfig,
|
refreshAll, refreshCron, refreshNodes, refreshSkills, refreshSessions, refreshTaskQueue, refreshEKGSummary, refreshVersion, loadConfig,
|
||||||
gatewayVersion, webuiVersion, hotReloadFields, hotReloadFieldDetails, q
|
gatewayVersion, webuiVersion, compiledChannels, hotReloadFields, hotReloadFieldDetails, q
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
|
|||||||
@@ -400,8 +400,13 @@ const ChannelSettings: React.FC = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const ui = useUI();
|
const ui = useUI();
|
||||||
const { cfg, setCfg, q, loadConfig } = useAppContext();
|
const { cfg, setCfg, q, loadConfig, compiledChannels } = useAppContext();
|
||||||
const key = (channelId || 'whatsapp') as ChannelKey;
|
const availableChannelKeys = useMemo(
|
||||||
|
() => (Object.keys(channelDefinitions) as ChannelKey[]).filter((item) => compiledChannels.includes(item)),
|
||||||
|
[compiledChannels],
|
||||||
|
);
|
||||||
|
const fallbackChannel = availableChannelKeys[0];
|
||||||
|
const key = (channelId || fallbackChannel || 'whatsapp') as ChannelKey;
|
||||||
const definition = channelDefinitions[key];
|
const definition = channelDefinitions[key];
|
||||||
|
|
||||||
const [draft, setDraft] = useState<Record<string, any>>({});
|
const [draft, setDraft] = useState<Record<string, any>>({});
|
||||||
@@ -409,13 +414,17 @@ const ChannelSettings: React.FC = () => {
|
|||||||
const [waStatus, setWaStatus] = useState<WhatsAppStatusPayload | null>(null);
|
const [waStatus, setWaStatus] = useState<WhatsAppStatusPayload | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!definition) {
|
if (!fallbackChannel) {
|
||||||
navigate('/channels/whatsapp', { replace: true });
|
navigate('/config', { replace: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!definition || !availableChannelKeys.includes(key)) {
|
||||||
|
navigate(`/channels/${fallbackChannel}`, { replace: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const next = clone(((cfg as any)?.channels?.[definition.id] || {}) as Record<string, any>);
|
const next = clone(((cfg as any)?.channels?.[definition.id] || {}) as Record<string, any>);
|
||||||
setDraft(next);
|
setDraft(next);
|
||||||
}, [cfg, definition, navigate]);
|
}, [availableChannelKeys, cfg, definition, fallbackChannel, key, navigate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (key !== 'whatsapp') return;
|
if (key !== 'whatsapp') return;
|
||||||
@@ -443,7 +452,7 @@ const ChannelSettings: React.FC = () => {
|
|||||||
return `/webui/api/whatsapp/qr.svg${q}${sep}ts=${encodeURIComponent(String(updatedAt))}`;
|
return `/webui/api/whatsapp/qr.svg${q}${sep}ts=${encodeURIComponent(String(updatedAt))}`;
|
||||||
}, [q, waStatus?.status?.updated_at]);
|
}, [q, waStatus?.status?.updated_at]);
|
||||||
|
|
||||||
if (!definition) return null;
|
if (!definition || !availableChannelKeys.includes(key)) return null;
|
||||||
|
|
||||||
const saveChannel = async () => {
|
const saveChannel = async () => {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user