Merge remote-tracking branch 'origin/master' into feat/workflow

# Conflicts:
#	pyproject.toml
#	src/langbot/pkg/api/http/controller/main.py
#	src/langbot/pkg/core/app.py
#	src/langbot/pkg/core/stages/build_app.py
#	src/langbot/pkg/provider/modelmgr/requester.py
#	uv.lock
#	web/package-lock.json
#	web/package.json
#	web/pnpm-lock.yaml
#	web/src/app/home/components/dynamic-form/DynamicFormComponent.tsx
#	web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx
#	web/src/app/home/components/home-sidebar/HomeSidebar.tsx
#	web/src/app/infra/entities/api/index.ts
#	web/src/app/infra/http/BackendClient.ts
#	web/src/i18n/locales/en-US.ts
#	web/src/i18n/locales/es-ES.ts
#	web/src/i18n/locales/ja-JP.ts
#	web/src/i18n/locales/ru-RU.ts
#	web/src/i18n/locales/th-TH.ts
#	web/src/i18n/locales/vi-VN.ts
#	web/src/i18n/locales/zh-Hans.ts
#	web/src/i18n/locales/zh-Hant.ts
#	web/src/router.tsx
This commit is contained in:
RockChinQ
2026-07-01 20:33:05 +08:00
773 changed files with 84040 additions and 16377 deletions
@@ -26,11 +26,10 @@ export interface SidebarEntityItem {
installInfo?: Record<string, unknown>;
hasUpdate?: boolean;
debug?: boolean;
// Set when this item appears in the unified extensions list
extensionType?: 'plugin' | 'mcp' | 'skill';
}
// Install action types that can be triggered from sidebar
export type PluginInstallAction = 'local' | 'github' | null;
// Plugin page registered by a plugin
export interface PluginPageItem {
id: string; // "author/name/pageId"
@@ -51,6 +50,7 @@ export interface SidebarDataContextValue {
knowledgeBases: SidebarEntityItem[];
plugins: SidebarEntityItem[];
mcpServers: SidebarEntityItem[];
skills: SidebarEntityItem[];
pluginPages: PluginPageItem[];
refreshBots: () => Promise<void>;
refreshPipelines: () => Promise<void>;
@@ -58,13 +58,14 @@ export interface SidebarDataContextValue {
refreshKnowledgeBases: () => Promise<void>;
refreshPlugins: () => Promise<void>;
refreshMCPServers: () => Promise<void>;
refreshSkills: () => Promise<void>;
refreshAll: () => Promise<void>;
// Breadcrumb: entity name shown when viewing a detail page
detailEntityName: string | null;
setDetailEntityName: (name: string | null) => void;
// Pending plugin install action triggered from sidebar
pendingPluginInstallAction: PluginInstallAction;
setPendingPluginInstallAction: (action: PluginInstallAction) => void;
// Whether the extensions list is grouped by type (shared between page and sidebar)
extensionsGroupByType: boolean;
setExtensionsGroupByType: (enabled: boolean) => void;
}
const SidebarDataContext = createContext<SidebarDataContextValue | null>(null);
@@ -80,10 +81,22 @@ export function SidebarDataProvider({
const [knowledgeBases, setKnowledgeBases] = useState<SidebarEntityItem[]>([]);
const [plugins, setPlugins] = useState<SidebarEntityItem[]>([]);
const [mcpServers, setMCPServers] = useState<SidebarEntityItem[]>([]);
const [skills, setSkills] = useState<SidebarEntityItem[]>([]);
const [pluginPages, setPluginPages] = useState<PluginPageItem[]>([]);
const [detailEntityName, setDetailEntityName] = useState<string | null>(null);
const [pendingPluginInstallAction, setPendingPluginInstallAction] =
useState<PluginInstallAction>(null);
const [extensionsGroupByType, setExtensionsGroupByTypeState] =
useState<boolean>(() => {
if (typeof window === 'undefined') return false;
return localStorage.getItem('extensions_group_by_type') === 'true';
});
const setExtensionsGroupByType = useCallback((enabled: boolean) => {
setExtensionsGroupByTypeState(enabled);
try {
localStorage.setItem('extensions_group_by_type', String(enabled));
} catch {
// ignore
}
}, []);
const refreshBots = useCallback(async () => {
try {
@@ -245,8 +258,8 @@ export function SidebarDataProvider({
const resp = await httpClient.getMCPServers();
setMCPServers(
resp.servers.map((server) => ({
id: server.name,
name: server.name,
id: server.name, // Keep __ for API calls
name: server.name.replace(/__/g, '/'), // Display with / for readability
enabled: server.enable,
runtimeStatus: server.runtime_info?.status,
})),
@@ -256,6 +269,22 @@ export function SidebarDataProvider({
}
}, []);
const refreshSkills = useCallback(async () => {
try {
const resp = await httpClient.getSkills();
setSkills(
resp.skills.map((skill) => ({
id: skill.name,
name: skill.display_name || skill.name,
description: skill.description,
updatedAt: skill.updated_at,
})),
);
} catch (error) {
console.error('Failed to fetch skills for sidebar:', error);
}
}, []);
const refreshAll = useCallback(async () => {
await Promise.all([
refreshBots(),
@@ -264,6 +293,7 @@ export function SidebarDataProvider({
refreshKnowledgeBases(),
refreshPlugins(),
refreshMCPServers(),
refreshSkills(),
]);
}, [
refreshBots,
@@ -272,6 +302,7 @@ export function SidebarDataProvider({
refreshKnowledgeBases,
refreshPlugins,
refreshMCPServers,
refreshSkills,
]);
// Fetch all entity lists on mount
@@ -288,6 +319,7 @@ export function SidebarDataProvider({
knowledgeBases,
plugins,
mcpServers,
skills,
pluginPages,
refreshBots,
refreshPipelines,
@@ -295,11 +327,12 @@ export function SidebarDataProvider({
refreshKnowledgeBases,
refreshPlugins,
refreshMCPServers,
refreshSkills,
refreshAll,
detailEntityName,
setDetailEntityName,
pendingPluginInstallAction,
setPendingPluginInstallAction,
extensionsGroupByType,
setExtensionsGroupByType,
}}
>
{children}