mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-21 16:05:19 +08:00
fix: stabilize native streaming and improve bot controls
This commit is contained in:
@@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -22,7 +22,7 @@ import type { BotSessionMonitorHandle } from '@/app/home/bots/components/bot-ses
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Settings, FileText, Users, RefreshCw, Trash2 } from 'lucide-react';
|
||||
import { Settings, FileText, Users, RefreshCw, Trash2, Loader2 } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { toast } from 'sonner';
|
||||
import { showBotError } from './bot-error';
|
||||
@@ -72,6 +72,7 @@ export default function BotDetailContent({ id }: { id: string }) {
|
||||
|
||||
// Enable state managed here so the header switch works
|
||||
const [botEnabled, setBotEnabled] = useState(true);
|
||||
const [isTogglingEnable, setIsTogglingEnable] = useState(false);
|
||||
const [enableLoaded, setEnableLoaded] = useState(false);
|
||||
|
||||
// Fetch bot enable state
|
||||
@@ -91,20 +92,25 @@ export default function BotDetailContent({ id }: { id: string }) {
|
||||
|
||||
const handleEnableToggle = useCallback(
|
||||
async (checked: boolean) => {
|
||||
if (isTogglingEnable) return;
|
||||
const prev = botEnabled;
|
||||
setIsTogglingEnable(true);
|
||||
setBotEnabled(checked);
|
||||
try {
|
||||
await httpClient.updateBot(id, { enable: checked });
|
||||
setBot((current) =>
|
||||
current ? { ...current, enable: checked } : current,
|
||||
);
|
||||
toast.success(t(checked ? 'bots.enableConfirmed' : 'bots.disableConfirmed'));
|
||||
refreshBots();
|
||||
} catch (error) {
|
||||
setBotEnabled(prev);
|
||||
showBotError(error, t('bots.setBotEnableError'), t);
|
||||
} finally {
|
||||
setIsTogglingEnable(false);
|
||||
}
|
||||
},
|
||||
[id, botEnabled, refreshBots, t],
|
||||
[id, botEnabled, isTogglingEnable, refreshBots, t],
|
||||
);
|
||||
|
||||
function handleFormSubmit() {
|
||||
@@ -210,18 +216,27 @@ export default function BotDetailContent({ id }: { id: string }) {
|
||||
</div>
|
||||
{enableLoaded && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge
|
||||
status={isTogglingEnable ? 'pending' : botEnabled ? 'enabled' : 'disabled'}
|
||||
className="gap-1.5 rounded-full"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
{isTogglingEnable
|
||||
? <Loader2 className="animate-spin" aria-hidden="true" />
|
||||
: <span className="size-1.5 rounded-full bg-current" aria-hidden="true" />}
|
||||
{t(isTogglingEnable
|
||||
? botEnabled ? 'bots.enabling' : 'bots.disabling'
|
||||
: botEnabled ? 'bots.enableConfirmed' : 'bots.disableConfirmed')}
|
||||
</Badge>
|
||||
<Switch
|
||||
id="bot-enable-switch"
|
||||
checked={botEnabled}
|
||||
onCheckedChange={handleEnableToggle}
|
||||
disabled={!canManage}
|
||||
disabled={!canManage || isTogglingEnable}
|
||||
aria-label={t('common.enable')}
|
||||
aria-busy={isTogglingEnable}
|
||||
/>
|
||||
<Label
|
||||
htmlFor="bot-enable-switch"
|
||||
className="text-sm text-muted-foreground cursor-pointer"
|
||||
>
|
||||
{t('common.enable')}
|
||||
</Label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -237,11 +252,11 @@ export default function BotDetailContent({ id }: { id: string }) {
|
||||
<Button
|
||||
type="submit"
|
||||
form="bot-form"
|
||||
disabled={!enableLoaded || (!formDirty && botEnabled)}
|
||||
disabled={!enableLoaded || !formDirty || isTogglingEnable}
|
||||
className={activeTab !== 'config' ? 'invisible' : ''}
|
||||
data-guide="bot-config-save"
|
||||
>
|
||||
{t(botEnabled ? 'common.save' : 'bots.saveAndEnable')}
|
||||
{t(formDirty && !botEnabled ? 'bots.saveAndEnable' : 'common.save')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@@ -800,7 +800,7 @@ function NavItems({
|
||||
{(isBot || isMCP) && (
|
||||
<span
|
||||
className={cn(
|
||||
'absolute -bottom-0.5 -right-0.5 size-2 rounded-full border-2 border-popover',
|
||||
'absolute -bottom-1 -right-1 size-3 rounded-full border-2 border-popover',
|
||||
isMCP
|
||||
? mcpStatusColor(item)
|
||||
: item.enabled === false
|
||||
@@ -868,7 +868,7 @@ function NavItems({
|
||||
{(isBot || isMCP) && (
|
||||
<span
|
||||
className={cn(
|
||||
'absolute -bottom-0.5 -right-0.5 size-2 rounded-full border-2 border-sidebar',
|
||||
'absolute -bottom-1 -right-1 size-3 rounded-full border-2 border-sidebar',
|
||||
isMCP
|
||||
? mcpStatusColor(item)
|
||||
: item.enabled === false
|
||||
|
||||
@@ -43,7 +43,8 @@ export function useMonitoringData(filterState: FilterState) {
|
||||
requestId === requestIdRef.current &&
|
||||
getCurrentWorkspaceSnapshot()?.workspace.uuid === workspaceUuid;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
// Keep the failure view during retries; restore the dashboard only after
|
||||
// a successful response, without briefly restarting its polling timer.
|
||||
|
||||
try {
|
||||
const { startTime, endTime } = resolveMonitoringWindow(
|
||||
@@ -385,6 +386,7 @@ export function useMonitoringData(filterState: FilterState) {
|
||||
].sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
||||
|
||||
setData(transformedData);
|
||||
setError(null);
|
||||
setRequestScope(scope);
|
||||
} catch (err) {
|
||||
if (!isCurrent()) return;
|
||||
|
||||
@@ -167,7 +167,7 @@ function MonitoringPageContent() {
|
||||
intervalRef.current = refreshInterval;
|
||||
countdown.current = refreshInterval;
|
||||
setRemainingSeconds(refreshInterval);
|
||||
if (!refreshInterval) return;
|
||||
if (!refreshInterval || error) return;
|
||||
const timer = window.setInterval(() => {
|
||||
if (document.visibilityState === 'visible' && !refreshState.current.busy) {
|
||||
countdown.current = Math.max(0, countdown.current - 1);
|
||||
@@ -179,7 +179,7 @@ function MonitoringPageContent() {
|
||||
}
|
||||
}, 1000);
|
||||
return () => window.clearInterval(timer);
|
||||
}, [refreshInterval]);
|
||||
}, [refreshInterval, error]);
|
||||
|
||||
// State for expanded errors
|
||||
const [expandedErrorId, setExpandedErrorId] = useState<string | null>(null);
|
||||
@@ -204,7 +204,7 @@ function MonitoringPageContent() {
|
||||
return (
|
||||
<div className="w-full h-full overflow-y-auto overflow-x-hidden">
|
||||
{/* Filters and Refresh Button - Sticky */}
|
||||
<div className="sticky top-0 z-10 -mt-1 pb-5 pt-1 bg-background">
|
||||
{!error && <div className="sticky top-0 z-10 -mt-1 pb-5 pt-1 bg-background">
|
||||
<div>
|
||||
<Card className="flex flex-col gap-4 p-4 xl:flex-row xl:items-end">
|
||||
<MonitoringFilters
|
||||
@@ -264,6 +264,8 @@ function MonitoringPageContent() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
{/* Content Area */}
|
||||
{error ? (
|
||||
<LoadErrorState title={t('monitoring.loadError')} onRetry={handleRefresh} busy={refreshing} />
|
||||
|
||||
@@ -822,6 +822,10 @@ const enUS = {
|
||||
earlier: 'Earlier',
|
||||
dateFormat: '{{month}}/{{day}}',
|
||||
setBotEnableError: 'Failed to set bot enable status',
|
||||
enabling: 'Enabling…',
|
||||
disabling: 'Disabling…',
|
||||
enableConfirmed: 'Enabled',
|
||||
disableConfirmed: 'Disabled',
|
||||
log: 'Log',
|
||||
configuration: 'Configuration',
|
||||
logs: 'Logs',
|
||||
|
||||
@@ -838,6 +838,10 @@ const esES = {
|
||||
earlier: 'Anterior',
|
||||
dateFormat: '{{day}}/{{month}}',
|
||||
setBotEnableError: 'Error al establecer el estado de activación del Bot',
|
||||
enabling: 'Activando…',
|
||||
disabling: 'Desactivando…',
|
||||
enableConfirmed: 'Activado',
|
||||
disableConfirmed: 'Desactivado',
|
||||
log: 'Registro',
|
||||
configuration: 'Configuración',
|
||||
logs: 'Registros',
|
||||
|
||||
@@ -825,6 +825,10 @@ const jaJP = {
|
||||
earlier: 'それ以前',
|
||||
dateFormat: '{{month}}月{{day}}日',
|
||||
setBotEnableError: 'ボットの有効状態の設定に失敗しました',
|
||||
enabling: '有効にしています…',
|
||||
disabling: '無効にしています…',
|
||||
enableConfirmed: '有効',
|
||||
disableConfirmed: '無効',
|
||||
log: 'ログ',
|
||||
configuration: '設定',
|
||||
logs: 'ログ',
|
||||
|
||||
@@ -833,6 +833,10 @@ const ruRU = {
|
||||
earlier: 'Ранее',
|
||||
dateFormat: '{{day}}.{{month}}',
|
||||
setBotEnableError: 'Не удалось изменить статус бота',
|
||||
enabling: 'Включение…',
|
||||
disabling: 'Отключение…',
|
||||
enableConfirmed: 'Включён',
|
||||
disableConfirmed: 'Отключён',
|
||||
log: 'Журнал',
|
||||
configuration: 'Настройки',
|
||||
logs: 'Журналы',
|
||||
|
||||
@@ -809,6 +809,10 @@ const thTH = {
|
||||
earlier: 'ก่อนหน้า',
|
||||
dateFormat: '{{day}}/{{month}}',
|
||||
setBotEnableError: 'ไม่สามารถตั้งค่าสถานะเปิดใช้งาน Bot ได้',
|
||||
enabling: 'กำลังเปิดใช้งาน…',
|
||||
disabling: 'กำลังปิดใช้งาน…',
|
||||
enableConfirmed: 'เปิดใช้งานแล้ว',
|
||||
disableConfirmed: 'ปิดใช้งานแล้ว',
|
||||
log: 'บันทึก',
|
||||
configuration: 'การกำหนดค่า',
|
||||
logs: 'บันทึก',
|
||||
|
||||
@@ -823,6 +823,10 @@ const viVN = {
|
||||
earlier: 'Trước đó',
|
||||
dateFormat: '{{day}}/{{month}}',
|
||||
setBotEnableError: 'Thiết lập trạng thái bật Bot thất bại',
|
||||
enabling: 'Đang bật…',
|
||||
disabling: 'Đang tắt…',
|
||||
enableConfirmed: 'Đã bật',
|
||||
disableConfirmed: 'Đã tắt',
|
||||
log: 'Nhật ký',
|
||||
configuration: 'Cấu hình',
|
||||
logs: 'Nhật ký',
|
||||
|
||||
@@ -775,6 +775,10 @@ const zhHans = {
|
||||
earlier: '更久之前',
|
||||
dateFormat: '{{month}}月{{day}}日',
|
||||
setBotEnableError: '设置机器人启用状态失败',
|
||||
enabling: '正在启用…',
|
||||
disabling: '正在禁用…',
|
||||
enableConfirmed: '已启用',
|
||||
disableConfirmed: '已禁用',
|
||||
log: '日志',
|
||||
configuration: '配置',
|
||||
logs: '日志',
|
||||
|
||||
@@ -773,6 +773,10 @@ const zhHant = {
|
||||
earlier: '更久之前',
|
||||
dateFormat: '{{month}}月{{day}}日',
|
||||
setBotEnableError: '設定機器人啟用狀態失敗',
|
||||
enabling: '正在啟用…',
|
||||
disabling: '正在停用…',
|
||||
enableConfirmed: '已啟用',
|
||||
disableConfirmed: '已停用',
|
||||
log: '日誌',
|
||||
configuration: '設定',
|
||||
logs: '日誌',
|
||||
|
||||
Reference in New Issue
Block a user