mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-13 06:00:58 +00:00
feat: displaying plugin debug info (#1828)
This commit is contained in:
committed by
GitHub
parent
0ddc3d60e7
commit
e49a161d0a
@@ -21,6 +21,22 @@ class PluginsRouterGroup(group.RouterGroup):
|
|||||||
|
|
||||||
return self.success(data={'plugins': plugins})
|
return self.success(data={'plugins': plugins})
|
||||||
|
|
||||||
|
@self.route('/debug-info', methods=['GET'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
|
||||||
|
async def _() -> str:
|
||||||
|
"""Get plugin debug information including debug URL and key"""
|
||||||
|
debug_info = await self.ap.plugin_connector.get_debug_info()
|
||||||
|
|
||||||
|
# Get debug URL from config
|
||||||
|
plugin_config = self.ap.instance_config.data.get('plugin', {})
|
||||||
|
debug_url = plugin_config.get('display_plugin_debug_url', 'http://localhost:5401')
|
||||||
|
|
||||||
|
return self.success(
|
||||||
|
data={
|
||||||
|
'debug_url': debug_url,
|
||||||
|
'plugin_debug_key': debug_info.get('plugin_debug_key', ''),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@self.route(
|
@self.route(
|
||||||
'/<author>/<plugin_name>/upgrade',
|
'/<author>/<plugin_name>/upgrade',
|
||||||
methods=['POST'],
|
methods=['POST'],
|
||||||
|
|||||||
@@ -385,6 +385,12 @@ class PluginRuntimeConnector:
|
|||||||
async def get_plugin_assets(self, plugin_author: str, plugin_name: str, filepath: str) -> dict[str, Any]:
|
async def get_plugin_assets(self, plugin_author: str, plugin_name: str, filepath: str) -> dict[str, Any]:
|
||||||
return await self.handler.get_plugin_assets(plugin_author, plugin_name, filepath)
|
return await self.handler.get_plugin_assets(plugin_author, plugin_name, filepath)
|
||||||
|
|
||||||
|
async def get_debug_info(self) -> dict[str, Any]:
|
||||||
|
"""Get debug information including debug key and WS URL"""
|
||||||
|
if not self.is_enable_plugin:
|
||||||
|
return {}
|
||||||
|
return await self.handler.get_debug_info()
|
||||||
|
|
||||||
async def emit_event(
|
async def emit_event(
|
||||||
self,
|
self,
|
||||||
event: events.BaseEventModel,
|
event: events.BaseEventModel,
|
||||||
|
|||||||
@@ -758,3 +758,12 @@ class RuntimeConnectionHandler(handler.Handler):
|
|||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
async def get_debug_info(self) -> dict[str, Any]:
|
||||||
|
"""Get debug information including debug key and WS URL"""
|
||||||
|
result = await self.call_action(
|
||||||
|
LangBotToRuntimeAction.GET_DEBUG_INFO,
|
||||||
|
{},
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|||||||
@@ -48,3 +48,4 @@ plugin:
|
|||||||
runtime_ws_url: 'ws://langbot_plugin_runtime:5400/control/ws'
|
runtime_ws_url: 'ws://langbot_plugin_runtime:5400/control/ws'
|
||||||
enable_marketplace: true
|
enable_marketplace: true
|
||||||
cloud_service_url: 'https://space.langbot.app'
|
cloud_service_url: 'https://space.langbot.app'
|
||||||
|
display_plugin_debug_url: 'http://localhost:5401'
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ import {
|
|||||||
Power,
|
Power,
|
||||||
Github,
|
Github,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
|
Code,
|
||||||
|
Copy,
|
||||||
|
Bug,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -38,6 +41,11 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/ui/popover';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import React, { useState, useRef, useCallback, useEffect } from 'react';
|
import React, { useState, useRef, useCallback, useEffect } from 'react';
|
||||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||||
@@ -105,6 +113,11 @@ export default function PluginConfigPage() {
|
|||||||
);
|
);
|
||||||
const [isEditMode, setIsEditMode] = useState(false);
|
const [isEditMode, setIsEditMode] = useState(false);
|
||||||
const [refreshKey, setRefreshKey] = useState(0);
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
|
const [debugInfo, setDebugInfo] = useState<{
|
||||||
|
debug_url: string;
|
||||||
|
plugin_debug_key: string;
|
||||||
|
} | null>(null);
|
||||||
|
const [debugPopoverOpen, setDebugPopoverOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchPluginSystemStatus = async () => {
|
const fetchPluginSystemStatus = async () => {
|
||||||
@@ -374,6 +387,22 @@ export default function PluginConfigPage() {
|
|||||||
[uploadPluginFile, isPluginSystemReady, t],
|
[uploadPluginFile, isPluginSystemReady, t],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleShowDebugInfo = async () => {
|
||||||
|
try {
|
||||||
|
const info = await httpClient.getPluginDebugInfo();
|
||||||
|
setDebugInfo(info);
|
||||||
|
setDebugPopoverOpen(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch debug info:', error);
|
||||||
|
toast.error(t('plugins.failedToGetDebugInfo'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCopyDebugInfo = (text: string) => {
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
toast.success(t('plugins.copiedToClipboard'));
|
||||||
|
};
|
||||||
|
|
||||||
const renderPluginDisabledState = () => (
|
const renderPluginDisabledState = () => (
|
||||||
<div className="flex flex-col items-center justify-center h-[60vh] text-center pt-[10vh]">
|
<div className="flex flex-col items-center justify-center h-[60vh] text-center pt-[10vh]">
|
||||||
<Power className="w-16 h-16 text-gray-400 mb-4" />
|
<Power className="w-16 h-16 text-gray-400 mb-4" />
|
||||||
@@ -466,7 +495,92 @@ export default function PluginConfigPage() {
|
|||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<div className="flex flex-row justify-end items-center">
|
<div className="flex flex-row justify-end items-center gap-2">
|
||||||
|
{activeTab === 'installed' && (
|
||||||
|
<Popover
|
||||||
|
open={debugPopoverOpen}
|
||||||
|
onOpenChange={setDebugPopoverOpen}
|
||||||
|
>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="px-4 py-5 cursor-pointer"
|
||||||
|
onClick={handleShowDebugInfo}
|
||||||
|
>
|
||||||
|
<Code className="w-4 h-4 mr-2" />
|
||||||
|
{t('plugins.debugInfo')}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[380px]" align="end">
|
||||||
|
<div className="space-y-3">
|
||||||
|
{/* Header with icon and title */}
|
||||||
|
<div className="flex items-center gap-2 pb-2 border-b">
|
||||||
|
<Bug className="w-4 h-4" />
|
||||||
|
<h4 className="font-semibold text-sm">
|
||||||
|
{t('plugins.debugInfoTitle')}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Debug URL row */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<label className="text-sm font-medium whitespace-nowrap min-w-[50px]">
|
||||||
|
{t('plugins.debugUrl')}:
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={debugInfo?.debug_url || ''}
|
||||||
|
readOnly
|
||||||
|
className="w-[220px] font-mono text-xs h-8"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 shrink-0"
|
||||||
|
onClick={() =>
|
||||||
|
handleCopyDebugInfo(debugInfo?.debug_url || '')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Copy className="w-3.5 h-3.5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Debug Key row */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<label className="text-sm font-medium whitespace-nowrap min-w-[50px]">
|
||||||
|
{t('plugins.debugKey')}:
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={
|
||||||
|
debugInfo?.plugin_debug_key ||
|
||||||
|
t('plugins.noDebugKey')
|
||||||
|
}
|
||||||
|
readOnly
|
||||||
|
className="w-[220px] font-mono text-xs h-8"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 shrink-0"
|
||||||
|
onClick={() =>
|
||||||
|
handleCopyDebugInfo(
|
||||||
|
debugInfo?.plugin_debug_key || '',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
disabled={!debugInfo?.plugin_debug_key}
|
||||||
|
>
|
||||||
|
<Copy className="w-3.5 h-3.5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{!debugInfo?.plugin_debug_key && (
|
||||||
|
<p className="text-xs text-muted-foreground ml-[58px]">
|
||||||
|
{t('plugins.debugKeyDisabled')}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="default" className="px-6 py-4 cursor-pointer">
|
<Button variant="default" className="px-6 py-4 cursor-pointer">
|
||||||
|
|||||||
@@ -639,6 +639,13 @@ export class BackendClient extends BaseHttpClient {
|
|||||||
return this.get('/api/v1/system/status/plugin-system');
|
return this.get('/api/v1/system/status/plugin-system');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getPluginDebugInfo(): Promise<{
|
||||||
|
debug_url: string;
|
||||||
|
plugin_debug_key: string;
|
||||||
|
}> {
|
||||||
|
return this.get('/api/v1/plugins/debug-info');
|
||||||
|
}
|
||||||
|
|
||||||
// ============ User API ============
|
// ============ User API ============
|
||||||
public checkIfInited(): Promise<{ initialized: boolean }> {
|
public checkIfInited(): Promise<{ initialized: boolean }> {
|
||||||
return this.get('/api/v1/user/init');
|
return this.get('/api/v1/user/init');
|
||||||
|
|||||||
@@ -231,6 +231,15 @@ const enUS = {
|
|||||||
failedToGetStatus: 'Failed to get plugin system status',
|
failedToGetStatus: 'Failed to get plugin system status',
|
||||||
pluginSystemNotReady:
|
pluginSystemNotReady:
|
||||||
'Plugin system is not ready, cannot perform this operation',
|
'Plugin system is not ready, cannot perform this operation',
|
||||||
|
debugInfo: 'Debug Info',
|
||||||
|
debugInfoTitle: 'Plugin Debug Information',
|
||||||
|
debugUrl: 'Debug URL',
|
||||||
|
debugKey: 'Debug Key',
|
||||||
|
noDebugKey: '(Not Set)',
|
||||||
|
debugKeyDisabled:
|
||||||
|
'Debug key is not set, plugin debugging does not require authentication',
|
||||||
|
failedToGetDebugInfo: 'Failed to get debug information',
|
||||||
|
copiedToClipboard: 'Copied to clipboard',
|
||||||
deleting: 'Deleting...',
|
deleting: 'Deleting...',
|
||||||
deletePlugin: 'Delete Plugin',
|
deletePlugin: 'Delete Plugin',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
|||||||
@@ -233,6 +233,15 @@ const jaJP = {
|
|||||||
failedToGetStatus: 'プラグインシステム状態の取得に失敗しました',
|
failedToGetStatus: 'プラグインシステム状態の取得に失敗しました',
|
||||||
pluginSystemNotReady:
|
pluginSystemNotReady:
|
||||||
'プラグインシステムが準備されていません。この操作を実行できません',
|
'プラグインシステムが準備されていません。この操作を実行できません',
|
||||||
|
debugInfo: 'デバッグ情報',
|
||||||
|
debugInfoTitle: 'プラグインデバッグ情報',
|
||||||
|
debugUrl: 'デバッグURL',
|
||||||
|
debugKey: 'デバッグキー',
|
||||||
|
noDebugKey: '(未設定)',
|
||||||
|
debugKeyDisabled:
|
||||||
|
'デバッグキーが設定されていません。プラグインデバッグには認証が不要です',
|
||||||
|
failedToGetDebugInfo: 'デバッグ情報の取得に失敗しました',
|
||||||
|
copiedToClipboard: 'クリップボードにコピーしました',
|
||||||
deleting: '削除中...',
|
deleting: '削除中...',
|
||||||
deletePlugin: 'プラグインを削除',
|
deletePlugin: 'プラグインを削除',
|
||||||
cancel: 'キャンセル',
|
cancel: 'キャンセル',
|
||||||
|
|||||||
@@ -222,6 +222,14 @@ const zhHans = {
|
|||||||
loadingStatus: '正在检查插件系统状态...',
|
loadingStatus: '正在检查插件系统状态...',
|
||||||
failedToGetStatus: '获取插件系统状态失败',
|
failedToGetStatus: '获取插件系统状态失败',
|
||||||
pluginSystemNotReady: '插件系统未就绪,无法执行此操作',
|
pluginSystemNotReady: '插件系统未就绪,无法执行此操作',
|
||||||
|
debugInfo: '调试信息',
|
||||||
|
debugInfoTitle: '插件调试信息',
|
||||||
|
debugUrl: '调试地址',
|
||||||
|
debugKey: '调试密钥',
|
||||||
|
noDebugKey: '(未设置)',
|
||||||
|
debugKeyDisabled: '未设置调试密钥,插件调试无需认证',
|
||||||
|
failedToGetDebugInfo: '获取调试信息失败',
|
||||||
|
copiedToClipboard: '已复制到剪贴板',
|
||||||
deleting: '删除中...',
|
deleting: '删除中...',
|
||||||
deletePlugin: '删除插件',
|
deletePlugin: '删除插件',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
|
|||||||
@@ -222,6 +222,14 @@ const zhHant = {
|
|||||||
loadingStatus: '正在檢查外掛系統狀態...',
|
loadingStatus: '正在檢查外掛系統狀態...',
|
||||||
failedToGetStatus: '取得外掛系統狀態失敗',
|
failedToGetStatus: '取得外掛系統狀態失敗',
|
||||||
pluginSystemNotReady: '外掛系統未就緒,無法執行此操作',
|
pluginSystemNotReady: '外掛系統未就緒,無法執行此操作',
|
||||||
|
debugInfo: '偵錯資訊',
|
||||||
|
debugInfoTitle: '外掛偵錯資訊',
|
||||||
|
debugUrl: '偵錯位址',
|
||||||
|
debugKey: '偵錯金鑰',
|
||||||
|
noDebugKey: '(未設定)',
|
||||||
|
debugKeyDisabled: '未設定偵錯金鑰,外掛偵錯無需認證',
|
||||||
|
failedToGetDebugInfo: '取得偵錯資訊失敗',
|
||||||
|
copiedToClipboard: '已複製到剪貼簿',
|
||||||
deleting: '刪除中...',
|
deleting: '刪除中...',
|
||||||
deletePlugin: '刪除外掛',
|
deletePlugin: '刪除外掛',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
|
|||||||
Reference in New Issue
Block a user