改为运行时控制画板 Agent 入口
新增前端运行时配置接口下发画板 Agent 开关 将画板 Agent 入口改为读取后端运行时配置 把开关变量改为 GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR 补充前后端测试和生产环境变量文档
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 微信小程序 web-view 登录配置。
|
||||
# 留空时不覆盖已有微信网页 OAuth 配置;正式联调时再填小程序 AppID / AppSecret。
|
||||
VITE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR=false
|
||||
GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR=false
|
||||
|
||||
WECHAT_MINI_PROGRAM_APP_ID=""
|
||||
WECHAT_MINI_PROGRAM_APP_SECRET=""
|
||||
|
||||
+3
-3
@@ -199,9 +199,9 @@ VITE_LLM_DEBUG_LOG="false"
|
||||
# Set to "true" to expose local diagnostic panels, or "false" to hide them.
|
||||
VITE_DEBUG_MODE=""
|
||||
|
||||
# Optional: show the image editor right-side Agent entry.
|
||||
# Keep this off by default outside local development.
|
||||
VITE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR="false"
|
||||
# Optional: show the image editor right-side Agent entry at runtime.
|
||||
# This is read by api-server and exposed through /api/runtime/frontend-config.
|
||||
GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR="false"
|
||||
|
||||
# Optional: official VikingDB credentials for regenerating build-tag similarities
|
||||
# with the Python embedding script. The script auto-loads `.env.local` and uses
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ LLM_DEBUG_LOG="true"
|
||||
# 注意:不要在客户端启用调试日志,避免敏感数据泄露
|
||||
# VITE_LLM_DEBUG_LOG="false"
|
||||
|
||||
VITE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR=true
|
||||
GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR=true
|
||||
|
||||
ALIYUN_OSS_BUCKET="xushi-dev"
|
||||
ALIYUN_OSS_REGION="oss-cn-beijing"
|
||||
|
||||
Vendored
+1
@@ -49,6 +49,7 @@ AUTH_REFRESH_COOKIE_PATH=/api/auth
|
||||
AUTH_REFRESH_COOKIE_SAME_SITE=Lax
|
||||
AUTH_REFRESH_COOKIE_SECURE=true
|
||||
GENARRATIVE_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED=false
|
||||
GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR=false
|
||||
|
||||
GENARRATIVE_SPACETIME_SERVER_URL=http://127.0.0.1:3101
|
||||
GENARRATIVE_SPACETIME_DATABASE=genarrative-prod
|
||||
|
||||
@@ -60,7 +60,7 @@ npm run check:server-rs-ddd
|
||||
- 平台基础能力:`/api/llm/*`、`/api/speech/volcengine/*`,只保留通用 LLM 和语音代理。
|
||||
- 资产基础能力:`/api/assets/direct-upload-tickets`、`/api/assets/sts-upload-credentials`、`/api/assets/objects/*`、`/api/assets/read-*`,负责直传、确认、绑定和读取。
|
||||
- 外部 OpenAPI:`/api/external/v1/openapi.json`、`/api/external/v1/assets/direct-upload-tickets`、`/api/external/v1/assets/objects/confirm`、`/api/external/v1/assets/read-url`、`/api/external/v1/editor/*`,使用 Bearer API Key 鉴权;API Key 管理仍在登录态 `/api/profile/api-keys`,不进入外部 OpenAPI JSON。
|
||||
- 创作 / 游玩支撑能力:`/api/creation-entry/config`、`/api/ai/tasks*`、`/api/runtime/chat/*`、`/api/runtime/settings`、`/api/runtime/save/snapshot`、`/api/profile/browse-history`、`/api/profile/save-archives*`、`/api/profile/play-stats`、`/api/assets/history`、`/api/assets/character-visual/*`、`/api/assets/character-animation/*`、`/api/assets/character-workflow-cache*`、`/api/assets/hyper3d/*`、`/api/runtime/custom-world/asset-studio/*`、`/api/editor/projects*`、`/api/editor/projects/{projectId}/agent-conversations`、`/api/editor/agent-conversations/{conversationId}*`。`/api/runtime/custom-world/asset-studio/*` 解析默认角色形象 / 动作提示词时可以在 OSS 缓存不可用或未配置时按无缓存返回默认提示;保存 workflow 缓存和真实素材读写仍必须要求 OSS 正常可用。
|
||||
- 创作 / 游玩支撑能力:`/api/creation-entry/config`、`/api/ai/tasks*`、`/api/runtime/frontend-config`、`/api/runtime/chat/*`、`/api/runtime/settings`、`/api/runtime/save/snapshot`、`/api/profile/browse-history`、`/api/profile/save-archives*`、`/api/profile/play-stats`、`/api/assets/history`、`/api/assets/character-visual/*`、`/api/assets/character-animation/*`、`/api/assets/character-workflow-cache*`、`/api/assets/hyper3d/*`、`/api/runtime/custom-world/asset-studio/*`、`/api/editor/projects*`、`/api/editor/projects/{projectId}/agent-conversations`、`/api/editor/agent-conversations/{conversationId}*`。`/api/runtime/frontend-config` 由 `api-server` 从运行时环境变量下发非敏感 UI 开关;画板右侧 Agent 入口由 `GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR` 控制,默认关闭,前端不再读取 `VITE_*` 构建期变量决定生产显示。`/api/runtime/custom-world/asset-studio/*` 解析默认角色形象 / 动作提示词时可以在 OSS 缓存不可用或未配置时按无缓存返回默认提示;保存 workflow 缓存和真实素材读写仍必须要求 OSS 正常可用。
|
||||
- 后台入口配置:`/admin/api/creation-entry/config`、`/admin/api/creation-entry/config/banners` 和 `/admin/api/creation-entry/config/interactions`。
|
||||
- 后台素材查询:`GET /admin/api/editor-assets` 通过 `admin_list_editor_assets_and_return` 后台只读 procedure 读取私有账号级 `editor_asset` 中 `source_type = 'generated'` 的素材,支持 `ownerUserId`、`keyword`、`createdAfter`、`createdBefore`、`cursor` 和 `limit`;返回缩略图 / Object Key、作者展示名、陶泥号、提示词、生成输入和生成成本,只用于查询,不提供分类筛选,也不执行精选审核、返还或展示状态修改,不通过后台 SQL 直查私有表。
|
||||
- 自定义世界 / RPG:`/api/runtime/custom-world*`、`/api/story/*`、`/api/runtime/chat/*`。
|
||||
|
||||
@@ -234,6 +234,10 @@ npm run database:backup:oss -- --data-dir /stdb --stop-service spacetimedb.servi
|
||||
|
||||
生产环境变量模板在 `deploy/env/api-server.env.example`:
|
||||
|
||||
主站前端运行时配置由 `api-server` 下发;画板右侧 Agent 入口使用
|
||||
`GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR=false` 默认关闭,需要开启时只改生产
|
||||
api-server 环境变量并重启 `api-server`,不再通过 `VITE_*` 构建期变量控制。
|
||||
|
||||
```env
|
||||
GENARRATIVE_DATABASE_BACKUP_DATA_DIR=/stdb
|
||||
GENARRATIVE_DATABASE_BACKUP_WORK_DIR=/var/lib/genarrative/database-backups
|
||||
|
||||
@@ -43,6 +43,7 @@ pub fn build_router(state: AppState) -> Router {
|
||||
.merge(modules::auth::router(state.clone()))
|
||||
.merge(modules::profile::router(state.clone()))
|
||||
.merge(modules::external_api::router(state.clone()))
|
||||
.merge(modules::frontend_runtime_config::router(state.clone()))
|
||||
.merge(modules::assets::router(state.clone()))
|
||||
.merge(modules::editor_project::router(state.clone()))
|
||||
.merge(modules::platform::router(state.clone()))
|
||||
@@ -518,6 +519,51 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn frontend_runtime_config_defaults_agent_sidebar_to_disabled() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/runtime/frontend-config")
|
||||
.body(Body::empty())
|
||||
.expect("frontend config request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("frontend config request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let payload = read_json_response(response).await;
|
||||
assert_eq!(
|
||||
payload["imageEditorAgentSidebarEnabled"],
|
||||
Value::Bool(false)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn frontend_runtime_config_returns_agent_sidebar_env_flag() {
|
||||
let config = AppConfig {
|
||||
image_editor_agent_sidebar_enabled: true,
|
||||
..AppConfig::default()
|
||||
};
|
||||
let app = build_router(AppState::new(config).expect("state should build"));
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/runtime/frontend-config")
|
||||
.body(Body::empty())
|
||||
.expect("frontend config request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("frontend config request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let payload = read_json_response(response).await;
|
||||
assert_eq!(payload["imageEditorAgentSidebarEnabled"], Value::Bool(true));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn spacetime_unavailable_router_returns_service_unavailable_for_requests() {
|
||||
let app =
|
||||
|
||||
@@ -56,6 +56,7 @@ pub struct AppConfig {
|
||||
pub editor_background_removal_base_url: String,
|
||||
pub editor_background_removal_token: Option<String>,
|
||||
pub editor_background_removal_request_timeout_ms: u64,
|
||||
pub image_editor_agent_sidebar_enabled: bool,
|
||||
pub log_filter: String,
|
||||
pub otel_enabled: bool,
|
||||
pub admin_username: Option<String>,
|
||||
@@ -283,6 +284,7 @@ impl Default for AppConfig {
|
||||
editor_background_removal_token: None,
|
||||
editor_background_removal_request_timeout_ms:
|
||||
DEFAULT_EDITOR_BACKGROUND_REMOVAL_REQUEST_TIMEOUT_MS,
|
||||
image_editor_agent_sidebar_enabled: false,
|
||||
log_filter: "info,tower_http=info".to_string(),
|
||||
otel_enabled: false,
|
||||
admin_username: None,
|
||||
@@ -473,6 +475,11 @@ impl AppConfig {
|
||||
]) {
|
||||
config.editor_background_removal_request_timeout_ms = timeout_ms;
|
||||
}
|
||||
if let Some(enabled) =
|
||||
read_first_bool_env(&["GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR"])
|
||||
{
|
||||
config.image_editor_agent_sidebar_enabled = enabled;
|
||||
}
|
||||
if let Some(listen_backlog) =
|
||||
read_first_positive_i32_env(&["GENARRATIVE_API_LISTEN_BACKLOG"])
|
||||
{
|
||||
@@ -2176,6 +2183,26 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_env_reads_image_editor_agent_sidebar_runtime_flag() {
|
||||
let _guard = ENV_LOCK
|
||||
.get_or_init(|| Mutex::new(()))
|
||||
.lock()
|
||||
.expect("env lock should not poison");
|
||||
|
||||
unsafe {
|
||||
std::env::remove_var("GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR");
|
||||
std::env::set_var("GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR", "true");
|
||||
}
|
||||
|
||||
let config = AppConfig::from_env();
|
||||
assert!(config.image_editor_agent_sidebar_enabled);
|
||||
|
||||
unsafe {
|
||||
std::env::remove_var("GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_env_reads_prefixed_character_animation_ffmpeg_paths() {
|
||||
let _guard = ENV_LOCK
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Extension, State},
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{api_response::json_success_body, request_context::RequestContext, state::AppState};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FrontendRuntimeConfigResponse {
|
||||
pub image_editor_agent_sidebar_enabled: bool,
|
||||
}
|
||||
|
||||
pub async fn get_frontend_runtime_config(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
) -> Json<serde_json::Value> {
|
||||
json_success_body(
|
||||
Some(&request_context),
|
||||
FrontendRuntimeConfigResponse {
|
||||
image_editor_agent_sidebar_enabled: state.config.image_editor_agent_sidebar_enabled,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -52,6 +52,7 @@ mod external_editor_api;
|
||||
mod external_generation;
|
||||
mod external_generation_worker;
|
||||
mod external_generation_worker_controller;
|
||||
mod frontend_runtime_config;
|
||||
pub(crate) mod generated_asset_sheets;
|
||||
mod generated_image_assets;
|
||||
mod health;
|
||||
|
||||
@@ -8,6 +8,7 @@ pub mod editor_project;
|
||||
pub mod edutainment;
|
||||
pub mod external_api;
|
||||
pub mod external_generation;
|
||||
pub mod frontend_runtime_config;
|
||||
pub mod health;
|
||||
pub mod internal;
|
||||
pub mod jump_hop;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
use axum::{Router, routing::get};
|
||||
|
||||
use crate::{frontend_runtime_config::get_frontend_runtime_config, state::AppState};
|
||||
|
||||
pub fn router(_state: AppState) -> Router<AppState> {
|
||||
Router::new().route(
|
||||
"/api/runtime/frontend-config",
|
||||
get(get_frontend_runtime_config),
|
||||
)
|
||||
}
|
||||
@@ -41,7 +41,6 @@ type EditorAgentConversationSummary = Awaited<
|
||||
type EditorAgentConversationDetail = Awaited<
|
||||
ReturnType<EditorAgentGetConversation>
|
||||
>;
|
||||
const EDITOR_AGENT_SIDEBAR_ENV = 'VITE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR';
|
||||
|
||||
const listEditorAgentConversationsMock = vi.hoisted(() =>
|
||||
vi.fn<
|
||||
@@ -110,6 +109,7 @@ const loadOrCreateRecentEditorProjectMock = vi.hoisted(() => vi.fn());
|
||||
const renameEditorProjectMock = vi.hoisted(() => vi.fn());
|
||||
const saveEditorProjectLayoutMock = vi.hoisted(() => vi.fn());
|
||||
const getPlatformProfileDashboardMock = vi.hoisted(() => vi.fn());
|
||||
const loadFrontendRuntimeConfigMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock('../../services/image-editor/editorProjectClient', async () => {
|
||||
const actual = await vi.importActual<
|
||||
@@ -140,6 +140,10 @@ vi.mock('../../services/platform-entry/platformProfileClient', () => ({
|
||||
getPlatformProfileDashboard: getPlatformProfileDashboardMock,
|
||||
}));
|
||||
|
||||
vi.mock('../../services/frontendRuntimeConfigService', () => ({
|
||||
loadFrontendRuntimeConfig: loadFrontendRuntimeConfigMock,
|
||||
}));
|
||||
|
||||
vi.mock('../../services/image-editor/editorAgentClient', () => ({
|
||||
createEditorAgentConversation: createEditorAgentConversationMock,
|
||||
deleteEditorAgentConversation: deleteEditorAgentConversationMock,
|
||||
@@ -214,7 +218,7 @@ function openMinimap() {
|
||||
}
|
||||
|
||||
async function ensureAgentConversationOpen() {
|
||||
const agentButton = screen.getByRole('button', { name: '画布 Agent' });
|
||||
const agentButton = await screen.findByRole('button', { name: '画布 Agent' });
|
||||
if (agentButton.getAttribute('aria-pressed') !== 'true') {
|
||||
fireEvent.click(agentButton);
|
||||
}
|
||||
@@ -222,7 +226,9 @@ async function ensureAgentConversationOpen() {
|
||||
}
|
||||
|
||||
function enableEditorAgentSidebarForTest() {
|
||||
vi.stubEnv(EDITOR_AGENT_SIDEBAR_ENV, 'true');
|
||||
loadFrontendRuntimeConfigMock.mockResolvedValue({
|
||||
imageEditorAgentSidebarEnabled: true,
|
||||
});
|
||||
}
|
||||
|
||||
describe('ImageCanvasEditorView', () => {
|
||||
@@ -246,7 +252,9 @@ describe('ImageCanvasEditorView', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.stubEnv(EDITOR_AGENT_SIDEBAR_ENV, 'false');
|
||||
loadFrontendRuntimeConfigMock.mockResolvedValue({
|
||||
imageEditorAgentSidebarEnabled: false,
|
||||
});
|
||||
listEditorAgentConversationsMock.mockResolvedValue([]);
|
||||
createEditorAgentConversationMock.mockResolvedValue({
|
||||
...createEditorAgentConversationSummary({
|
||||
@@ -277,7 +285,7 @@ describe('ImageCanvasEditorView', () => {
|
||||
deleteEditorAgentConversationMock.mockReset();
|
||||
streamEditorAgentMessageMock.mockReset();
|
||||
getPlatformProfileDashboardMock.mockReset();
|
||||
vi.unstubAllEnvs();
|
||||
loadFrontendRuntimeConfigMock.mockReset();
|
||||
});
|
||||
|
||||
it('loads the project from projectid query before falling back to recent project', async () => {
|
||||
@@ -1487,6 +1495,8 @@ describe('ImageCanvasEditorView', () => {
|
||||
enableEditorAgentSidebarForTest();
|
||||
render(<ImageCanvasEditorView />);
|
||||
|
||||
await screen.findByRole('button', { name: '画布 Agent' });
|
||||
|
||||
const assetSidebar = openAssetSidebar();
|
||||
expect(within(assetSidebar).getByText('素材')).toBeTruthy();
|
||||
|
||||
@@ -1507,7 +1517,7 @@ describe('ImageCanvasEditorView', () => {
|
||||
screen.getByRole('complementary', { name: '画布任务列表' }),
|
||||
).toBeTruthy();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '画布 Agent' }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: '画布 Agent' }));
|
||||
expect(
|
||||
screen.getByRole('complementary', { name: '图片资源栏' }),
|
||||
).toBeTruthy();
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
loadEditorProject,
|
||||
loadEditorGenerationPricing,
|
||||
} from '../../services/image-editor/editorProjectClient';
|
||||
import { loadFrontendRuntimeConfig } from '../../services/frontendRuntimeConfigService';
|
||||
import { shouldShowRechargeEntry } from '../../services/payment/paymentPlatform';
|
||||
import { getPlatformProfileDashboard } from '../../services/platform-entry/platformProfileClient';
|
||||
import { useAuthUi } from '../auth/AuthUiContext';
|
||||
@@ -117,12 +118,6 @@ type ImageCanvasEditorViewProps = {
|
||||
onProjectAccessLost?: () => void;
|
||||
};
|
||||
|
||||
function resolveEditorAgentSidebarEnabled(
|
||||
rawValue = import.meta.env.VITE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR,
|
||||
): boolean {
|
||||
return rawValue === 'true' || rawValue === '1';
|
||||
}
|
||||
|
||||
function isCanvasStartupTool(value: string | null): value is CanvasStartupTool {
|
||||
return CANVAS_STARTUP_TOOLS.includes(value as CanvasStartupTool);
|
||||
}
|
||||
@@ -327,6 +322,9 @@ export function ImageCanvasEditorView({
|
||||
const showRechargeEntry = shouldShowRechargeEntry();
|
||||
const currentEditorUserId = authUi?.user?.id ?? null;
|
||||
const [isToolbarGuideVisible, setIsToolbarGuideVisible] = useState(false);
|
||||
const [isAgentConversationEnabled, setIsAgentConversationEnabled] =
|
||||
useState(false);
|
||||
const [isAgentConversationOpen, setIsAgentConversationOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
@@ -345,6 +343,28 @@ export function ImageCanvasEditorView({
|
||||
isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
void loadFrontendRuntimeConfig()
|
||||
.then((config) => {
|
||||
if (!isMounted) {
|
||||
return;
|
||||
}
|
||||
const enabled = config.imageEditorAgentSidebarEnabled === true;
|
||||
setIsAgentConversationEnabled(enabled);
|
||||
setIsAgentConversationOpen(enabled);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!isMounted) {
|
||||
return;
|
||||
}
|
||||
setIsAgentConversationEnabled(false);
|
||||
setIsAgentConversationOpen(false);
|
||||
});
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
const selectedLayerIdsRef = useRef<string[]>([]);
|
||||
const setQuickEditPanelRef = useRef<
|
||||
Dispatch<SetStateAction<QuickEditPanelState | null>>
|
||||
@@ -1167,10 +1187,6 @@ export function ImageCanvasEditorView({
|
||||
applyProjectSnapshot: applyGeneratedProjectSnapshot,
|
||||
onWalletBalanceMayHaveChanged: refreshEditorWalletBalance,
|
||||
});
|
||||
const isAgentConversationEnabled = resolveEditorAgentSidebarEnabled();
|
||||
const [isAgentConversationOpen, setIsAgentConversationOpen] = useState(
|
||||
() => isAgentConversationEnabled,
|
||||
);
|
||||
const effectiveIsAgentConversationOpen =
|
||||
isAgentConversationEnabled && isAgentConversationOpen;
|
||||
const toggleAgentConversation = useCallback(() => {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const apiClientMocks = vi.hoisted(() => ({
|
||||
requestJson: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./apiClient', async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import('./apiClient')>('./apiClient');
|
||||
return {
|
||||
...actual,
|
||||
requestJson: apiClientMocks.requestJson,
|
||||
};
|
||||
});
|
||||
|
||||
import { loadFrontendRuntimeConfig } from './frontendRuntimeConfigService';
|
||||
|
||||
describe('frontendRuntimeConfigService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
apiClientMocks.requestJson.mockResolvedValue({
|
||||
imageEditorAgentSidebarEnabled: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('loads public frontend runtime config without auth side effects', async () => {
|
||||
await expect(loadFrontendRuntimeConfig()).resolves.toEqual({
|
||||
imageEditorAgentSidebarEnabled: false,
|
||||
});
|
||||
|
||||
expect(apiClientMocks.requestJson).toHaveBeenCalledWith(
|
||||
'/api/runtime/frontend-config',
|
||||
{ method: 'GET' },
|
||||
'读取前端运行时配置失败',
|
||||
{
|
||||
skipAuth: true,
|
||||
skipRefresh: true,
|
||||
notifyAuthStateChange: false,
|
||||
clearAuthOnUnauthorized: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { requestJson } from './apiClient';
|
||||
|
||||
const FRONTEND_RUNTIME_CONFIG_API = '/api/runtime/frontend-config';
|
||||
|
||||
export type FrontendRuntimeConfig = {
|
||||
imageEditorAgentSidebarEnabled: boolean;
|
||||
};
|
||||
|
||||
export async function loadFrontendRuntimeConfig() {
|
||||
return requestJson<FrontendRuntimeConfig>(
|
||||
FRONTEND_RUNTIME_CONFIG_API,
|
||||
{ method: 'GET' },
|
||||
'读取前端运行时配置失败',
|
||||
{
|
||||
skipAuth: true,
|
||||
skipRefresh: true,
|
||||
notifyAuthStateChange: false,
|
||||
clearAuthOnUnauthorized: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
Vendored
-1
@@ -2,7 +2,6 @@
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_DEBUG_MODE?: string;
|
||||
readonly VITE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR?: string;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
|
||||
Reference in New Issue
Block a user