diff --git a/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-dev.sample.yml b/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-dev.sample.yml index 4bdacd66..7885f4d6 100644 --- a/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-dev.sample.yml +++ b/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-dev.sample.yml @@ -185,6 +185,12 @@ eco-market: mcp: proxy-base-url: ${MCP_PROXY_URL:http://localhost:8020} +intelligent-data: + service-url: ${INTELLIGENT_DATA_SERVICE_URL:http://192.168.2.106:3000/test-console} + +document-editor: + service-url: ${DOCUMENT_EDITOR_SERVICE_URL:} + knife4j: # 开启增强配置 enable: true diff --git a/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-dev.yml b/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-dev.yml index b3c93f21..2a2c3c3f 100644 --- a/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-dev.yml +++ b/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-dev.yml @@ -185,6 +185,12 @@ eco-market: mcp: proxy-base-url: ${MCP_PROXY_URL:http://localhost:18020} +intelligent-data: + service-url: ${INTELLIGENT_DATA_SERVICE_URL:http://192.168.2.106:3000/test-console} + +document-editor: + service-url: ${DOCUMENT_EDITOR_SERVICE_URL:} + knife4j: # 开启增强配置 enable: true diff --git a/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-prod.sample.yml b/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-prod.sample.yml index 995fd8bf..42e71ac3 100644 --- a/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-prod.sample.yml +++ b/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-prod.sample.yml @@ -182,6 +182,12 @@ eco-market: mcp: proxy-base-url: ${MCP_PROXY_URL:http://localhost:8020} +intelligent-data: + service-url: ${INTELLIGENT_DATA_SERVICE_URL:http://192.168.2.106:3000/test-console} + +document-editor: + service-url: ${DOCUMENT_EDITOR_SERVICE_URL:} + knife4j: # 开启增强配置 enable: true diff --git a/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-test.sample.yml b/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-test.sample.yml index 995fd8bf..42e71ac3 100644 --- a/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-test.sample.yml +++ b/qiming-backend/app-platform-bootstrap/app-platform-web-bootstrap/src/main/resources/application-test.sample.yml @@ -182,6 +182,12 @@ eco-market: mcp: proxy-base-url: ${MCP_PROXY_URL:http://localhost:8020} +intelligent-data: + service-url: ${INTELLIGENT_DATA_SERVICE_URL:http://192.168.2.106:3000/test-console} + +document-editor: + service-url: ${DOCUMENT_EDITOR_SERVICE_URL:} + knife4j: # 开启增强配置 enable: true diff --git a/qiming-backend/app-platform-modules/platform-system/system-application/src/main/java/com/xspaceagi/system/application/dto/TenantConfigDto.java b/qiming-backend/app-platform-modules/platform-system/system-application/src/main/java/com/xspaceagi/system/application/dto/TenantConfigDto.java index 47d75eb8..ab3fbcbc 100644 --- a/qiming-backend/app-platform-modules/platform-system/system-application/src/main/java/com/xspaceagi/system/application/dto/TenantConfigDto.java +++ b/qiming-backend/app-platform-modules/platform-system/system-application/src/main/java/com/xspaceagi/system/application/dto/TenantConfigDto.java @@ -179,6 +179,12 @@ public class TenantConfigDto implements Serializable { @Schema(description = "模型API根地址") private String baseModelApiUrl; + @Schema(description = "智能取数服务地址") + private String intelligentDataServiceUrl; + + @Schema(description = "文档编辑服务地址") + private String documentEditorServiceUrl; + public enum AuthTypeEnum { PHONE(1, "手机"), CAS(2, "CAS"), diff --git a/qiming-backend/app-platform-modules/platform-system/system-infra/src/main/java/com/xspaceagi/system/infra/dao/service/impl/TenantConfigServiceImpl.java b/qiming-backend/app-platform-modules/platform-system/system-infra/src/main/java/com/xspaceagi/system/infra/dao/service/impl/TenantConfigServiceImpl.java index bccfcd22..8daa52a7 100644 --- a/qiming-backend/app-platform-modules/platform-system/system-infra/src/main/java/com/xspaceagi/system/infra/dao/service/impl/TenantConfigServiceImpl.java +++ b/qiming-backend/app-platform-modules/platform-system/system-infra/src/main/java/com/xspaceagi/system/infra/dao/service/impl/TenantConfigServiceImpl.java @@ -72,6 +72,30 @@ public class TenantConfigServiceImpl extends ServiceImpl getConfig() { @@ -69,6 +75,12 @@ public class TenantController { tenantConfigDto.setFaviconUrl(iFileAccessService.getFileUrlWithAk(tenantConfigDto.getFaviconUrl(), true)); tenantConfigDto.setSquareBanner(iFileAccessService.getFileUrlWithAk(tenantConfigDto.getSquareBanner(), true)); tenantConfigDto.setMaxFileSize(maxFileSize); + if (StringUtils.isBlank(tenantConfigDto.getIntelligentDataServiceUrl())) { + tenantConfigDto.setIntelligentDataServiceUrl(intelligentDataServiceUrl); + } + if (StringUtils.isBlank(tenantConfigDto.getDocumentEditorServiceUrl())) { + tenantConfigDto.setDocumentEditorServiceUrl(documentEditorServiceUrl); + } if (StringUtils.isBlank(tenantConfigDto.getTemplateConfig()) || tenantConfigDto.getTemplateConfig().equals("{}")) { tenantConfigDto.setTemplateConfig("{\"primaryColor\":\"#5147ff\",\"backgroundId\":\"bg-variant-1\",\"antdTheme\":\"light\",\"layoutStyle\":\"light\",\"navigationStyle\":\"style1\",\"timestamp\":1757425328082}"); } @@ -108,4 +120,4 @@ public class TenantController { I18nUtil.replaceSystemMessage(tenantConfigDto); return ReqResult.success(tenantConfigDto); } -} \ No newline at end of file +} diff --git a/qiming/src/pages/SystemManagement/SystemConfig/SystemConfig/BaseFormItem/index.tsx b/qiming/src/pages/SystemManagement/SystemConfig/SystemConfig/BaseFormItem/index.tsx index a21072c6..2644fa23 100644 --- a/qiming/src/pages/SystemManagement/SystemConfig/SystemConfig/BaseFormItem/index.tsx +++ b/qiming/src/pages/SystemManagement/SystemConfig/SystemConfig/BaseFormItem/index.tsx @@ -126,6 +126,24 @@ export default function BaseFormItem({ }, ], }; + const serviceUrlFieldNames = [ + 'intelligentDataServiceUrl', + 'documentEditorServiceUrl', + ]; + const fieldRules: Rule[] | undefined = serviceUrlFieldNames.includes( + props.name, + ) + ? [ + { + required: props.name === 'intelligentDataServiceUrl', + message: `请输入${props.description}`, + }, + { + type: 'url', + message: '请输入有效的 HTTP 或 HTTPS 地址', + }, + ] + : rules[currentTab]; // 处理 Select 类型的初始值:-1 表示未选择,应转换为 undefined const getInitialValue = () => { if (['Select'].includes(props.inputType) && Number(props.value) === -1) { @@ -181,7 +199,7 @@ export default function BaseFormItem({ key={props.name} name={props.name} initialValue={getInitialValue()} - rules={rules[currentTab]} + rules={fieldRules} {...(props.inputType === 'MultiInput' ? { label: , diff --git a/qiming/src/types/interfaces/systemManage.ts b/qiming/src/types/interfaces/systemManage.ts index 9a9f00b3..ed9029a6 100644 --- a/qiming/src/types/interfaces/systemManage.ts +++ b/qiming/src/types/interfaces/systemManage.ts @@ -369,6 +369,10 @@ export interface TenantConfigDto { siteName?: string; /** 站点描述 */ siteDescription?: string; + /** 智能取数服务地址 */ + intelligentDataServiceUrl?: string; + /** 文档编辑服务地址 */ + documentEditorServiceUrl?: string; /** 站点LOGO(为空使用默认值) */ siteLogo?: string; /** 登录页banner */ diff --git a/qimingclaw/crates/agent-electron-client/src/renderer/App.tsx b/qimingclaw/crates/agent-electron-client/src/renderer/App.tsx index 501f7831..83ea4b26 100644 --- a/qimingclaw/crates/agent-electron-client/src/renderer/App.tsx +++ b/qimingclaw/crates/agent-electron-client/src/renderer/App.tsx @@ -50,6 +50,8 @@ import SkillsMarketplacePage from "./components/pages/SkillsMarketplacePage"; import AgentMarketplacePage from "./components/pages/AgentMarketplacePage"; import McpMarketplacePage from "./components/pages/McpMarketplacePage"; import WorkflowMarketplacePage from "./components/pages/WorkflowMarketplacePage"; +import DataFetchPage from "./components/pages/DataFetchPage"; +import DocumentEditorPage from "./components/pages/DocumentEditorPage"; import MCPSettings from "./components/settings/MCPSettings"; import AppSidebar, { NavKey, @@ -106,6 +108,8 @@ type TabKey = | "legacySessions" | "automation" | "knowledge" + | "document" + | "dataFetch" | "skills" | "agents" | "mcpLibrary" @@ -1080,6 +1084,16 @@ function App() { return; } + if (key === "document") { + setActiveTab("document"); + return; + } + + if (key === "dataFetch") { + setActiveTab("dataFetch"); + return; + } + if (key === "toolIntegration" || key === "systemSettings") { setActiveTab("settings"); return; @@ -1337,7 +1351,9 @@ function App() { {/* 主内容区:flex 子撑满,便于日志等页占满高度 */}
} {activeTab === "knowledge" && } + {activeTab === "document" && } + {activeTab === "dataFetch" && } {activeTab === "skills" && } {activeTab === "agents" && ( diff --git a/qimingclaw/crates/agent-electron-client/src/renderer/components/pages/ConfiguredServicePage.tsx b/qimingclaw/crates/agent-electron-client/src/renderer/components/pages/ConfiguredServicePage.tsx new file mode 100644 index 00000000..44d21653 --- /dev/null +++ b/qimingclaw/crates/agent-electron-client/src/renderer/components/pages/ConfiguredServicePage.tsx @@ -0,0 +1,144 @@ +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { Alert, Button, Spin } from "antd"; +import { ReloadOutlined } from "@ant-design/icons"; +import styles from "../../styles/components/ConfiguredServicePage.module.css"; + +interface ConfiguredServicePageProps { + title: string; + loadServiceUrl: () => Promise; +} + +function ConfiguredServicePage({ + title, + loadServiceUrl, +}: ConfiguredServicePageProps) { + const webviewRef = useRef(null); + const [serviceUrl, setServiceUrl] = useState(null); + const [loadingConfig, setLoadingConfig] = useState(true); + const [loadingPage, setLoadingPage] = useState(false); + const [error, setError] = useState(null); + + const loadService = useCallback(async () => { + setLoadingConfig(true); + setError(null); + try { + setServiceUrl(await loadServiceUrl()); + } catch (cause) { + setServiceUrl(null); + setError( + cause instanceof Error ? cause.message : `读取${title}服务配置失败`, + ); + } finally { + setLoadingConfig(false); + } + }, [loadServiceUrl, title]); + + useEffect(() => { + void loadService(); + }, [loadService]); + + useEffect(() => { + const element = webviewRef.current as any; + if (!element || !serviceUrl) return; + + const handleStart = () => { + setLoadingPage(true); + setError(null); + }; + const handleStop = () => setLoadingPage(false); + const handleFailure = (event: any) => { + if (event.errorCode !== -3) { + setLoadingPage(false); + setError( + `${title}页面加载失败:${event.errorDescription || "网络连接失败"}`, + ); + } + }; + + element.addEventListener("did-start-loading", handleStart); + element.addEventListener("did-stop-loading", handleStop); + element.addEventListener("did-fail-load", handleFailure); + return () => { + element.removeEventListener("did-start-loading", handleStart); + element.removeEventListener("did-stop-loading", handleStop); + element.removeEventListener("did-fail-load", handleFailure); + }; + }, [serviceUrl, title]); + + const handleReload = useCallback(() => { + const element = webviewRef.current as any; + if (element?.reload) { + element.reload(); + return; + } + void loadService(); + }, [loadService]); + + if (loadingConfig) { + return ( +
+ +
+ ); + } + + if (!serviceUrl) { + return ( +
+ } + onClick={loadService} + > + 重试 + + } + /> +
+ ); + } + + return ( +
+
+ {title} + + {serviceUrl} + + +
+ {error && ( + setError(null)} + /> + )} + +
+ ); +} + +export default ConfiguredServicePage; diff --git a/qimingclaw/crates/agent-electron-client/src/renderer/components/pages/DataFetchPage.tsx b/qimingclaw/crates/agent-electron-client/src/renderer/components/pages/DataFetchPage.tsx new file mode 100644 index 00000000..7f118efe --- /dev/null +++ b/qimingclaw/crates/agent-electron-client/src/renderer/components/pages/DataFetchPage.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { fetchIntelligentDataServiceUrl } from "../../services/core/servicePages"; +import ConfiguredServicePage from "./ConfiguredServicePage"; + +function DataFetchPage() { + return ( + + ); +} + +export default DataFetchPage; diff --git a/qimingclaw/crates/agent-electron-client/src/renderer/components/pages/DocumentEditorPage.tsx b/qimingclaw/crates/agent-electron-client/src/renderer/components/pages/DocumentEditorPage.tsx new file mode 100644 index 00000000..64b563ce --- /dev/null +++ b/qimingclaw/crates/agent-electron-client/src/renderer/components/pages/DocumentEditorPage.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { fetchDocumentEditorServiceUrl } from "../../services/core/servicePages"; +import ConfiguredServicePage from "./ConfiguredServicePage"; + +function DocumentEditorPage() { + return ( + + ); +} + +export default DocumentEditorPage; diff --git a/qimingclaw/crates/agent-electron-client/src/renderer/services/core/automation.ts b/qimingclaw/crates/agent-electron-client/src/renderer/services/core/automation.ts index 95655271..38456d5a 100644 --- a/qimingclaw/crates/agent-electron-client/src/renderer/services/core/automation.ts +++ b/qimingclaw/crates/agent-electron-client/src/renderer/services/core/automation.ts @@ -2,7 +2,7 @@ import { message } from "antd"; import { AUTH_KEYS, DEFAULT_API_TIMEOUT, - DEFAULT_SERVER_HOST, + STORAGE_KEYS, } from "@shared/constants"; import { getDomainTokenKey } from "@shared/utils/domain"; import { normalizeServerHost } from "./auth"; @@ -179,11 +179,15 @@ export interface AutomationTemplate { } async function getBaseUrl(): Promise { - const config = (await window.electronAPI?.settings.get( - AUTH_KEYS.STEP1_CONFIG, - )) as { serverHost?: string } | null; - - return normalizeServerHost(config?.serverHost || DEFAULT_SERVER_HOST); + const [userInfo, config] = (await Promise.all([ + window.electronAPI?.settings.get(AUTH_KEYS.USER_INFO), + window.electronAPI?.settings.get(STORAGE_KEYS.STEP1_CONFIG), + ])) as [{ currentDomain?: string } | null, { serverHost?: string } | null]; + const loginServerHost = userInfo?.currentDomain || config?.serverHost || ""; + if (!loginServerHost) { + throw new Error("未找到登录站点地址,请重新登录"); + } + return normalizeServerHost(loginServerHost); } async function getAuthToken(domain: string): Promise { diff --git a/qimingclaw/crates/agent-electron-client/src/renderer/services/core/servicePages.ts b/qimingclaw/crates/agent-electron-client/src/renderer/services/core/servicePages.ts new file mode 100644 index 00000000..0b73230c --- /dev/null +++ b/qimingclaw/crates/agent-electron-client/src/renderer/services/core/servicePages.ts @@ -0,0 +1,94 @@ +import { + AUTH_KEYS, + DEFAULT_API_TIMEOUT, + STORAGE_KEYS, +} from "@shared/constants"; +import { getDomainTokenKey } from "@shared/utils/domain"; +import { normalizeServerHost } from "./auth"; + +const SUCCESS_CODE = "0000"; +const INTELLIGENT_DATA_FALLBACK_URL = "http://192.168.2.106:3000/test-console"; + +interface TenantConfigResponse { + code: string; + message?: string; + data?: { + intelligentDataServiceUrl?: string; + documentEditorServiceUrl?: string; + }; +} + +async function getLoginServerHost(): Promise { + const [userInfo, config] = (await Promise.all([ + window.electronAPI?.settings.get(AUTH_KEYS.USER_INFO), + window.electronAPI?.settings.get(STORAGE_KEYS.STEP1_CONFIG), + ])) as [{ currentDomain?: string } | null, { serverHost?: string } | null]; + const loginServerHost = userInfo?.currentDomain || config?.serverHost || ""; + if (!loginServerHost) { + throw new Error("未找到登录站点地址,请重新登录"); + } + return normalizeServerHost(loginServerHost); +} + +async function getToken(serverHost: string): Promise { + const oneShotToken = (await window.electronAPI?.settings.get( + AUTH_KEYS.AUTH_TOKEN, + )) as string | null; + if (oneShotToken) return oneShotToken; + return (await window.electronAPI?.settings.get( + getDomainTokenKey(serverHost), + )) as string | null; +} + +function normalizeServiceUrl(value: string, serviceName: string): string { + const candidate = value.trim(); + if (!candidate) { + throw new Error(`${serviceName}服务地址未配置,请在系统设置中配置`); + } + const url = new URL(candidate); + if (!/^https?:$/.test(url.protocol)) { + throw new Error(`${serviceName}服务地址必须使用 HTTP 或 HTTPS`); + } + return url.toString(); +} + +async function fetchTenantConfig(): Promise { + const serverHost = await getLoginServerHost(); + const token = await getToken(serverHost); + const response = await fetch(`${serverHost}/api/tenant/config`, { + method: "GET", + credentials: "include", + cache: "no-store", + headers: { + Accept: "application/json", + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + signal: AbortSignal.timeout(DEFAULT_API_TIMEOUT), + }); + + if (!response.ok) { + throw new Error(`读取服务配置失败(HTTP ${response.status})`); + } + + const result = (await response.json()) as TenantConfigResponse; + if (result.code !== SUCCESS_CODE) { + throw new Error(result.message || "读取服务配置失败"); + } + return result.data; +} + +export async function fetchIntelligentDataServiceUrl(): Promise { + const config = await fetchTenantConfig(); + return normalizeServiceUrl( + config?.intelligentDataServiceUrl || INTELLIGENT_DATA_FALLBACK_URL, + "智能取数", + ); +} + +export async function fetchDocumentEditorServiceUrl(): Promise { + const config = await fetchTenantConfig(); + return normalizeServiceUrl( + config?.documentEditorServiceUrl || "", + "文档编辑", + ); +} diff --git a/qimingclaw/crates/agent-electron-client/src/renderer/styles/components/ConfiguredServicePage.module.css b/qimingclaw/crates/agent-electron-client/src/renderer/styles/components/ConfiguredServicePage.module.css new file mode 100644 index 00000000..734eef3c --- /dev/null +++ b/qimingclaw/crates/agent-electron-client/src/renderer/styles/components/ConfiguredServicePage.module.css @@ -0,0 +1,58 @@ +.container { + display: flex; + flex: 1; + flex-direction: column; + min-height: 0; + overflow: hidden; + background: var(--color-bg-container); +} + +.toolbar { + display: flex; + align-items: center; + gap: 10px; + min-height: 44px; + padding: 7px 16px; + border-bottom: 1px solid var(--color-border); + background: var(--color-bg-section-header); + flex-shrink: 0; +} + +.title { + color: var(--color-text); + font-size: 14px; + font-weight: 600; + flex-shrink: 0; +} + +.url { + min-width: 0; + flex: 1; + overflow: hidden; + color: var(--color-text-tertiary); + font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.alert { + margin: 8px 12px 0; + flex-shrink: 0; +} + +.webview { + width: 100%; + min-height: 0; + flex: 1; + border: 0; + background: #fff; +} + +.state { + display: flex; + align-items: center; + justify-content: center; + flex: 1; + min-height: 240px; + padding: 24px; +}