fix git bugs.
parent
9f98d3f68d
commit
3622117d85
|
|
@ -27,6 +27,13 @@ class BotDockerManager:
|
|||
self._storage_limit_supported: Optional[bool] = None
|
||||
self._storage_limit_warning_emitted = False
|
||||
|
||||
def _new_short_lived_client(self):
|
||||
try:
|
||||
return docker.from_env(timeout=6)
|
||||
except Exception as e:
|
||||
print(f"[DockerManager] failed to create short-lived client: {e}")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _normalize_resource_limits(
|
||||
cpu_cores: Optional[float],
|
||||
|
|
@ -612,21 +619,41 @@ class BotDockerManager:
|
|||
self._last_delivery_error[bot_id] = reason
|
||||
return False
|
||||
|
||||
def _read_log_lines(self, bot_id: str, tail: Optional[int] = None) -> List[str]:
|
||||
def _read_log_lines_with_client(self, client, bot_id: str, tail: Optional[int] = None) -> List[str]:
|
||||
container = client.containers.get(f"worker_{bot_id}")
|
||||
raw = container.logs(tail=max(1, int(tail))) if tail is not None else container.logs()
|
||||
if isinstance(raw, (bytes, bytearray)):
|
||||
text = raw.decode("utf-8", errors="ignore")
|
||||
else:
|
||||
text = str(raw or "")
|
||||
return [line for line in text.splitlines() if line.strip()]
|
||||
|
||||
def _read_log_lines(self, bot_id: str, tail: Optional[int] = None, *, fresh_client: bool = False) -> List[str]:
|
||||
if fresh_client:
|
||||
client = self._new_short_lived_client()
|
||||
if not client:
|
||||
return []
|
||||
try:
|
||||
return self._read_log_lines_with_client(client, bot_id, tail=tail)
|
||||
except Exception as e:
|
||||
print(f"[DockerManager] Error reading logs for {bot_id} with short-lived client: {e}")
|
||||
return []
|
||||
finally:
|
||||
try:
|
||||
client.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not self.client:
|
||||
return []
|
||||
container_name = f"worker_{bot_id}"
|
||||
try:
|
||||
container = self.client.containers.get(container_name)
|
||||
raw = container.logs(tail=max(1, int(tail))) if tail is not None else container.logs()
|
||||
text = raw.decode("utf-8", errors="ignore")
|
||||
return [line for line in text.splitlines() if line.strip()]
|
||||
return self._read_log_lines_with_client(self.client, bot_id, tail=tail)
|
||||
except Exception as e:
|
||||
print(f"[DockerManager] Error reading logs for {bot_id}: {e}")
|
||||
return []
|
||||
|
||||
def get_recent_logs(self, bot_id: str, tail: int = 300) -> List[str]:
|
||||
return self._read_log_lines(bot_id, tail=max(1, int(tail)))
|
||||
def get_recent_logs(self, bot_id: str, tail: int = 300, *, fresh_client: bool = False) -> List[str]:
|
||||
return self._read_log_lines(bot_id, tail=max(1, int(tail)), fresh_client=fresh_client)
|
||||
|
||||
def get_logs_page(
|
||||
self,
|
||||
|
|
@ -634,6 +661,8 @@ class BotDockerManager:
|
|||
offset: int = 0,
|
||||
limit: int = 50,
|
||||
reverse: bool = True,
|
||||
*,
|
||||
fresh_client: bool = False,
|
||||
) -> Dict[str, Any]:
|
||||
safe_offset = max(0, int(offset))
|
||||
safe_limit = max(1, int(limit))
|
||||
|
|
@ -641,7 +670,7 @@ class BotDockerManager:
|
|||
# Docker logs API supports tail but not arbitrary offsets. For reverse pagination
|
||||
# we only read the minimal newest slice needed for the requested page.
|
||||
tail_count = safe_offset + safe_limit + 1
|
||||
lines = self._read_log_lines(bot_id, tail=tail_count)
|
||||
lines = self._read_log_lines(bot_id, tail=tail_count, fresh_client=fresh_client)
|
||||
ordered = list(reversed(lines))
|
||||
page = ordered[safe_offset:safe_offset + safe_limit]
|
||||
has_more = len(lines) > safe_offset + safe_limit
|
||||
|
|
@ -654,7 +683,7 @@ class BotDockerManager:
|
|||
"reverse": reverse,
|
||||
}
|
||||
|
||||
lines = self._read_log_lines(bot_id, tail=None)
|
||||
lines = self._read_log_lines(bot_id, tail=None, fresh_client=fresh_client)
|
||||
total = len(lines)
|
||||
page = lines[safe_offset:safe_offset + safe_limit]
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -84,10 +84,14 @@ def get_bot_logs(
|
|||
offset=max(0, int(offset)),
|
||||
limit=max(1, int(limit)),
|
||||
reverse=bool(reverse),
|
||||
fresh_client=True,
|
||||
)
|
||||
return {"bot_id": bot_id, **page}
|
||||
effective_tail = max(1, int(tail or 300))
|
||||
return {"bot_id": bot_id, "logs": docker_manager.get_recent_logs(bot_id, tail=effective_tail)}
|
||||
return {
|
||||
"bot_id": bot_id,
|
||||
"logs": docker_manager.get_recent_logs(bot_id, tail=effective_tail, fresh_client=True),
|
||||
}
|
||||
|
||||
|
||||
async def relogin_weixin(session: Session, *, bot_id: str) -> Dict[str, Any]:
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ function AppShell() {
|
|||
const [botCompactPanelTab, setBotCompactPanelTab] = useState<CompactBotPanelTab>('chat');
|
||||
|
||||
const forcedBotId = route.kind === 'bot' ? route.botId : '';
|
||||
useBotsSync(forcedBotId || undefined);
|
||||
useBotsSync(forcedBotId || undefined, route.kind === 'bot');
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import { createContext } from 'react';
|
||||
|
||||
export type PromptTone = 'info' | 'success' | 'warning' | 'error';
|
||||
|
||||
export interface NotifyOptions {
|
||||
title?: string;
|
||||
tone?: PromptTone;
|
||||
durationMs?: number;
|
||||
}
|
||||
|
||||
export interface ConfirmOptions {
|
||||
title?: string;
|
||||
message: string;
|
||||
tone?: PromptTone;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
}
|
||||
|
||||
export interface LucentPromptApi {
|
||||
notify: (message: string, options?: NotifyOptions) => void;
|
||||
confirm: (options: ConfirmOptions) => Promise<boolean>;
|
||||
}
|
||||
|
||||
export const LucentPromptContext = createContext<LucentPromptApi | null>(null);
|
||||
|
|
@ -1,25 +1,11 @@
|
|||
import { createContext, useCallback, useContext, useMemo, useRef, useState, type ReactNode } from 'react';
|
||||
import { useCallback, useMemo, useRef, useState, type ReactNode } from 'react';
|
||||
import { AlertCircle, AlertTriangle, CheckCircle2, Info, X } from 'lucide-react';
|
||||
import { useAppStore } from '../../store/appStore';
|
||||
import type { ConfirmOptions, LucentPromptApi, NotifyOptions, PromptTone } from './LucentPromptContext';
|
||||
import { LucentPromptContext } from './LucentPromptContext';
|
||||
import { LucentIconButton } from './LucentIconButton';
|
||||
import './lucent-prompt.css';
|
||||
|
||||
type PromptTone = 'info' | 'success' | 'warning' | 'error';
|
||||
|
||||
interface NotifyOptions {
|
||||
title?: string;
|
||||
tone?: PromptTone;
|
||||
durationMs?: number;
|
||||
}
|
||||
|
||||
interface ConfirmOptions {
|
||||
title?: string;
|
||||
message: string;
|
||||
tone?: PromptTone;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
}
|
||||
|
||||
interface ToastItem {
|
||||
id: number;
|
||||
title?: string;
|
||||
|
|
@ -36,13 +22,6 @@ interface ConfirmState {
|
|||
resolve: (value: boolean) => void;
|
||||
}
|
||||
|
||||
interface LucentPromptApi {
|
||||
notify: (message: string, options?: NotifyOptions) => void;
|
||||
confirm: (options: ConfirmOptions) => Promise<boolean>;
|
||||
}
|
||||
|
||||
const LucentPromptContext = createContext<LucentPromptApi | null>(null);
|
||||
|
||||
function ToneIcon({ tone }: { tone: PromptTone }) {
|
||||
if (tone === 'success') return <CheckCircle2 size={16} />;
|
||||
if (tone === 'warning') return <AlertTriangle size={16} />;
|
||||
|
|
@ -158,11 +137,3 @@ export function LucentPromptProvider({ children }: { children: ReactNode }) {
|
|||
</LucentPromptContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useLucentPrompt() {
|
||||
const ctx = useContext(LucentPromptContext);
|
||||
if (!ctx) {
|
||||
throw new Error('useLucentPrompt must be used inside LucentPromptProvider');
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
import { useContext } from 'react';
|
||||
|
||||
import { LucentPromptContext } from './LucentPromptContext';
|
||||
|
||||
export function useLucentPrompt() {
|
||||
const ctx = useContext(LucentPromptContext);
|
||||
if (!ctx) {
|
||||
throw new Error('useLucentPrompt must be used inside LucentPromptProvider');
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ function extractToolCallProgressHint(raw: string, isZh: boolean): string | null
|
|||
return `${isZh ? '工具调用' : 'Tool Call'}\n${callLabel}`;
|
||||
}
|
||||
|
||||
export function useBotsSync(forcedBotId?: string) {
|
||||
export function useBotsSync(forcedBotId?: string, enableMonitorSockets: boolean = true) {
|
||||
const { activeBots, setBots, updateBotState, addBotLog, addBotMessage, addBotEvent, setBotMessages } = useAppStore();
|
||||
const socketsRef = useRef<Record<string, WebSocket>>({});
|
||||
const heartbeatsRef = useRef<Record<string, number>>({});
|
||||
|
|
@ -254,6 +254,16 @@ export function useBotsSync(forcedBotId?: string) {
|
|||
}, [syncBotMessages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableMonitorSockets) {
|
||||
Object.values(socketsRef.current).forEach((ws) => ws.close());
|
||||
Object.values(heartbeatsRef.current).forEach((timerId) => window.clearInterval(timerId));
|
||||
heartbeatsRef.current = {};
|
||||
socketsRef.current = {};
|
||||
return () => {
|
||||
// no-op
|
||||
};
|
||||
}
|
||||
|
||||
const runningIds = new Set(
|
||||
Object.values(activeBots)
|
||||
.filter((bot) => bot.docker_status === 'RUNNING')
|
||||
|
|
@ -421,7 +431,7 @@ export function useBotsSync(forcedBotId?: string) {
|
|||
return () => {
|
||||
// no-op: clean in unmount effect below
|
||||
};
|
||||
}, [activeBots, addBotEvent, addBotLog, addBotMessage, forced, isZh, syncBotMessages, t.progress, t.replied, t.stateUpdated, updateBotState]);
|
||||
}, [activeBots, addBotEvent, addBotLog, addBotMessage, enableMonitorSockets, forced, isZh, syncBotMessages, t.progress, t.replied, t.stateUpdated, updateBotState]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export const dashboardEn = {
|
|||
channelSaved: 'Channel saved (effective after bot restart).',
|
||||
channelSaveFail: 'Failed to save channel.',
|
||||
channelAddFail: 'Failed to add channel.',
|
||||
channelDeleted: 'Channel deleted.',
|
||||
channelDeleteConfirm: (channelType: string) => `Delete channel ${channelType}?`,
|
||||
channelDeleteFail: 'Failed to delete channel.',
|
||||
stopFail: 'Stop failed. Check backend logs.',
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export const dashboardZhCn = {
|
|||
channelSaved: '渠道配置已保存(重启 Bot 后生效)。',
|
||||
channelSaveFail: '渠道保存失败。',
|
||||
channelAddFail: '新增渠道失败。',
|
||||
channelDeleted: '渠道已删除。',
|
||||
channelDeleteConfirm: (channelType: string) => `确认删除渠道 ${channelType}?`,
|
||||
channelDeleteFail: '删除渠道失败。',
|
||||
stopFail: '停止失败,请查看后端日志。',
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { ChatMessage } from '../../../types/bot';
|
|||
import { normalizeAssistantMessageText, normalizeUserMessageText } from '../../../shared/text/messageText';
|
||||
import { normalizeAttachmentPaths } from '../../../shared/workspace/utils';
|
||||
import { normalizeDashboardAttachmentPath } from '../../../shared/workspace/workspaceMarkdown';
|
||||
import type { BotMessageResponseRow } from '../types';
|
||||
|
||||
export function formatClock(ts: number) {
|
||||
const d = new Date(ts);
|
||||
|
|
@ -37,7 +38,7 @@ export function formatDateInputValue(ts: number): string {
|
|||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
export function mapBotMessageResponseRow(row: any): ChatMessage {
|
||||
export function mapBotMessageResponseRow(row: BotMessageResponseRow): ChatMessage {
|
||||
const roleRaw = String(row?.role || '').toLowerCase();
|
||||
const role: ChatMessage['role'] =
|
||||
roleRaw === 'user' || roleRaw === 'assistant' || roleRaw === 'system' ? roleRaw : 'assistant';
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { memo, type RefObject } from 'react';
|
|||
|
||||
import { ProtectedSearchInput } from '../../../components/ProtectedSearchInput';
|
||||
import { LucentIconButton } from '../../../components/lucent/LucentIconButton';
|
||||
import type { BotState } from '../../../types/bot';
|
||||
import type { CompactPanelTab } from '../types';
|
||||
import './DashboardMenus.css';
|
||||
import './BotListPanel.css';
|
||||
|
|
@ -33,9 +34,9 @@ interface BotListLabels {
|
|||
}
|
||||
|
||||
interface BotListPanelProps {
|
||||
bots: any[];
|
||||
filteredBots: any[];
|
||||
pagedBots: any[];
|
||||
bots: BotState[];
|
||||
filteredBots: BotState[];
|
||||
pagedBots: BotState[];
|
||||
selectedBotId: string;
|
||||
normalizedBotListQuery: string;
|
||||
botListQuery: string;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import type { RefObject } from 'react';
|
|||
import { DrawerShell } from '../../../components/DrawerShell';
|
||||
import { PasswordInput } from '../../../components/PasswordInput';
|
||||
import { LucentIconButton } from '../../../components/lucent/LucentIconButton';
|
||||
import type { ChannelLabels, DashboardLabels } from '../localeTypes';
|
||||
import type { BotChannel, ChannelType, WeixinLoginStatus } from '../types';
|
||||
import './DashboardManagementModals.css';
|
||||
|
||||
|
|
@ -12,6 +13,8 @@ interface PasswordToggleLabels {
|
|||
hide: string;
|
||||
}
|
||||
|
||||
type ChannelConfigLabels = ChannelLabels & Pick<DashboardLabels, 'cancel' | 'close'>;
|
||||
|
||||
function parseChannelListValue(raw: unknown): string {
|
||||
if (!Array.isArray(raw)) return '';
|
||||
return raw
|
||||
|
|
@ -57,7 +60,7 @@ function ChannelFieldsEditor({
|
|||
onPatch,
|
||||
}: {
|
||||
channel: BotChannel;
|
||||
labels: Record<string, any>;
|
||||
labels: ChannelConfigLabels;
|
||||
passwordToggleLabels: PasswordToggleLabels;
|
||||
onPatch: (patch: Partial<BotChannel>) => void;
|
||||
}) {
|
||||
|
|
@ -289,7 +292,7 @@ interface ChannelConfigModalProps {
|
|||
weixinLoginStatus: WeixinLoginStatus | null;
|
||||
hasSelectedBot: boolean;
|
||||
isZh: boolean;
|
||||
labels: Record<string, any>;
|
||||
labels: ChannelConfigLabels;
|
||||
passwordToggleLabels: PasswordToggleLabels;
|
||||
onClose: () => void;
|
||||
onUpdateGlobalDeliveryFlag: (key: 'sendProgress' | 'sendToolHints', value: boolean) => void;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { DrawerShell } from '../../../components/DrawerShell';
|
|||
import { PasswordInput } from '../../../components/PasswordInput';
|
||||
import { LucentIconButton } from '../../../components/lucent/LucentIconButton';
|
||||
import { LucentSelect } from '../../../components/lucent/LucentSelect';
|
||||
import type { DashboardLabels } from '../localeTypes';
|
||||
import type { MCPServerDraft, WorkspaceSkillOption } from '../types';
|
||||
import './DashboardManagementModals.css';
|
||||
|
||||
|
|
@ -19,7 +20,7 @@ interface SkillsModalProps {
|
|||
isSkillUploading: boolean;
|
||||
isZh: boolean;
|
||||
hasSelectedBot: boolean;
|
||||
labels: Record<string, any>;
|
||||
labels: DashboardLabels;
|
||||
skillZipPickerRef: RefObject<HTMLInputElement | null>;
|
||||
skillAddMenuRef: RefObject<HTMLDivElement | null>;
|
||||
skillAddMenuOpen: boolean;
|
||||
|
|
@ -144,7 +145,7 @@ interface McpConfigModalProps {
|
|||
newMcpPanelOpen: boolean;
|
||||
isSavingMcp: boolean;
|
||||
isZh: boolean;
|
||||
labels: Record<string, any>;
|
||||
labels: DashboardLabels;
|
||||
passwordToggleLabels: PasswordToggleLabels;
|
||||
onClose: () => void;
|
||||
getMcpUiKey: (row: Pick<MCPServerDraft, 'name' | 'url'>, fallbackIndex: number) => string;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type { RefObject } from 'react';
|
|||
|
||||
import { DrawerShell } from '../../../components/DrawerShell';
|
||||
import { LucentIconButton } from '../../../components/lucent/LucentIconButton';
|
||||
import type { DashboardLabels } from '../localeTypes';
|
||||
import type { BotTopic, TopicPresetTemplate } from '../types';
|
||||
import './DashboardManagementModals.css';
|
||||
|
||||
|
|
@ -28,7 +29,7 @@ interface TopicConfigModalProps {
|
|||
isSavingTopic: boolean;
|
||||
hasSelectedBot: boolean;
|
||||
isZh: boolean;
|
||||
labels: Record<string, any>;
|
||||
labels: DashboardLabels;
|
||||
onClose: () => void;
|
||||
getTopicUiKey: (topic: Pick<BotTopic, 'topic_key' | 'id'>, fallbackIndex: number) => string;
|
||||
countRoutingTextList: (raw: string) => number;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import axios from 'axios';
|
||||
|
||||
import { APP_ENDPOINTS } from '../../../config/env';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import type { DashboardLabels } from '../localeTypes';
|
||||
import type { MCPConfigResponse, MCPServerConfig, MCPServerDraft } from '../types';
|
||||
import { mapMcpResponseToDrafts } from '../utils';
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ interface PromptApi {
|
|||
interface McpManagerDeps extends PromptApi {
|
||||
selectedBotId: string;
|
||||
isZh: boolean;
|
||||
t: any;
|
||||
t: DashboardLabels;
|
||||
currentMcpServers: MCPServerDraft[];
|
||||
currentPersistedMcpServers: MCPServerDraft[];
|
||||
currentNewMcpDraft: MCPServerDraft;
|
||||
|
|
@ -172,8 +174,8 @@ export function createMcpManager({
|
|||
resetNewMcpDraft();
|
||||
}
|
||||
notify(t.mcpSaved, { tone: 'success' });
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || t.mcpSaveFail, { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, t.mcpSaveFail), { tone: 'error' });
|
||||
} finally {
|
||||
setIsSavingMcp(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import axios from 'axios';
|
||||
|
||||
import { APP_ENDPOINTS } from '../../../config/env';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import type { DashboardLabels } from '../localeTypes';
|
||||
import { isSystemFallbackTopic, normalizePresetTextList, resolvePresetText } from '../topic/topicPresetUtils';
|
||||
import type { BotTopic, TopicPresetTemplate } from '../types';
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ interface PromptApi {
|
|||
interface TopicManagerDeps extends PromptApi {
|
||||
selectedBotId: string;
|
||||
isZh: boolean;
|
||||
t: any;
|
||||
t: DashboardLabels;
|
||||
effectiveTopicPresetTemplates: TopicPresetTemplate[];
|
||||
setShowTopicModal: (value: boolean) => void;
|
||||
setTopics: (value: BotTopic[] | ((prev: BotTopic[]) => BotTopic[])) => void;
|
||||
|
|
@ -263,8 +265,8 @@ export function createTopicManager({
|
|||
});
|
||||
await loadTopics(selectedBotId);
|
||||
notify(t.topicSaved, { tone: 'success' });
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || t.topicSaveFail, { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, t.topicSaveFail), { tone: 'error' });
|
||||
} finally {
|
||||
setIsSavingTopic(false);
|
||||
}
|
||||
|
|
@ -298,8 +300,8 @@ export function createTopicManager({
|
|||
resetNewTopicDraft();
|
||||
setNewTopicPanelOpen(false);
|
||||
notify(t.topicSaved, { tone: 'success' });
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || t.topicSaveFail, { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, t.topicSaveFail), { tone: 'error' });
|
||||
} finally {
|
||||
setIsSavingTopic(false);
|
||||
}
|
||||
|
|
@ -320,8 +322,8 @@ export function createTopicManager({
|
|||
await axios.delete(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/topics/${encodeURIComponent(topicKey)}`);
|
||||
await loadTopics(selectedBotId);
|
||||
notify(t.topicDeleted, { tone: 'success' });
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || t.topicDeleteFail, { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, t.topicDeleteFail), { tone: 'error' });
|
||||
} finally {
|
||||
setIsSavingTopic(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useId, useState } from 'react';
|
||||
|
||||
import { useLucentPrompt } from '../../../components/lucent/LucentPromptProvider';
|
||||
import { useLucentPrompt } from '../../../components/lucent/useLucentPrompt';
|
||||
import { pickLocale } from '../../../i18n';
|
||||
import { useBotWorkspace } from '../../../shared/workspace/useBotWorkspace';
|
||||
import { channelsEn } from '../../../i18n/channels.en';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ import { useCallback, useEffect, useState } from 'react';
|
|||
import axios from 'axios';
|
||||
|
||||
import { APP_ENDPOINTS } from '../../../config/env';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import type { BotState } from '../../../types/bot';
|
||||
import { getLlmProviderDefaultApiBase } from '../../../utils/llmProviders';
|
||||
import type { DashboardLabels } from '../localeTypes';
|
||||
import type { AgentTab, BotEditForm, BotParamDraft, NanobotImage } from '../types';
|
||||
import { clampCpuCores, clampMaxTokens, clampMemoryMb, clampStorageGb, clampTemperature } from '../utils';
|
||||
|
||||
|
|
@ -53,15 +56,15 @@ interface NotifyOptions {
|
|||
|
||||
interface UseDashboardBotEditorOptions {
|
||||
defaultSystemTimezone: string;
|
||||
ensureSelectedBotDetail: () => Promise<any>;
|
||||
ensureSelectedBotDetail: () => Promise<BotState | undefined>;
|
||||
isZh: boolean;
|
||||
notify: (message: string, options?: NotifyOptions) => void;
|
||||
refresh: () => Promise<void>;
|
||||
selectedBotId: string;
|
||||
selectedBot?: any;
|
||||
selectedBot?: BotState;
|
||||
setRuntimeMenuOpen: (open: boolean) => void;
|
||||
availableImages: NanobotImage[];
|
||||
t: any;
|
||||
t: DashboardLabels;
|
||||
}
|
||||
|
||||
export function useDashboardBotEditor({
|
||||
|
|
@ -86,7 +89,7 @@ export function useDashboardBotEditor({
|
|||
const [showParamModal, setShowParamModal] = useState(false);
|
||||
const [showAgentModal, setShowAgentModal] = useState(false);
|
||||
|
||||
const applyEditFormFromBot = useCallback((bot?: any) => {
|
||||
const applyEditFormFromBot = useCallback((bot?: BotState) => {
|
||||
if (!bot) return;
|
||||
const provider = String(bot.llm_provider || '').trim().toLowerCase();
|
||||
setProviderTestResult('');
|
||||
|
|
@ -212,8 +215,8 @@ export function useDashboardBotEditor({
|
|||
} else {
|
||||
setProviderTestResult(t.connFail(res.data?.detail || 'unknown error'));
|
||||
}
|
||||
} catch (error: any) {
|
||||
const msg = error?.response?.data?.detail || error?.message || 'request failed';
|
||||
} catch (error: unknown) {
|
||||
const msg = resolveApiErrorMessage(error, 'request failed');
|
||||
setProviderTestResult(t.connFail(msg));
|
||||
} finally {
|
||||
setIsTestingProvider(false);
|
||||
|
|
@ -286,8 +289,8 @@ export function useDashboardBotEditor({
|
|||
await refresh();
|
||||
closeModals();
|
||||
notify(t.configUpdated, { tone: 'success' });
|
||||
} catch (error: any) {
|
||||
const msg = error?.response?.data?.detail || t.saveFail;
|
||||
} catch (error: unknown) {
|
||||
const msg = resolveApiErrorMessage(error, t.saveFail);
|
||||
notify(msg, { tone: 'error' });
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ import { useCallback, useEffect } from 'react';
|
|||
import axios from 'axios';
|
||||
|
||||
import { APP_ENDPOINTS } from '../../../config/env';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import type { BotState } from '../../../types/bot';
|
||||
import type { ChatMessage } from '../../../types/bot';
|
||||
import type { DashboardLabels } from '../localeTypes';
|
||||
|
||||
type PromptTone = 'info' | 'success' | 'warning' | 'error';
|
||||
|
||||
|
|
@ -27,9 +30,9 @@ interface UseDashboardBotManagementOptions {
|
|||
refresh: () => Promise<void>;
|
||||
selectedBot?: BotState;
|
||||
selectedBotId: string;
|
||||
setBotMessages: (botId: string, messages: any[]) => void;
|
||||
setBotMessages: (botId: string, messages: ChatMessage[]) => void;
|
||||
setSelectedBotId: (botId: string) => void;
|
||||
t: any;
|
||||
t: DashboardLabels;
|
||||
}
|
||||
|
||||
export function useDashboardBotManagement({
|
||||
|
|
@ -92,8 +95,8 @@ export function useDashboardBotManagement({
|
|||
await axios.delete(`${APP_ENDPOINTS.apiBase}/bots/${selectedBot.id}/messages`);
|
||||
setBotMessages(selectedBot.id, []);
|
||||
notify(t.clearHistoryDone, { tone: 'success' });
|
||||
} catch (error: any) {
|
||||
const message = error?.response?.data?.detail || t.clearHistoryFail;
|
||||
} catch (error: unknown) {
|
||||
const message = resolveApiErrorMessage(error, t.clearHistoryFail);
|
||||
notify(message, { tone: 'error' });
|
||||
}
|
||||
}, [confirm, notify, selectedBot, setBotMessages, t]);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useCallback, useEffect, useMemo, useRef, useState, type SetStateAction } from 'react';
|
||||
|
||||
import { channelsEn } from '../../../i18n/channels.en';
|
||||
import type { BotState } from '../../../types/bot';
|
||||
import type { ChannelLabels } from '../localeTypes';
|
||||
import { optionalChannelTypes } from '../constants';
|
||||
import { createChannelManager, type ChannelManagerLabels, type GlobalDeliveryState } from '../config-managers/channelManager';
|
||||
import type { BotChannel, WeixinLoginStatus } from '../types';
|
||||
|
|
@ -34,7 +34,7 @@ interface UseDashboardChannelConfigOptions {
|
|||
selectedBot?: Pick<BotState, 'id' | 'docker_status' | 'send_progress' | 'send_tool_hints'> | null;
|
||||
selectedBotId: string;
|
||||
t: ChannelManagerLabels & { cancel: string; close: string };
|
||||
lc: typeof channelsEn;
|
||||
lc: ChannelLabels;
|
||||
weixinLoginStatus: WeixinLoginStatus | null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import { useCallback, useEffect, useRef, useState, type Dispatch, type SetStateA
|
|||
import axios from 'axios';
|
||||
|
||||
import { APP_ENDPOINTS } from '../../../config/env';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import type { ChatMessage } from '../../../types/bot';
|
||||
import type { BotMessagesByDateResponse } from '../types';
|
||||
import type { DashboardLabels } from '../localeTypes';
|
||||
import type { BotMessageResponseRow, BotMessagesByDateResponse } from '../types';
|
||||
import {
|
||||
formatConversationDate,
|
||||
formatDateInputValue,
|
||||
|
|
@ -23,7 +25,7 @@ interface UseDashboardChatHistoryOptions {
|
|||
setBotMessageFeedback: (botId: string, messageId: number, feedback: 'up' | 'down' | null) => void;
|
||||
notify: (message: string, options?: DashboardChatNotifyOptions) => void;
|
||||
confirm: (options: DashboardChatConfirmOptions) => Promise<boolean>;
|
||||
t: any;
|
||||
t: DashboardLabels;
|
||||
isZh: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +129,7 @@ export function useDashboardChatHistory({
|
|||
|
||||
const fetchBotMessages = useCallback(async (botId: string): Promise<ChatMessage[]> => {
|
||||
const safeLimit = Math.max(100, Math.min(500, chatPullPageSize * 3));
|
||||
const res = await axios.get<any[]>(`${APP_ENDPOINTS.apiBase}/bots/${botId}/messages`, {
|
||||
const res = await axios.get<BotMessageResponseRow[]>(`${APP_ENDPOINTS.apiBase}/bots/${botId}/messages`, {
|
||||
params: { limit: safeLimit },
|
||||
});
|
||||
const rows = Array.isArray(res.data) ? res.data : [];
|
||||
|
|
@ -171,7 +173,7 @@ export function useDashboardChatHistory({
|
|||
: Math.max(10, Math.min(500, chatPullPageSize));
|
||||
const beforeIdRaw = Number(options?.beforeId);
|
||||
const beforeId = Number.isFinite(beforeIdRaw) && beforeIdRaw > 0 ? Math.floor(beforeIdRaw) : undefined;
|
||||
const res = await axios.get<{ items?: any[]; has_more?: boolean; next_before_id?: number | null }>(
|
||||
const res = await axios.get<{ items?: BotMessageResponseRow[]; has_more?: boolean; next_before_id?: number | null }>(
|
||||
`${APP_ENDPOINTS.apiBase}/bots/${botId}/messages/page`,
|
||||
{
|
||||
params: {
|
||||
|
|
@ -364,8 +366,8 @@ export function useDashboardChatHistory({
|
|||
{ tone: 'warning' },
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || (isZh ? '按日期读取对话失败。' : 'Failed to load conversation by date.'), {
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, isZh ? '按日期读取对话失败。' : 'Failed to load conversation by date.'), {
|
||||
tone: 'error',
|
||||
});
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import { useCallback, useEffect, useState, type MutableRefObject, type RefObject
|
|||
import axios from 'axios';
|
||||
|
||||
import { APP_ENDPOINTS } from '../../../config/env';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import type { ChatMessage } from '../../../types/bot';
|
||||
import { normalizeAssistantMessageText, normalizeUserMessageText } from '../../../shared/text/messageText';
|
||||
import type { DashboardLabels } from '../localeTypes';
|
||||
import type { DashboardChatConfirmOptions, DashboardChatNotifyOptions } from './dashboardChatShared';
|
||||
|
||||
interface UseDashboardChatMessageActionsOptions {
|
||||
|
|
@ -16,7 +18,7 @@ interface UseDashboardChatMessageActionsOptions {
|
|||
setBotMessageFeedback: (botId: string, messageId: number, feedback: 'up' | 'down' | null) => void;
|
||||
notify: (message: string, options?: DashboardChatNotifyOptions) => void;
|
||||
confirm: (options: DashboardChatConfirmOptions) => Promise<boolean>;
|
||||
t: any;
|
||||
t: DashboardLabels;
|
||||
}
|
||||
|
||||
export function useDashboardChatMessageActions({
|
||||
|
|
@ -76,8 +78,8 @@ export function useDashboardChatMessageActions({
|
|||
} else {
|
||||
notify(nextFeedback === 'up' ? t.feedbackUpSaved : t.feedbackDownSaved, { tone: 'success' });
|
||||
}
|
||||
} catch (error: any) {
|
||||
const msg = error?.response?.data?.detail || t.feedbackSaveFail;
|
||||
} catch (error: unknown) {
|
||||
const msg = resolveApiErrorMessage(error, t.feedbackSaveFail);
|
||||
notify(msg, { tone: 'error' });
|
||||
} finally {
|
||||
setFeedbackSavingByMessageId((prev) => {
|
||||
|
|
@ -189,8 +191,8 @@ export function useDashboardChatMessageActions({
|
|||
await deleteConversationMessageOnServer(targetMessageId);
|
||||
removeConversationMessageLocally(message, targetMessageId);
|
||||
notify(t.deleteMessageDone, { tone: 'success' });
|
||||
} catch (error: any) {
|
||||
if (error?.response?.status === 404) {
|
||||
} catch (error: unknown) {
|
||||
if (axios.isAxiosError(error) && error.response?.status === 404) {
|
||||
try {
|
||||
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/messages/${targetMessageId}/delete`);
|
||||
removeConversationMessageLocally(message, targetMessageId);
|
||||
|
|
@ -200,7 +202,7 @@ export function useDashboardChatMessageActions({
|
|||
// continue to secondary re-match fallback below
|
||||
}
|
||||
}
|
||||
if (error?.response?.status === 404) {
|
||||
if (axios.isAxiosError(error) && error.response?.status === 404) {
|
||||
const refreshedMessageId = Number(await resolveMessageIdFromLatest(message));
|
||||
if (Number.isFinite(refreshedMessageId) && refreshedMessageId > 0 && refreshedMessageId !== targetMessageId) {
|
||||
try {
|
||||
|
|
@ -208,24 +210,24 @@ export function useDashboardChatMessageActions({
|
|||
removeConversationMessageLocally(message, refreshedMessageId);
|
||||
notify(t.deleteMessageDone, { tone: 'success' });
|
||||
return;
|
||||
} catch (retryError: any) {
|
||||
if (retryError?.response?.status === 404) {
|
||||
} catch (retryError: unknown) {
|
||||
if (axios.isAxiosError(retryError) && retryError.response?.status === 404) {
|
||||
try {
|
||||
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/messages/${refreshedMessageId}/delete`);
|
||||
removeConversationMessageLocally(message, refreshedMessageId);
|
||||
notify(t.deleteMessageDone, { tone: 'success' });
|
||||
return;
|
||||
} catch (postRetryError: any) {
|
||||
notify(postRetryError?.response?.data?.detail || t.deleteMessageFail, { tone: 'error' });
|
||||
} catch (postRetryError: unknown) {
|
||||
notify(resolveApiErrorMessage(postRetryError, t.deleteMessageFail), { tone: 'error' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
notify(retryError?.response?.data?.detail || t.deleteMessageFail, { tone: 'error' });
|
||||
notify(resolveApiErrorMessage(retryError, t.deleteMessageFail), { tone: 'error' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
notify(error?.response?.data?.detail || t.deleteMessageFail, { tone: 'error' });
|
||||
notify(resolveApiErrorMessage(error, t.deleteMessageFail), { tone: 'error' });
|
||||
} finally {
|
||||
setDeletingMessageIdMap((prev) => {
|
||||
const next = { ...prev };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import { useCallback, useState, type ChangeEvent } from 'react';
|
||||
import type { BotState } from '../../../types/bot';
|
||||
import type { BotSkillMarketItem } from '../../platform/types';
|
||||
import type { ChannelLabels, DashboardLabels } from '../localeTypes';
|
||||
import { useDashboardSkillsConfig } from './useDashboardSkillsConfig';
|
||||
import { useDashboardChannelConfig } from './useDashboardChannelConfig';
|
||||
import { useDashboardMcpConfig } from './useDashboardMcpConfig';
|
||||
|
|
@ -25,14 +28,14 @@ interface UseDashboardConfigPanelsOptions {
|
|||
confirm: (options: ConfirmOptions) => Promise<boolean>;
|
||||
cronActionJobId: string | null;
|
||||
cronActionType?: 'starting' | 'stopping' | 'deleting' | '';
|
||||
cronJobs: any[];
|
||||
cronJobs: import('../types').CronJob[];
|
||||
cronLoading: boolean;
|
||||
createEnvParam: (key: string, value: string) => Promise<boolean>;
|
||||
deleteCronJob: (jobId: string) => Promise<void>;
|
||||
deleteEnvParam: (key: string) => Promise<boolean>;
|
||||
effectiveTopicPresetTemplates: TopicPresetTemplate[];
|
||||
envEntries: [string, string][];
|
||||
installMarketSkill: (skill: any) => Promise<void>;
|
||||
installMarketSkill: (skill: BotSkillMarketItem) => Promise<void>;
|
||||
isMarketSkillsLoading: boolean;
|
||||
isSkillUploading: boolean;
|
||||
isZh: boolean;
|
||||
|
|
@ -43,7 +46,7 @@ interface UseDashboardConfigPanelsOptions {
|
|||
loadTopicFeedStats: (botId: string) => Promise<void>;
|
||||
loadWeixinLoginStatus: (botId: string, silent?: boolean) => Promise<void>;
|
||||
marketSkillInstallingId: number | null;
|
||||
marketSkills: any[];
|
||||
marketSkills: BotSkillMarketItem[];
|
||||
notify: (message: string, options?: NotifyOptions) => void;
|
||||
onPickSkillZip: (event: ChangeEvent<HTMLInputElement>) => Promise<void>;
|
||||
passwordToggleLabels: { show: string; hide: string };
|
||||
|
|
@ -52,12 +55,12 @@ interface UseDashboardConfigPanelsOptions {
|
|||
removeBotSkill: (skill: WorkspaceSkillOption) => Promise<void>;
|
||||
resetSupportState: () => void;
|
||||
saveSingleEnvParam: (originalKey: string, nextKey: string, nextValue: string) => Promise<boolean>;
|
||||
selectedBot?: any;
|
||||
selectedBot?: BotState | null;
|
||||
selectedBotId: string;
|
||||
startCronJob: (jobId: string) => Promise<void>;
|
||||
stopCronJob: (jobId: string) => Promise<void>;
|
||||
t: any;
|
||||
lc: any;
|
||||
t: DashboardLabels;
|
||||
lc: ChannelLabels;
|
||||
weixinLoginStatus: WeixinLoginStatus | null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { Dispatch, SetStateAction } from 'react';
|
||||
|
||||
import type { ChatMessage } from '../../../types/bot';
|
||||
import type { DashboardLabels } from '../localeTypes';
|
||||
import type { DashboardChatConfirmOptions, DashboardChatNotifyOptions } from './dashboardChatShared';
|
||||
import { useDashboardChatComposer } from './useDashboardChatComposer';
|
||||
import { useDashboardChatHistory } from './useDashboardChatHistory';
|
||||
|
|
@ -24,7 +25,7 @@ interface UseDashboardConversationOptions {
|
|||
setBotMessageFeedback: (botId: string, messageId: number, feedback: 'up' | 'down' | null) => void;
|
||||
notify: (message: string, options?: DashboardChatNotifyOptions) => void;
|
||||
confirm: (options: DashboardChatConfirmOptions) => Promise<boolean>;
|
||||
t: any;
|
||||
t: DashboardLabels;
|
||||
isZh: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { useEffect, type Dispatch, type MutableRefObject, type SetStateAction } from 'react';
|
||||
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
|
||||
type PromptTone = 'info' | 'success' | 'warning' | 'error';
|
||||
|
||||
interface NotifyOptions {
|
||||
|
|
@ -131,8 +133,8 @@ export function useDashboardLifecycle({
|
|||
loadInitialConfigData(selectedBotId),
|
||||
]);
|
||||
requestAnimationFrame(() => scrollConversationToBottom('auto'));
|
||||
} catch (error: any) {
|
||||
const detail = String(error?.response?.data?.detail || '').trim();
|
||||
} catch (error: unknown) {
|
||||
const detail = resolveApiErrorMessage(error, '').trim();
|
||||
if (!cancelled && detail) {
|
||||
notify(detail, { tone: 'error' });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { createMcpManager } from '../config-managers/mcpManager';
|
||||
import type { DashboardLabels, DashboardSelectedBot } from '../localeTypes';
|
||||
import type { MCPServerDraft } from '../types';
|
||||
|
||||
type PromptTone = 'info' | 'success' | 'warning' | 'error';
|
||||
|
|
@ -25,9 +26,9 @@ interface UseDashboardMcpConfigOptions {
|
|||
isZh: boolean;
|
||||
notify: (message: string, options?: NotifyOptions) => void;
|
||||
passwordToggleLabels: { show: string; hide: string };
|
||||
selectedBot?: any;
|
||||
selectedBot?: DashboardSelectedBot;
|
||||
selectedBotId: string;
|
||||
t: any;
|
||||
t: DashboardLabels;
|
||||
}
|
||||
|
||||
export function useDashboardMcpConfig({
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||
import axios from 'axios';
|
||||
|
||||
import { APP_ENDPOINTS } from '../../../config/env';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import type { BotState } from '../../../types/bot';
|
||||
import type { ChannelLabels, DashboardLabels } from '../localeTypes';
|
||||
import type { BotResourceSnapshot, NanobotImage, WeixinLoginStatus } from '../types';
|
||||
|
||||
type PromptTone = 'info' | 'success' | 'warning' | 'error';
|
||||
|
|
@ -28,8 +30,8 @@ interface UseDashboardRuntimeControlOptions {
|
|||
selectedBotId: string;
|
||||
selectedBot?: BotState;
|
||||
isZh: boolean;
|
||||
t: any;
|
||||
lc: any;
|
||||
t: DashboardLabels;
|
||||
lc: ChannelLabels;
|
||||
mergeBot: (bot: BotState) => void;
|
||||
setBots: (bots: BotState[]) => void;
|
||||
updateBotStatus: (botId: string, dockerStatus: string) => void;
|
||||
|
|
@ -112,8 +114,9 @@ export function useDashboardRuntimeControl({
|
|||
try {
|
||||
const res = await axios.get<BotResourceSnapshot>(`${APP_ENDPOINTS.apiBase}/bots/${botId}/resources`);
|
||||
setResourceSnapshot(res.data);
|
||||
} catch (error: any) {
|
||||
const msg = error?.response?.data?.detail || (isZh ? '读取资源监控失败。' : 'Failed to load resource metrics.');
|
||||
} catch (error: unknown) {
|
||||
const fallback = isZh ? '读取资源监控失败。' : 'Failed to load resource metrics.';
|
||||
const msg = resolveApiErrorMessage(error, fallback);
|
||||
setResourceError(String(msg));
|
||||
} finally {
|
||||
setResourceLoading(false);
|
||||
|
|
@ -126,7 +129,8 @@ export function useDashboardRuntimeControl({
|
|||
void loadResourceSnapshot(botId);
|
||||
}, [loadResourceSnapshot]);
|
||||
|
||||
const loadWeixinLoginStatus = useCallback(async (botId: string, _silent: boolean = false) => {
|
||||
const loadWeixinLoginStatus = useCallback(async (botId: string, silent?: boolean) => {
|
||||
void silent;
|
||||
if (!botId) {
|
||||
setWeixinLoginStatus(null);
|
||||
return;
|
||||
|
|
@ -169,8 +173,8 @@ export function useDashboardRuntimeControl({
|
|||
notify(lc.weixinReloginDone, { tone: 'success' });
|
||||
await loadWeixinLoginStatus(selectedBot.id);
|
||||
await refresh();
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || lc.weixinReloginFail, { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, lc.weixinReloginFail), { tone: 'error' });
|
||||
}
|
||||
}, [lc.weixinReloginDone, lc.weixinReloginFail, loadWeixinLoginStatus, notify, refresh, selectedBot]);
|
||||
|
||||
|
|
@ -289,9 +293,9 @@ export function useDashboardRuntimeControl({
|
|||
updateBotStatus(id, 'RUNNING');
|
||||
await refresh();
|
||||
await ensureControlVisible(startedAt);
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
await refresh();
|
||||
notify(error?.response?.data?.detail || t.startFail, { tone: 'error' });
|
||||
notify(resolveApiErrorMessage(error, t.startFail), { tone: 'error' });
|
||||
await ensureControlVisible(startedAt);
|
||||
} finally {
|
||||
setOperatingBotId(null);
|
||||
|
|
@ -320,9 +324,9 @@ export function useDashboardRuntimeControl({
|
|||
updateBotStatus(id, 'RUNNING');
|
||||
await refresh();
|
||||
await ensureControlVisible(startedAt);
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
await refresh();
|
||||
notify(error?.response?.data?.detail || t.restartFail, { tone: 'error' });
|
||||
notify(resolveApiErrorMessage(error, t.restartFail), { tone: 'error' });
|
||||
await ensureControlVisible(startedAt);
|
||||
} finally {
|
||||
setOperatingBotId(null);
|
||||
|
|
@ -347,8 +351,8 @@ export function useDashboardRuntimeControl({
|
|||
}
|
||||
await refresh();
|
||||
notify(enabled ? t.enableDone : t.disableDone, { tone: 'success' });
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || (enabled ? t.enableFail : t.disableFail), { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, enabled ? t.enableFail : t.disableFail), { tone: 'error' });
|
||||
} finally {
|
||||
setOperatingBotId(null);
|
||||
clearControlState(id);
|
||||
|
|
|
|||
|
|
@ -1,23 +1,25 @@
|
|||
import { useCallback, useEffect, useRef, useState, type ChangeEvent, type Dispatch, type SetStateAction } from 'react';
|
||||
|
||||
import type { BotSkillMarketItem } from '../../platform/types';
|
||||
import type { DashboardLabels, DashboardSelectedBot } from '../localeTypes';
|
||||
import type { WorkspaceSkillOption } from '../types';
|
||||
import { formatBytes } from '../utils';
|
||||
|
||||
interface UseDashboardSkillsConfigOptions {
|
||||
botSkills: WorkspaceSkillOption[];
|
||||
closeRuntimeMenu: () => void;
|
||||
installMarketSkill: (skill: any) => Promise<void>;
|
||||
installMarketSkill: (skill: BotSkillMarketItem) => Promise<void>;
|
||||
isMarketSkillsLoading: boolean;
|
||||
isSkillUploading: boolean;
|
||||
isZh: boolean;
|
||||
labels: Record<string, any>;
|
||||
labels: DashboardLabels;
|
||||
loadBotSkills: (botId: string) => Promise<void>;
|
||||
loadMarketSkills: (botId: string) => Promise<void>;
|
||||
marketSkillInstallingId: number | null;
|
||||
marketSkills: any[];
|
||||
marketSkills: BotSkillMarketItem[];
|
||||
onPickSkillZip: (event: ChangeEvent<HTMLInputElement>) => Promise<void>;
|
||||
removeBotSkill: (skill: WorkspaceSkillOption) => Promise<void>;
|
||||
selectedBot?: any;
|
||||
selectedBot?: DashboardSelectedBot;
|
||||
}
|
||||
|
||||
export function useDashboardSkillsConfig({
|
||||
|
|
@ -115,7 +117,7 @@ export function useDashboardSkillsConfig({
|
|||
if (!selectedBot) return;
|
||||
await loadMarketSkills(selectedBot.id);
|
||||
},
|
||||
onInstall: async (skill: any) => {
|
||||
onInstall: async (skill: BotSkillMarketItem) => {
|
||||
await installMarketSkill(skill);
|
||||
if (selectedBot) {
|
||||
await loadBotSkills(selectedBot.id);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import { useCallback, useMemo, useState, type ChangeEvent } from 'react';
|
|||
import axios from 'axios';
|
||||
|
||||
import { APP_ENDPOINTS } from '../../../config/env';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import type { BotSkillMarketItem } from '../../platform/types';
|
||||
import type { DashboardLabels } from '../localeTypes';
|
||||
import type {
|
||||
BotEnvParams,
|
||||
CronJob,
|
||||
|
|
@ -34,7 +36,7 @@ interface ConfirmOptions {
|
|||
interface UseDashboardSupportDataOptions {
|
||||
selectedBotId: string;
|
||||
isZh: boolean;
|
||||
t: any;
|
||||
t: DashboardLabels;
|
||||
notify: (message: string, options?: NotifyOptions) => void;
|
||||
confirm: (options: ConfirmOptions) => Promise<boolean>;
|
||||
onCloseEnvParamsModal?: () => void;
|
||||
|
|
@ -138,12 +140,12 @@ export function useDashboardSupportData({
|
|||
return merged;
|
||||
});
|
||||
if (!append) setTopicFeedError('');
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
if (!append) {
|
||||
setTopicFeedItems([]);
|
||||
setTopicFeedNextCursor(null);
|
||||
}
|
||||
setTopicFeedError(error?.response?.data?.detail || (isZh ? '读取主题消息失败。' : 'Failed to load topic feed.'));
|
||||
setTopicFeedError(resolveApiErrorMessage(error, isZh ? '读取主题消息失败。' : 'Failed to load topic feed.'));
|
||||
} finally {
|
||||
if (append) {
|
||||
setTopicFeedLoadingMore(false);
|
||||
|
|
@ -202,12 +204,12 @@ export function useDashboardSupportData({
|
|||
try {
|
||||
await axios.delete(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/topic-items/${targetId}`);
|
||||
setTopicFeedItems((prev) => prev.filter((row) => Number(row.id) !== targetId));
|
||||
if (!Boolean(item?.is_read)) {
|
||||
if (!item?.is_read) {
|
||||
setTopicFeedUnreadCount((prev) => Math.max(0, prev - 1));
|
||||
}
|
||||
notify(isZh ? '主题消息已删除。' : 'Topic item deleted.', { tone: 'success' });
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || (isZh ? '删除主题消息失败。' : 'Failed to delete topic item.'), {
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, isZh ? '删除主题消息失败。' : 'Failed to delete topic item.'), {
|
||||
tone: 'error',
|
||||
});
|
||||
} finally {
|
||||
|
|
@ -231,9 +233,9 @@ export function useDashboardSupportData({
|
|||
try {
|
||||
const res = await axios.get<BotSkillMarketItem[]>(`${APP_ENDPOINTS.apiBase}/bots/${botId}/skill-market`);
|
||||
setMarketSkills(Array.isArray(res.data) ? res.data : []);
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
setMarketSkills([]);
|
||||
notify(error?.response?.data?.detail || t.toolsLoadFail, { tone: 'error' });
|
||||
notify(resolveApiErrorMessage(error, t.toolsLoadFail), { tone: 'error' });
|
||||
} finally {
|
||||
setIsMarketSkillsLoading(false);
|
||||
}
|
||||
|
|
@ -268,8 +270,8 @@ export function useDashboardSupportData({
|
|||
}
|
||||
notify(t.envParamsSaved, { tone: 'success' });
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || t.envParamsSaveFail, { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, t.envParamsSaveFail), { tone: 'error' });
|
||||
return false;
|
||||
}
|
||||
}, [notify, onCloseEnvParamsModal, selectedBotId, t.envParamsSaveFail, t.envParamsSaved]);
|
||||
|
|
@ -320,10 +322,10 @@ export function useDashboardSupportData({
|
|||
await axios.delete(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/skills/${encodeURIComponent(skill.id)}`);
|
||||
await loadBotSkills(selectedBotId);
|
||||
await loadMarketSkills(selectedBotId);
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || t.toolsRemoveFail, { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, t.toolsRemoveFail), { tone: 'error' });
|
||||
}
|
||||
}, [confirm, loadBotSkills, loadMarketSkills, notify, selectedBotId, t.removeSkill, t.toolsRemoveConfirm, t.toolsRemoveFail]);
|
||||
}, [confirm, loadBotSkills, loadMarketSkills, notify, selectedBotId, t]);
|
||||
|
||||
const installMarketSkill = useCallback(async (marketSkill: BotSkillMarketItem) => {
|
||||
if (!selectedBotId) return;
|
||||
|
|
@ -342,8 +344,8 @@ export function useDashboardSupportData({
|
|||
: `Installed: ${(res.data?.installed || []).join(', ') || marketSkill.display_name || marketSkill.skill_key}`,
|
||||
{ tone: 'success' },
|
||||
);
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || t.toolsAddFail, { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, t.toolsAddFail), { tone: 'error' });
|
||||
await loadMarketSkills(selectedBotId);
|
||||
} finally {
|
||||
setMarketSkillInstallingId(null);
|
||||
|
|
@ -370,8 +372,8 @@ export function useDashboardSupportData({
|
|||
const nextSkills = Array.isArray(res.data?.skills) ? res.data.skills : [];
|
||||
setBotSkills(nextSkills);
|
||||
await loadMarketSkills(selectedBotId);
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || t.toolsAddFail, { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, t.toolsAddFail), { tone: 'error' });
|
||||
} finally {
|
||||
setIsSkillUploading(false);
|
||||
event.target.value = '';
|
||||
|
|
@ -400,8 +402,8 @@ export function useDashboardSupportData({
|
|||
try {
|
||||
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/cron/jobs/${jobId}/start`);
|
||||
await loadCronJobs(selectedBotId);
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || t.cronStartFail, { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, t.cronStartFail), { tone: 'error' });
|
||||
} finally {
|
||||
setCronActionJobId('');
|
||||
setCronActionType('');
|
||||
|
|
@ -415,8 +417,8 @@ export function useDashboardSupportData({
|
|||
try {
|
||||
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/cron/jobs/${jobId}/stop`);
|
||||
await loadCronJobs(selectedBotId);
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || t.cronStopFail, { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, t.cronStopFail), { tone: 'error' });
|
||||
} finally {
|
||||
setCronActionJobId('');
|
||||
setCronActionType('');
|
||||
|
|
@ -436,13 +438,13 @@ export function useDashboardSupportData({
|
|||
try {
|
||||
await axios.delete(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/cron/jobs/${jobId}`);
|
||||
await loadCronJobs(selectedBotId);
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || t.cronDeleteFail, { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, t.cronDeleteFail), { tone: 'error' });
|
||||
} finally {
|
||||
setCronActionJobId('');
|
||||
setCronActionType('');
|
||||
}
|
||||
}, [confirm, loadCronJobs, notify, selectedBotId, t.cronDelete, t.cronDeleteConfirm, t.cronDeleteFail]);
|
||||
}, [confirm, loadCronJobs, notify, selectedBotId, t]);
|
||||
|
||||
return {
|
||||
botSkills,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import type { DashboardLabels } from '../localeTypes';
|
||||
import { fetchDashboardSystemTemplates, updateDashboardSystemTemplates } from '../api/system';
|
||||
import { parseTopicPresets } from '../topic/topicPresetUtils';
|
||||
import type { TopicPresetTemplate } from '../types';
|
||||
|
|
@ -16,7 +17,7 @@ interface UseDashboardTemplateManagerOptions {
|
|||
currentTopicPresetCount: number;
|
||||
notify: (message: string, options?: NotifyOptions) => void;
|
||||
setTopicPresetTemplates: (value: TopicPresetTemplate[]) => void;
|
||||
t: any;
|
||||
t: DashboardLabels;
|
||||
}
|
||||
|
||||
function countAgentTemplateFields(raw: string) {
|
||||
|
|
@ -109,8 +110,9 @@ export function useDashboardTemplateManager({
|
|||
topic_presets: parsedTopic,
|
||||
};
|
||||
}
|
||||
} catch (error: any) {
|
||||
notify(error?.message || t.templateParseFail, { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : t.templateParseFail;
|
||||
notify(message || t.templateParseFail, { tone: 'error' });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { createTopicManager } from '../config-managers/topicManager';
|
||||
import type { DashboardLabels, DashboardSelectedBot } from '../localeTypes';
|
||||
import { resolvePresetText } from '../topic/topicPresetUtils';
|
||||
import type { BotTopic, TopicPresetTemplate } from '../types';
|
||||
|
||||
|
|
@ -26,9 +27,9 @@ interface UseDashboardTopicConfigOptions {
|
|||
effectiveTopicPresetTemplates: TopicPresetTemplate[];
|
||||
isZh: boolean;
|
||||
notify: (message: string, options?: NotifyOptions) => void;
|
||||
selectedBot?: any;
|
||||
selectedBot?: DashboardSelectedBot;
|
||||
selectedBotId: string;
|
||||
t: any;
|
||||
t: DashboardLabels;
|
||||
}
|
||||
|
||||
export function useDashboardTopicConfig({
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import { useEffect, useRef, useState, type Dispatch, type RefObject, type SetSta
|
|||
import axios from 'axios';
|
||||
|
||||
import { APP_ENDPOINTS } from '../../../config/env';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import { normalizeUserMessageText } from '../../../shared/text/messageText';
|
||||
import type { DashboardLabels } from '../localeTypes';
|
||||
|
||||
type PromptTone = 'info' | 'success' | 'warning' | 'error';
|
||||
|
||||
|
|
@ -20,7 +22,7 @@ interface UseDashboardVoiceInputOptions {
|
|||
setCommand: Dispatch<SetStateAction<string>>;
|
||||
composerTextareaRef: RefObject<HTMLTextAreaElement | null>;
|
||||
notify: (message: string, options?: NotifyOptions) => void;
|
||||
t: any;
|
||||
t: DashboardLabels;
|
||||
}
|
||||
|
||||
export function useDashboardVoiceInput({
|
||||
|
|
@ -89,13 +91,11 @@ export function useDashboardVoiceInput({
|
|||
});
|
||||
window.requestAnimationFrame(() => composerTextareaRef.current?.focus());
|
||||
notify(t.voiceTranscribeDone, { tone: 'success' });
|
||||
} catch (error: any) {
|
||||
const msg = String(error?.response?.data?.detail || '').trim();
|
||||
} catch (error: unknown) {
|
||||
const msg = resolveApiErrorMessage(error, '').trim();
|
||||
console.error('Speech transcription failed', {
|
||||
botId,
|
||||
message: msg || t.voiceTranscribeFail,
|
||||
status: error?.response?.status,
|
||||
response: error?.response?.data,
|
||||
error,
|
||||
});
|
||||
notify(msg || t.voiceTranscribeFail, { tone: 'error' });
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import { channelsEn } from '../../i18n/channels.en';
|
||||
import { dashboardEn } from '../../i18n/dashboard.en';
|
||||
import type { BotState } from '../../types/bot';
|
||||
|
||||
type WidenLocaleValue<T> =
|
||||
T extends string ? string
|
||||
: T extends number ? number
|
||||
: T extends boolean ? boolean
|
||||
: T extends (...args: infer Args) => infer Result ? (...args: Args) => Result
|
||||
: T extends readonly (infer Item)[] ? WidenLocaleValue<Item>[]
|
||||
: T extends object ? { [K in keyof T]: WidenLocaleValue<T[K]> }
|
||||
: T;
|
||||
|
||||
export type DashboardLabels = WidenLocaleValue<typeof dashboardEn>;
|
||||
export type ChannelLabels = WidenLocaleValue<typeof channelsEn>;
|
||||
export type DashboardChannelLabels = DashboardLabels & ChannelLabels;
|
||||
export type DashboardSelectedBot = BotState | null | undefined;
|
||||
|
|
@ -274,7 +274,7 @@ export function TopicFeedPanel({
|
|||
const itemId = Number(item.id || 0);
|
||||
const level = String(item.level || 'info').trim().toLowerCase();
|
||||
const levelText = level === 'warn' ? 'WARN' : level === 'error' ? 'ERROR' : level === 'success' ? 'SUCCESS' : 'INFO';
|
||||
const unread = !Boolean(item.is_read);
|
||||
const unread = !item.is_read;
|
||||
const card = deriveTopicSummaryCard(item);
|
||||
const rawContent = String(item.content || '').trim();
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -22,8 +22,17 @@ export type StagedSubmissionDraft = {
|
|||
};
|
||||
export type BotEnvParams = Record<string, string>;
|
||||
|
||||
export interface BotMessageResponseRow {
|
||||
id?: number | string | null;
|
||||
role?: string | null;
|
||||
feedback?: string | null;
|
||||
text?: string | null;
|
||||
media?: unknown;
|
||||
ts?: number | string | null;
|
||||
}
|
||||
|
||||
export interface BotMessagesByDateResponse {
|
||||
items?: any[];
|
||||
items?: BotMessageResponseRow[];
|
||||
anchor_id?: number | null;
|
||||
resolved_ts?: number | null;
|
||||
matched_exact_date?: boolean;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ import { useEffect, useMemo, useState } from 'react';
|
|||
import axios from 'axios';
|
||||
import { RefreshCw, Trash2 } from 'lucide-react';
|
||||
import { APP_ENDPOINTS } from '../../config/env';
|
||||
import { resolveApiErrorMessage } from '../../shared/http/apiErrors';
|
||||
import { useAppStore } from '../../store/appStore';
|
||||
import { pickLocale } from '../../i18n';
|
||||
import { imageFactoryZhCn } from '../../i18n/image-factory.zh-cn';
|
||||
import { imageFactoryEn } from '../../i18n/image-factory.en';
|
||||
import { useLucentPrompt } from '../../components/lucent/LucentPromptProvider';
|
||||
import { imageFactoryZhCn } from '../../i18n/image-factory.zh-cn';
|
||||
import { useLucentPrompt } from '../../components/lucent/useLucentPrompt';
|
||||
import { LucentIconButton } from '../../components/lucent/LucentIconButton';
|
||||
|
||||
interface NanobotImage {
|
||||
|
|
@ -100,8 +101,8 @@ export function ImageFactoryModule() {
|
|||
source_dir: 'manual',
|
||||
});
|
||||
await fetchData();
|
||||
} catch (error: any) {
|
||||
const msg = error?.response?.data?.detail || t.registerFail;
|
||||
} catch (error: unknown) {
|
||||
const msg = resolveApiErrorMessage(error, t.registerFail);
|
||||
notify(msg, { tone: 'error' });
|
||||
} finally {
|
||||
setIsRegisteringTag('');
|
||||
|
|
@ -120,8 +121,8 @@ export function ImageFactoryModule() {
|
|||
try {
|
||||
await axios.delete(`${APP_ENDPOINTS.apiBase}/images/${encodeURIComponent(tag)}`);
|
||||
await fetchData();
|
||||
} catch (error: any) {
|
||||
const msg = error?.response?.data?.detail || t.deleteFail;
|
||||
} catch (error: unknown) {
|
||||
const msg = resolveApiErrorMessage(error, t.deleteFail);
|
||||
notify(msg, { tone: 'error' });
|
||||
} finally {
|
||||
setIsDeletingTag('');
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { channelsZhCn } from '../../i18n/channels.zh-cn';
|
|||
import { wizardEn } from '../../i18n/wizard.en';
|
||||
import { wizardZhCn } from '../../i18n/wizard.zh-cn';
|
||||
import { LucentIconButton } from '../../components/lucent/LucentIconButton';
|
||||
import { useLucentPrompt } from '../../components/lucent/LucentPromptProvider';
|
||||
import { useLucentPrompt } from '../../components/lucent/useLucentPrompt';
|
||||
import { useAppStore } from '../../store/appStore';
|
||||
import { BotWizardAgentStep } from './components/BotWizardAgentStep';
|
||||
import { BotWizardBaseStep } from './components/BotWizardBaseStep';
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@ import { LucentIconButton } from '../../../components/lucent/LucentIconButton';
|
|||
import { LucentSelect } from '../../../components/lucent/LucentSelect';
|
||||
import { PasswordInput } from '../../../components/PasswordInput';
|
||||
import { buildLlmProviderOptions } from '../../../utils/llmProviders';
|
||||
import type { OnboardingChannelLabels, WizardLabels } from '../localeTypes';
|
||||
import type { BotWizardForm } from '../types';
|
||||
|
||||
interface BotWizardBaseStepProps {
|
||||
isZh: boolean;
|
||||
ui: any;
|
||||
lc: any;
|
||||
ui: WizardLabels;
|
||||
lc: OnboardingChannelLabels;
|
||||
passwordToggleLabels: { show: string; hide: string };
|
||||
form: BotWizardForm;
|
||||
setForm: Dispatch<SetStateAction<BotWizardForm>>;
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ import { Plus, Trash2 } from 'lucide-react';
|
|||
import { LucentIconButton } from '../../../components/lucent/LucentIconButton';
|
||||
import { LucentSelect } from '../../../components/lucent/LucentSelect';
|
||||
import { PasswordInput } from '../../../components/PasswordInput';
|
||||
import type { OnboardingChannelLabels } from '../localeTypes';
|
||||
import type { ChannelType, WizardChannelConfig } from '../types';
|
||||
import { EMPTY_CHANNEL_PICKER } from '../types';
|
||||
|
||||
interface BotWizardChannelModalProps {
|
||||
open: boolean;
|
||||
lc: any;
|
||||
lc: OnboardingChannelLabels;
|
||||
passwordToggleLabels: { show: string; hide: string };
|
||||
channels: WizardChannelConfig[];
|
||||
sendProgress: boolean;
|
||||
|
|
@ -32,7 +33,7 @@ function renderChannelFields({
|
|||
}: {
|
||||
channel: WizardChannelConfig;
|
||||
idx: number;
|
||||
lc: any;
|
||||
lc: OnboardingChannelLabels;
|
||||
passwordToggleLabels: { show: string; hide: string };
|
||||
onUpdateChannel: (index: number, patch: Partial<WizardChannelConfig>) => void;
|
||||
}) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import type { Dispatch, SetStateAction } from 'react';
|
||||
|
||||
import type { WizardLabels } from '../localeTypes';
|
||||
import type { BotWizardForm, NanobotImage } from '../types';
|
||||
|
||||
interface BotWizardImageStepProps {
|
||||
ui: any;
|
||||
ui: WizardLabels;
|
||||
readyImages: NanobotImage[];
|
||||
isLoadingImages: boolean;
|
||||
form: BotWizardForm;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import type { WizardLabels } from '../localeTypes';
|
||||
import type { BotWizardForm } from '../types';
|
||||
|
||||
interface BotWizardReviewStepProps {
|
||||
isZh: boolean;
|
||||
ui: any;
|
||||
ui: WizardLabels;
|
||||
autoStart: boolean;
|
||||
setAutoStart: (value: boolean) => void;
|
||||
form: BotWizardForm;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ import { Plus, Trash2 } from 'lucide-react';
|
|||
|
||||
import { LucentIconButton } from '../../../components/lucent/LucentIconButton';
|
||||
import { PasswordInput } from '../../../components/PasswordInput';
|
||||
import type { WizardLabels } from '../localeTypes';
|
||||
|
||||
interface BotWizardToolsModalProps {
|
||||
open: boolean;
|
||||
ui: any;
|
||||
ui: WizardLabels;
|
||||
closeLabel: string;
|
||||
envEntries: Array<[string, string]>;
|
||||
envDraftKey: string;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import { useEffect, useMemo, useRef, useState } from 'react';
|
|||
import axios from 'axios';
|
||||
|
||||
import { APP_ENDPOINTS } from '../../../config/env';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import { getLlmProviderDefaultApiBase } from '../../../utils/llmProviders';
|
||||
import { getSystemTimezoneOptions } from '../../../utils/systemTimezones';
|
||||
import type { WizardLabels } from '../localeTypes';
|
||||
import type {
|
||||
AgentTab,
|
||||
BotWizardForm,
|
||||
|
|
@ -26,7 +28,7 @@ interface NotifyOptions {
|
|||
}
|
||||
|
||||
interface UseBotWizardOptions {
|
||||
ui: any;
|
||||
ui: WizardLabels;
|
||||
notify: (message: string, options?: NotifyOptions) => void;
|
||||
onCreated?: () => void;
|
||||
onGoDashboard?: () => void;
|
||||
|
|
@ -172,8 +174,8 @@ export function useBotWizard({
|
|||
await axios.get(`${APP_ENDPOINTS.apiBase}/bots/${encodeURIComponent(raw)}`);
|
||||
setBotIdStatus('exists');
|
||||
setBotIdStatusText(ui.botIdExists);
|
||||
} catch (error: any) {
|
||||
if (error?.response?.status === 404) {
|
||||
} catch (error: unknown) {
|
||||
if (axios.isAxiosError(error) && error.response?.status === 404) {
|
||||
setBotIdStatus('available');
|
||||
setBotIdStatusText(ui.botIdAvailable);
|
||||
return;
|
||||
|
|
@ -292,8 +294,8 @@ export function useBotWizard({
|
|||
} else {
|
||||
setTestResult(ui.connFailed(res.data?.detail || 'unknown error'));
|
||||
}
|
||||
} catch (error: any) {
|
||||
const msg = error?.response?.data?.detail || error?.message || 'request failed';
|
||||
} catch (error: unknown) {
|
||||
const msg = resolveApiErrorMessage(error, 'request failed');
|
||||
setTestResult(ui.connFailed(msg));
|
||||
} finally {
|
||||
setIsTestingProvider(false);
|
||||
|
|
@ -355,12 +357,12 @@ export function useBotWizard({
|
|||
setBotIdStatus('idle');
|
||||
setBotIdStatusText('');
|
||||
notify(ui.created, { tone: 'success' });
|
||||
} catch (error: any) {
|
||||
if (axios.isCancel(error) || error?.code === 'ERR_CANCELED') {
|
||||
} catch (error: unknown) {
|
||||
if (axios.isAxiosError(error) && (axios.isCancel(error) || error.code === 'ERR_CANCELED')) {
|
||||
notify(ui.createCanceled, { tone: 'info' });
|
||||
return;
|
||||
}
|
||||
const msg = error?.response?.data?.detail || ui.createFailed;
|
||||
const msg = resolveApiErrorMessage(error, ui.createFailed);
|
||||
notify(msg, { tone: 'error' });
|
||||
} finally {
|
||||
if (createAbortControllerRef.current === controller) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
import { channelsEn } from '../../i18n/channels.en';
|
||||
import { wizardEn } from '../../i18n/wizard.en';
|
||||
|
||||
type WidenLocaleValue<T> =
|
||||
T extends string ? string
|
||||
: T extends number ? number
|
||||
: T extends boolean ? boolean
|
||||
: T extends (...args: infer Args) => infer Result ? (...args: Args) => Result
|
||||
: T extends readonly (infer Item)[] ? WidenLocaleValue<Item>[]
|
||||
: T extends object ? { [K in keyof T]: WidenLocaleValue<T[K]> }
|
||||
: T;
|
||||
|
||||
export type WizardLabels = WidenLocaleValue<typeof wizardEn>;
|
||||
export type OnboardingChannelLabels = WidenLocaleValue<typeof channelsEn>;
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ChevronLeft, ChevronRight, RefreshCw, ShieldCheck } from 'lucide-react';
|
||||
|
||||
import '../../components/skill-market/SkillMarketShared.css';
|
||||
import { ProtectedSearchInput } from '../../components/ProtectedSearchInput';
|
||||
import { LucentIconButton } from '../../components/lucent/LucentIconButton';
|
||||
import { LucentSelect } from '../../components/lucent/LucentSelect';
|
||||
import { useLucentPrompt } from '../../components/lucent/LucentPromptProvider';
|
||||
import { useLucentPrompt } from '../../components/lucent/useLucentPrompt';
|
||||
import { resolveApiErrorMessage } from '../../shared/http/apiErrors';
|
||||
import { fetchPlatformLoginLogs, fetchPreferredPlatformPageSize } from './api/settings';
|
||||
import type { PlatformLoginLogItem } from './types';
|
||||
import './PlatformDashboardPage.css';
|
||||
|
|
@ -65,7 +66,7 @@ export function PlatformLoginLogPage({ isZh }: PlatformLoginLogPageProps) {
|
|||
|
||||
const pageCount = Math.max(1, Math.ceil(total / Math.max(1, pageSize)));
|
||||
|
||||
const loadRows = async () => {
|
||||
const loadRows = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await fetchPlatformLoginLogs({
|
||||
|
|
@ -77,14 +78,14 @@ export function PlatformLoginLogPage({ isZh }: PlatformLoginLogPageProps) {
|
|||
});
|
||||
setItems(Array.isArray(data?.items) ? data.items : []);
|
||||
setTotal(Number(data?.total || 0));
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || (isZh ? '读取登录日志失败。' : 'Failed to load login logs.'), {
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, isZh ? '读取登录日志失败。' : 'Failed to load login logs.'), {
|
||||
tone: 'error',
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}, [authType, isZh, notify, page, pageSize, search, status]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadRows();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import axios from 'axios';
|
|||
import { APP_ENDPOINTS } from '../../../config/env';
|
||||
import type { SkillMarketItem } from '../types';
|
||||
|
||||
export interface SkillMarketListResponse extends Array<SkillMarketItem> {}
|
||||
export type SkillMarketListResponse = SkillMarketItem[];
|
||||
|
||||
export function fetchPlatformSkillMarket() {
|
||||
return axios.get<SkillMarketListResponse>(`${APP_ENDPOINTS.apiBase}/platform/skills`).then((res) => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { ChevronLeft, ChevronRight, RefreshCw, Terminal } from 'lucide-react';
|
|||
|
||||
import { ProtectedSearchInput } from '../../../components/ProtectedSearchInput';
|
||||
import { LucentIconButton } from '../../../components/lucent/LucentIconButton';
|
||||
import { useLucentPrompt } from '../../../components/lucent/LucentPromptProvider';
|
||||
import { useLucentPrompt } from '../../../components/lucent/useLucentPrompt';
|
||||
import { dashboardEn } from '../../../i18n/dashboard.en';
|
||||
import { dashboardZhCn } from '../../../i18n/dashboard.zh-cn';
|
||||
import { pickLocale } from '../../../i18n';
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ChevronLeft, ChevronRight, Pencil, Plus, RefreshCw, Settings2, Trash2 } from 'lucide-react';
|
||||
|
||||
import '../../../components/skill-market/SkillMarketShared.css';
|
||||
import { DrawerShell } from '../../../components/DrawerShell';
|
||||
import { ProtectedSearchInput } from '../../../components/ProtectedSearchInput';
|
||||
import { LucentIconButton } from '../../../components/lucent/LucentIconButton';
|
||||
import { useLucentPrompt } from '../../../components/lucent/LucentPromptProvider';
|
||||
import { useLucentPrompt } from '../../../components/lucent/useLucentPrompt';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import type { PlatformSettings, SystemSettingItem } from '../types';
|
||||
import {
|
||||
createPlatformSystemSetting,
|
||||
|
|
@ -88,19 +89,19 @@ function PlatformSettingsView({
|
|||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const loadRows = async () => {
|
||||
const loadRows = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await fetchPlatformSystemSettings();
|
||||
setItems(Array.isArray(data?.items) ? data.items : []);
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || (isZh ? '读取系统参数失败。' : 'Failed to load system settings.'), { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, isZh ? '读取系统参数失败。' : 'Failed to load system settings.'), { tone: 'error' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}, [isZh, notify]);
|
||||
|
||||
const refreshSnapshot = async () => {
|
||||
const refreshSnapshot = useCallback(async () => {
|
||||
try {
|
||||
const data = await fetchPlatformSettings();
|
||||
onSaved(data);
|
||||
|
|
@ -108,14 +109,14 @@ function PlatformSettingsView({
|
|||
// Ignore snapshot refresh failures here; the table is still the source of truth in the view.
|
||||
}
|
||||
setPageSize(await fetchPreferredPlatformPageSize(10));
|
||||
};
|
||||
}, [onSaved]);
|
||||
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
void (async () => {
|
||||
await Promise.allSettled([loadRows(), refreshSnapshot()]);
|
||||
})();
|
||||
}, []);
|
||||
}, [loadRows, refreshSnapshot]);
|
||||
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
|
|
@ -159,10 +160,10 @@ function PlatformSettingsView({
|
|||
await refreshSnapshot();
|
||||
notify(isZh ? '系统参数已保存。' : 'System setting saved.', { tone: 'success' });
|
||||
setShowEditor(false);
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
const detail = error instanceof SyntaxError
|
||||
? (isZh ? 'JSON 参数格式错误。' : 'Invalid JSON value.')
|
||||
: (error?.response?.data?.detail || (isZh ? '保存参数失败。' : 'Failed to save setting.'));
|
||||
: resolveApiErrorMessage(error, isZh ? '保存参数失败。' : 'Failed to save setting.');
|
||||
notify(detail, { tone: 'error' });
|
||||
} finally {
|
||||
setSaving(false);
|
||||
|
|
@ -268,8 +269,8 @@ function PlatformSettingsView({
|
|||
await deletePlatformSystemSetting(item.key);
|
||||
await loadRows();
|
||||
await refreshSnapshot();
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || (isZh ? '删除失败。' : 'Delete failed.'), { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, isZh ? '删除失败。' : 'Delete failed.'), { tone: 'error' });
|
||||
}
|
||||
})();
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { useEffect, useMemo, useState, type ChangeEvent } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState, type ChangeEvent } from 'react';
|
||||
import { ChevronLeft, ChevronRight, FileArchive, Hammer, Pencil, Plus, RefreshCw, Trash2, Upload } from 'lucide-react';
|
||||
import '../../../components/skill-market/SkillMarketShared.css';
|
||||
import { DrawerShell } from '../../../components/DrawerShell';
|
||||
import { ProtectedSearchInput } from '../../../components/ProtectedSearchInput';
|
||||
import type { SkillMarketItem } from '../types';
|
||||
import { LucentIconButton } from '../../../components/lucent/LucentIconButton';
|
||||
import { useLucentPrompt } from '../../../components/lucent/LucentPromptProvider';
|
||||
import { useLucentPrompt } from '../../../components/lucent/useLucentPrompt';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import { fetchPreferredPlatformPageSize } from '../api/settings';
|
||||
import {
|
||||
createPlatformSkillMarketItem,
|
||||
|
|
@ -74,21 +75,21 @@ function SkillMarketManagerView({
|
|||
? '技能市场仅接收人工上传的 ZIP 技能包。平台将统一保存技能元数据与归档文件,并为 Bot 安装提供标准化来源,不再自动扫描 /data/skills 目录。'
|
||||
: 'The marketplace accepts manually uploaded ZIP skill packages only. The platform stores the package metadata and archives as the standard source for bot installation, without auto-scanning /data/skills.';
|
||||
|
||||
const loadRows = async () => {
|
||||
const loadRows = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const rows = await fetchPlatformSkillMarket();
|
||||
setItems(rows);
|
||||
return rows;
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || (isZh ? '读取技能市场失败。' : 'Failed to load the skill marketplace.'), {
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, isZh ? '读取技能市场失败。' : 'Failed to load the skill marketplace.'), {
|
||||
tone: 'error',
|
||||
});
|
||||
return [] as SkillMarketItem[];
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}, [isZh, notify]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadRows();
|
||||
|
|
@ -96,7 +97,7 @@ function SkillMarketManagerView({
|
|||
void (async () => {
|
||||
setPageSize(await fetchPreferredPlatformPageSize(10));
|
||||
})();
|
||||
}, []);
|
||||
}, [loadRows]);
|
||||
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
|
|
@ -167,8 +168,8 @@ function SkillMarketManagerView({
|
|||
setPage(Math.floor(nextIndex / pageSize) + 1);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || (isZh ? '保存技能失败。' : 'Failed to save skill.'), { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, isZh ? '保存技能失败。' : 'Failed to save skill.'), { tone: 'error' });
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
@ -190,8 +191,8 @@ function SkillMarketManagerView({
|
|||
}
|
||||
await loadRows();
|
||||
notify(isZh ? '技能已删除。' : 'Skill deleted.', { tone: 'success' });
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || (isZh ? '删除技能失败。' : 'Failed to delete skill.'), { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, isZh ? '删除技能失败。' : 'Failed to delete skill.'), { tone: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { FileText, RefreshCw } from 'lucide-react';
|
||||
|
||||
import '../../../components/skill-market/SkillMarketShared.css';
|
||||
import { useLucentPrompt } from '../../../components/lucent/LucentPromptProvider';
|
||||
import { useLucentPrompt } from '../../../components/lucent/useLucentPrompt';
|
||||
import { MarkdownLiteEditor } from '../../../components/markdown/MarkdownLiteEditor';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import { fetchPlatformSystemTemplates, updatePlatformSystemTemplates } from '../api/templates';
|
||||
|
||||
interface TemplateManagerPageProps {
|
||||
|
|
@ -71,33 +72,45 @@ function TemplateManagerView({
|
|||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const applyTemplates = useCallback((res: Awaited<ReturnType<typeof fetchPlatformSystemTemplates>>) => {
|
||||
setTemplates({
|
||||
soul_md: String(res?.agent_md_templates?.soul_md || ''),
|
||||
agents_md: String(res?.agent_md_templates?.agents_md || ''),
|
||||
user_md: String(res?.agent_md_templates?.user_md || ''),
|
||||
tools_md: String(res?.agent_md_templates?.tools_md || ''),
|
||||
identity_md: String(res?.agent_md_templates?.identity_md || ''),
|
||||
});
|
||||
setTopicPresetsText(JSON.stringify(res?.topic_presets || { presets: [] }, null, 2));
|
||||
}, []);
|
||||
|
||||
const reloadTemplates = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetchPlatformSystemTemplates();
|
||||
applyTemplates(res);
|
||||
return true;
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, isZh ? '读取模板失败。' : 'Failed to load templates.'), { tone: 'error' });
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [applyTemplates, isZh, notify]);
|
||||
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetchPlatformSystemTemplates();
|
||||
if (!alive) return;
|
||||
setTemplates({
|
||||
soul_md: String(res?.agent_md_templates?.soul_md || ''),
|
||||
agents_md: String(res?.agent_md_templates?.agents_md || ''),
|
||||
user_md: String(res?.agent_md_templates?.user_md || ''),
|
||||
tools_md: String(res?.agent_md_templates?.tools_md || ''),
|
||||
identity_md: String(res?.agent_md_templates?.identity_md || ''),
|
||||
});
|
||||
setTopicPresetsText(JSON.stringify(res?.topic_presets || { presets: [] }, null, 2));
|
||||
const ok = await reloadTemplates();
|
||||
if (!alive || !ok) return;
|
||||
if (alive) {
|
||||
setActiveTab('agents_md');
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || (isZh ? '读取模板失败。' : 'Failed to load templates.'), { tone: 'error' });
|
||||
} finally {
|
||||
if (alive) setLoading(false);
|
||||
}
|
||||
};
|
||||
void load();
|
||||
return () => {
|
||||
alive = false;
|
||||
};
|
||||
}, [isZh, notify]);
|
||||
}, [reloadTemplates]);
|
||||
|
||||
const activeMeta = useMemo(() => templateMeta[activeTab], [activeTab]);
|
||||
|
||||
|
|
@ -144,24 +157,7 @@ function TemplateManagerView({
|
|||
className="btn btn-secondary btn-sm skill-market-button-with-icon"
|
||||
type="button"
|
||||
disabled={loading}
|
||||
onClick={() => void (async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetchPlatformSystemTemplates();
|
||||
setTemplates({
|
||||
soul_md: String(res?.agent_md_templates?.soul_md || ''),
|
||||
agents_md: String(res?.agent_md_templates?.agents_md || ''),
|
||||
user_md: String(res?.agent_md_templates?.user_md || ''),
|
||||
tools_md: String(res?.agent_md_templates?.tools_md || ''),
|
||||
identity_md: String(res?.agent_md_templates?.identity_md || ''),
|
||||
});
|
||||
setTopicPresetsText(JSON.stringify(res?.topic_presets || { presets: [] }, null, 2));
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || (isZh ? '刷新模板失败。' : 'Failed to refresh templates.'), { tone: 'error' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
})()}
|
||||
onClick={() => void reloadTemplates()}
|
||||
>
|
||||
{loading ? <RefreshCw size={14} className="animate-spin" /> : null}
|
||||
<span>{isZh ? '重载' : 'Reload'}</span>
|
||||
|
|
@ -203,10 +199,10 @@ function TemplateManagerView({
|
|||
topic_presets: topicPresets,
|
||||
});
|
||||
notify(isZh ? '模版已保存。' : 'Templates saved.', { tone: 'success' });
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
const detail = error instanceof SyntaxError
|
||||
? (isZh ? 'Topic 预设 JSON 解析失败。' : 'Invalid topic presets JSON.')
|
||||
: (error?.response?.data?.detail || (isZh ? '保存模板失败。' : 'Failed to save templates.'));
|
||||
: resolveApiErrorMessage(error, isZh ? '保存模板失败。' : 'Failed to save templates.');
|
||||
notify(detail, { tone: 'error' });
|
||||
} finally {
|
||||
setSaving(false);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useCallback, useMemo, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
import { useLucentPrompt } from '../../../components/lucent/LucentPromptProvider';
|
||||
import { useLucentPrompt } from '../../../components/lucent/useLucentPrompt';
|
||||
import { APP_ENDPOINTS } from '../../../config/env';
|
||||
import { sortBotsByCreatedAtDesc } from '../../../shared/bot/sortBots';
|
||||
import { useAppStore } from '../../../store/appStore';
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|||
import axios from 'axios';
|
||||
|
||||
import { APP_ENDPOINTS } from '../../../config/env';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import type { BotChannel, MCPConfigResponse, WorkspaceSkillOption } from '../../dashboard/types';
|
||||
import type { BotState } from '../../../types/bot';
|
||||
import type { PlatformBotResourceSnapshot, PlatformUsageResponse } from '../types';
|
||||
|
|
@ -147,8 +148,8 @@ export function usePlatformManagementState({
|
|||
try {
|
||||
const res = await axios.get<PlatformBotResourceSnapshot>(`${APP_ENDPOINTS.apiBase}/bots/${encodeURIComponent(botId)}/resources`);
|
||||
setResourceSnapshot(res.data);
|
||||
} catch (error: any) {
|
||||
const msg = error?.response?.data?.detail || (isZh ? '读取资源监控失败。' : 'Failed to load resource metrics.');
|
||||
} catch (error: unknown) {
|
||||
const msg = resolveApiErrorMessage(error, isZh ? '读取资源监控失败。' : 'Failed to load resource metrics.');
|
||||
setResourceError(String(msg));
|
||||
} finally {
|
||||
setResourceLoading(false);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||
import axios from 'axios';
|
||||
|
||||
import { APP_ENDPOINTS } from '../../../config/env';
|
||||
import { resolveApiErrorMessage } from '../../../shared/http/apiErrors';
|
||||
import {
|
||||
normalizePlatformPageSize,
|
||||
readCachedPlatformPageSize,
|
||||
|
|
@ -49,8 +50,8 @@ export function usePlatformOverviewState({
|
|||
);
|
||||
writeCachedPlatformPageSize(normalizedPageSize);
|
||||
setPlatformPageSize(normalizedPageSize);
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || (isZh ? '读取平台总览失败。' : 'Failed to load platform overview.'), { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, isZh ? '读取平台总览失败。' : 'Failed to load platform overview.'), { tone: 'error' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -66,8 +67,8 @@ export function usePlatformOverviewState({
|
|||
},
|
||||
});
|
||||
setUsageData(res.data);
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || (isZh ? '读取用量统计失败。' : 'Failed to load usage analytics.'), { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, isZh ? '读取用量统计失败。' : 'Failed to load usage analytics.'), { tone: 'error' });
|
||||
} finally {
|
||||
setUsageLoading(false);
|
||||
}
|
||||
|
|
@ -78,8 +79,8 @@ export function usePlatformOverviewState({
|
|||
try {
|
||||
const res = await axios.get<{ items: BotActivityStatsItem[] }>(`${APP_ENDPOINTS.apiBase}/platform/activity-stats`);
|
||||
setActivityStatsData(Array.isArray(res.data?.items) ? res.data.items : []);
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || (isZh ? '读取 Bot 活跃度统计失败。' : 'Failed to load bot activity analytics.'), { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, isZh ? '读取 Bot 活跃度统计失败。' : 'Failed to load bot activity analytics.'), { tone: 'error' });
|
||||
} finally {
|
||||
setActivityLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import axios from 'axios';
|
||||
|
||||
export interface ApiErrorDetail {
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
export function resolveApiErrorMessage(error: unknown, fallback: string): string {
|
||||
if (axios.isAxiosError<ApiErrorDetail>(error)) {
|
||||
const detail = String(error.response?.data?.detail || '').trim();
|
||||
if (detail) return detail;
|
||||
const message = String(error.message || '').trim();
|
||||
if (message) return message;
|
||||
} else if (error instanceof Error) {
|
||||
const message = String(error.message || '').trim();
|
||||
if (message) return message;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from './utils';
|
||||
import type { WorkspaceAttachmentPolicySnapshot, WorkspaceNotifyOptions } from './workspaceShared';
|
||||
import { useWorkspaceAttachments, type WorkspaceAttachmentLabels } from './useWorkspaceAttachments';
|
||||
import { useWorkspacePreview } from './useWorkspacePreview';
|
||||
import { useWorkspacePreview, type WorkspacePreviewLabels } from './useWorkspacePreview';
|
||||
|
||||
interface WorkspaceTreeLabels {
|
||||
workspaceLoadFail: string;
|
||||
|
|
@ -45,7 +45,7 @@ interface UseBotWorkspaceOptions {
|
|||
refreshAttachmentPolicy: () => Promise<WorkspaceAttachmentPolicySnapshot>;
|
||||
restorePendingAttachments?: (botId: string) => string[];
|
||||
notify: (message: string, options?: WorkspaceNotifyOptions) => void;
|
||||
t: WorkspaceTreeLabels & WorkspaceAttachmentLabels & Record<string, unknown>;
|
||||
t: WorkspaceTreeLabels & WorkspaceAttachmentLabels & WorkspacePreviewLabels & Record<string, unknown>;
|
||||
isZh: boolean;
|
||||
fileNotPreviewableLabel: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||
import axios from 'axios';
|
||||
|
||||
import { APP_ENDPOINTS } from '../../config/env';
|
||||
import { resolveApiErrorMessage } from '../http/apiErrors';
|
||||
import type { WorkspaceFileResponse, WorkspacePreviewMode, WorkspacePreviewState } from './types';
|
||||
import {
|
||||
buildWorkspaceDownloadHref,
|
||||
|
|
@ -18,13 +19,22 @@ import {
|
|||
} from './utils';
|
||||
import type { WorkspaceNotifyOptions } from './workspaceShared';
|
||||
|
||||
export interface WorkspacePreviewLabels {
|
||||
fileReadFail: string;
|
||||
fileEditDisabled: string;
|
||||
fileSaveFail: string;
|
||||
fileSaved: string;
|
||||
urlCopied: string;
|
||||
urlCopyFail: string;
|
||||
}
|
||||
|
||||
interface UseWorkspacePreviewOptions {
|
||||
selectedBotId: string;
|
||||
workspaceCurrentPath: string;
|
||||
workspaceDownloadExtensionSet: ReadonlySet<string>;
|
||||
loadWorkspaceTree: (botId: string, path?: string) => Promise<void>;
|
||||
notify: (message: string, options?: WorkspaceNotifyOptions) => void;
|
||||
t: any;
|
||||
t: WorkspacePreviewLabels;
|
||||
isZh: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -61,6 +71,8 @@ export function useWorkspacePreview({
|
|||
|
||||
const workspacePreviewCanEdit = Boolean(workspacePreview?.isMarkdown && !workspacePreview?.truncated);
|
||||
const workspacePreviewEditorEnabled = workspacePreviewCanEdit && workspacePreviewMode === 'edit';
|
||||
const workspacePreviewPath = workspacePreview?.path || '';
|
||||
const workspacePreviewContent = workspacePreview?.content || '';
|
||||
|
||||
const getWorkspaceDownloadHref = useCallback((filePath: string, forceDownload: boolean = true) =>
|
||||
buildWorkspaceDownloadHref(selectedBotId, filePath, forceDownload),
|
||||
|
|
@ -153,8 +165,8 @@ export function useWorkspacePreview({
|
|||
isVideo: false,
|
||||
isAudio: false,
|
||||
});
|
||||
} catch (error: any) {
|
||||
const msg = error?.response?.data?.detail || t.fileReadFail;
|
||||
} catch (error: unknown) {
|
||||
const msg = resolveApiErrorMessage(error, t.fileReadFail);
|
||||
notify(msg, { tone: 'error' });
|
||||
} finally {
|
||||
setWorkspaceFileLoading(false);
|
||||
|
|
@ -187,8 +199,8 @@ export function useWorkspacePreview({
|
|||
});
|
||||
notify(t.fileSaved, { tone: 'success' });
|
||||
void loadWorkspaceTree(selectedBotId, workspaceCurrentPath);
|
||||
} catch (error: any) {
|
||||
notify(error?.response?.data?.detail || t.fileSaveFail, { tone: 'error' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, t.fileSaveFail), { tone: 'error' });
|
||||
} finally {
|
||||
setWorkspacePreviewSaving(false);
|
||||
}
|
||||
|
|
@ -266,15 +278,15 @@ export function useWorkspacePreview({
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!workspacePreview) {
|
||||
if (!workspacePreviewPath) {
|
||||
setWorkspacePreviewMode('preview');
|
||||
setWorkspacePreviewSaving(false);
|
||||
setWorkspacePreviewDraft('');
|
||||
return;
|
||||
}
|
||||
setWorkspacePreviewSaving(false);
|
||||
setWorkspacePreviewDraft(workspacePreview.content || '');
|
||||
}, [workspacePreview?.content, workspacePreview?.path]);
|
||||
setWorkspacePreviewDraft(workspacePreviewContent);
|
||||
}, [workspacePreviewContent, workspacePreviewPath]);
|
||||
|
||||
return {
|
||||
closeWorkspacePreview,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export const WORKSPACE_LINK_PREFIX = 'https://workspace.local/open/';
|
|||
const WORKSPACE_ABS_PATH_PATTERN =
|
||||
/\/root\/\.nanobot\/workspace\/[^\n\r<>"'`]+?\.(?:md|markdown|json|txt|log|csv|tsv|yaml|yml|toml|html|htm|pdf|png|jpg|jpeg|gif|webp|svg|mp3|wav|m4a|flac|ogg|opus|aac|amr|wma|mp4|mov|avi|mkv|webm|m4v|3gp|mpeg|mpg|ts|doc|docx|xls|xlsx|xlsm|ppt|pptx|odt|ods|odp|wps)\b/gi;
|
||||
const WORKSPACE_RELATIVE_PATH_PATTERN =
|
||||
/(^|[\s(\[])(\/[^\n\r<>"'`)\]]+?\.(?:md|markdown|json|txt|log|csv|tsv|yaml|yml|toml|html|htm|pdf|png|jpg|jpeg|gif|webp|svg|mp3|wav|m4a|flac|ogg|opus|aac|amr|wma|mp4|mov|avi|mkv|webm|m4v|3gp|mpeg|mpg|ts|doc|docx|xls|xlsx|xlsm|ppt|pptx|odt|ods|odp|wps))(?![A-Za-z0-9_./-])/gim;
|
||||
/(^|[\s([])(\/[^\n\r<>"'`)\]]+?\.(?:md|markdown|json|txt|log|csv|tsv|yaml|yml|toml|html|htm|pdf|png|jpg|jpeg|gif|webp|svg|mp3|wav|m4a|flac|ogg|opus|aac|amr|wma|mp4|mov|avi|mkv|webm|m4v|3gp|mpeg|mpg|ts|doc|docx|xls|xlsx|xlsm|ppt|pptx|odt|ods|odp|wps))(?![A-Za-z0-9_./-])/gim;
|
||||
const WORKSPACE_RENDER_PATTERN =
|
||||
/\[(\/root\/\.nanobot\/workspace\/[^\]]+)\]\((https:\/\/workspace\.local\/open\/[^)\r\n]*)\)|\/root\/\.nanobot\/workspace\/[^\s<>"'`)\],,。!?;:]+|https:\/\/workspace\.local\/open\/[^)\r\n]+/gi;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue