feat: AI端点测试动态参数表单、接口工作流行内测试、本地开发环境改为线上域名
- 后端新增 /ai/endpoint/test 和 /ai/endpoint/stream 接口,支持直接端点测试 - 前端增加行内测试功能(场景绑定+接口工作流) - 测试对话框增加动态参数表单和参数定义编辑 - 支持 _meta 格式的默认输入参数处理 - web、web-admin 本地开发环境 API 调用改为线上域名 https://lifescript.happylifeos.com Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package com.emotion.controller;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.emotion.common.Result;
|
||||
import com.emotion.dto.request.ai.AiRuntimeRequest;
|
||||
import com.emotion.dto.response.ai.AiTestTemplateResponse;
|
||||
import com.emotion.dto.response.ai.AiRuntimeTestResponse;
|
||||
import com.emotion.dto.response.ai.AiStreamEvent;
|
||||
import com.emotion.entity.AiCallLog;
|
||||
@@ -80,6 +81,11 @@ public class AiRoutingController {
|
||||
return Result.success(endpointConfigService.listVisible());
|
||||
}
|
||||
|
||||
@GetMapping("/endpoints/test-template")
|
||||
public Result<AiTestTemplateResponse> endpointTestTemplate(@RequestParam String id) {
|
||||
return Result.success(runtimeService.buildEndpointTestTemplate(id));
|
||||
}
|
||||
|
||||
@PostMapping("/endpoints")
|
||||
public Result<AiEndpointConfig> createEndpoint(@RequestBody AiEndpointConfig endpoint) {
|
||||
return Result.success(endpointConfigService.saveEndpoint(endpoint));
|
||||
@@ -101,6 +107,11 @@ public class AiRoutingController {
|
||||
return Result.success(sceneBindingService.listVisible());
|
||||
}
|
||||
|
||||
@GetMapping("/scenes/test-template")
|
||||
public Result<AiTestTemplateResponse> sceneTestTemplate(@RequestParam String sceneCode) {
|
||||
return Result.success(runtimeService.buildSceneTestTemplate(sceneCode));
|
||||
}
|
||||
|
||||
@PostMapping("/scenes")
|
||||
public Result<AiSceneBinding> createScene(@RequestBody AiSceneBinding scene) {
|
||||
if (scene.getIsEnabled() == null) {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.emotion.dto.response.ai;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AiTestTemplateResponse {
|
||||
|
||||
private String sceneCode;
|
||||
|
||||
private String endpointId;
|
||||
|
||||
private String endpointCode;
|
||||
|
||||
private String endpointName;
|
||||
|
||||
private String providerType;
|
||||
|
||||
@Builder.Default
|
||||
private Map<String, Object> inputs = new LinkedHashMap<>();
|
||||
|
||||
@Builder.Default
|
||||
private List<ParamField> paramFields = new ArrayList<>();
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class ParamField {
|
||||
private String name;
|
||||
private String label;
|
||||
private String type;
|
||||
private Object value;
|
||||
private Boolean required;
|
||||
private String placeholder;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.emotion.service;
|
||||
|
||||
import com.emotion.dto.request.ai.AiRuntimeRequest;
|
||||
import com.emotion.dto.response.ai.AiTestTemplateResponse;
|
||||
import com.emotion.dto.response.ai.AiRuntimeTestResponse;
|
||||
import com.emotion.dto.response.ai.AiStreamEvent;
|
||||
|
||||
@@ -16,4 +17,8 @@ public interface AiRuntimeService {
|
||||
AiRuntimeTestResponse testEndpoint(String endpointId, Map<String, Object> inputs);
|
||||
|
||||
void invokeEndpointStream(String endpointId, Map<String, Object> inputs, Consumer<AiStreamEvent> consumer);
|
||||
|
||||
AiTestTemplateResponse buildEndpointTestTemplate(String endpointId);
|
||||
|
||||
AiTestTemplateResponse buildSceneTestTemplate(String sceneCode);
|
||||
}
|
||||
|
||||
@@ -76,8 +76,12 @@ public class CozeProviderAdapter implements AiProviderAdapter {
|
||||
JSONObject body = new JSONObject();
|
||||
if ("workflow".equalsIgnoreCase(endpoint.getEndpointType())) {
|
||||
body.put("workflow_id", endpoint.getWorkflowId());
|
||||
if (StringUtils.hasText(endpoint.getBotId())) {
|
||||
body.put("bot_id", endpoint.getBotId());
|
||||
}
|
||||
body.put("user_id", StringUtils.hasText(request.getUserId()) ? request.getUserId() : "anonymous");
|
||||
body.put("stream", true);
|
||||
body.put("parameters", inputs);
|
||||
body.put("is_async", false);
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -90,8 +94,13 @@ public class CozeProviderAdapter implements AiProviderAdapter {
|
||||
message.put("role", "user");
|
||||
message.put("content_type", "text");
|
||||
message.put("content", templateRenderer.firstText(inputs));
|
||||
message.put("type", "question");
|
||||
messages.add(message);
|
||||
body.put("additional_messages", messages);
|
||||
body.put("parameters", new JSONObject());
|
||||
if (StringUtils.hasText(endpoint.getWorkflowId())) {
|
||||
body.put("workflow_id", endpoint.getWorkflowId());
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -108,19 +117,27 @@ public class CozeProviderAdapter implements AiProviderAdapter {
|
||||
if (StringUtils.hasText(content) && (type == null || type.contains("answer") || type.contains("delta"))) {
|
||||
return content;
|
||||
}
|
||||
String output = json.getString("output");
|
||||
if (StringUtils.hasText(output)) {
|
||||
return output;
|
||||
}
|
||||
String answer = json.getString("answer");
|
||||
if (StringUtils.hasText(answer)) {
|
||||
return answer;
|
||||
}
|
||||
JSONObject message = json.getJSONObject("message");
|
||||
if (message != null && StringUtils.hasText(message.getString("content"))) {
|
||||
return message.getString("content");
|
||||
}
|
||||
JSONObject data = json.getJSONObject("data");
|
||||
if (data != null) {
|
||||
String output = data.getString("output");
|
||||
if (StringUtils.hasText(output)) {
|
||||
return output;
|
||||
String dataOutput = data.getString("output");
|
||||
if (StringUtils.hasText(dataOutput)) {
|
||||
return dataOutput;
|
||||
}
|
||||
String answer = data.getString("answer");
|
||||
if (StringUtils.hasText(answer)) {
|
||||
return answer;
|
||||
String dataAnswer = data.getString("answer");
|
||||
if (StringUtils.hasText(dataAnswer)) {
|
||||
return dataAnswer;
|
||||
}
|
||||
String dataContent = data.getString("content");
|
||||
if (StringUtils.hasText(dataContent)) {
|
||||
|
||||
@@ -76,6 +76,10 @@ public class DifyProviderAdapter implements AiProviderAdapter {
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("response_mode", "streaming");
|
||||
body.put("user", user(request));
|
||||
Object conversationId = inputs.get("conversation_id");
|
||||
if (conversationId != null && StringUtils.hasText(String.valueOf(conversationId))) {
|
||||
body.put("conversation_id", conversationId);
|
||||
}
|
||||
if ("chat".equalsIgnoreCase(endpoint.getEndpointType())) {
|
||||
body.put("query", templateRenderer.firstText(inputs));
|
||||
body.put("inputs", inputs);
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -30,6 +31,7 @@ public class ProviderHttpSupport {
|
||||
|
||||
public void applyHeaders(HttpHeaders headers, AiProvider provider, AiEndpointConfig endpoint) {
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setAccept(List.of(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_JSON));
|
||||
if (StringUtils.hasText(provider.getApiKey())) {
|
||||
headers.setBearerAuth(provider.getApiKey());
|
||||
}
|
||||
@@ -43,6 +45,17 @@ public class ProviderHttpSupport {
|
||||
}
|
||||
try {
|
||||
JSONObject json = JSON.parseObject(data);
|
||||
String event = json.getString("event");
|
||||
String type = json.getString("type");
|
||||
if ("error".equalsIgnoreCase(event) || "error".equalsIgnoreCase(type)) {
|
||||
String code = firstText(json, "code", "error_code", "errorCode");
|
||||
String message = firstText(json, "message", "error", "error_msg", "errorMessage");
|
||||
if (!StringUtils.hasText(message)) {
|
||||
message = data;
|
||||
}
|
||||
consumer.accept(AiStreamEvent.error(StringUtils.hasText(code) ? code : "AI_PROVIDER_ERROR", message));
|
||||
throw new IllegalStateException(message);
|
||||
}
|
||||
String delta = extractor.extract(json);
|
||||
if (StringUtils.hasText(delta)) {
|
||||
counter.increment();
|
||||
@@ -54,6 +67,25 @@ public class ProviderHttpSupport {
|
||||
}
|
||||
}
|
||||
|
||||
private String firstText(JSONObject json, String... keys) {
|
||||
for (String key : keys) {
|
||||
String value = json.getString(key);
|
||||
if (StringUtils.hasText(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
JSONObject data = json.getJSONObject("data");
|
||||
if (data != null) {
|
||||
for (String key : keys) {
|
||||
String value = data.getString(key);
|
||||
if (StringUtils.hasText(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void applyJsonHeaders(HttpHeaders headers, String jsonText) {
|
||||
if (!StringUtils.hasText(jsonText)) {
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.emotion.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.emotion.dto.request.ai.AiRuntimeRequest;
|
||||
import com.emotion.dto.response.ai.AiTestTemplateResponse;
|
||||
import com.emotion.dto.response.ai.AiRuntimeTestResponse;
|
||||
import com.emotion.dto.response.ai.AiStreamEvent;
|
||||
import com.emotion.entity.AiCallLog;
|
||||
@@ -20,6 +22,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
@@ -247,6 +251,172 @@ public class AiRuntimeServiceImpl implements AiRuntimeService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiTestTemplateResponse buildEndpointTestTemplate(String endpointId) {
|
||||
AiEndpointConfig endpoint = endpointConfigService.getById(endpointId);
|
||||
if (endpoint == null || Integer.valueOf(1).equals(endpoint.getIsDeleted())) {
|
||||
throw new IllegalArgumentException("AI_ENDPOINT_NOT_FOUND");
|
||||
}
|
||||
AiProvider provider = providerService.getById(endpoint.getProviderId());
|
||||
if (provider == null || Integer.valueOf(1).equals(provider.getIsDeleted())) {
|
||||
throw new IllegalArgumentException("AI_PROVIDER_NOT_FOUND");
|
||||
}
|
||||
return buildTemplate(null, endpoint, provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiTestTemplateResponse buildSceneTestTemplate(String sceneCode) {
|
||||
AiSceneBinding scene = sceneBindingService.resolveScene(sceneCode);
|
||||
if (scene == null) {
|
||||
throw new IllegalArgumentException("AI_SCENE_NOT_BOUND");
|
||||
}
|
||||
AiEndpointConfig endpoint = endpointConfigService.getById(scene.getEndpointId());
|
||||
if (endpoint == null || Integer.valueOf(1).equals(endpoint.getIsDeleted())) {
|
||||
throw new IllegalArgumentException("AI_ENDPOINT_NOT_FOUND");
|
||||
}
|
||||
AiProvider provider = providerService.getById(endpoint.getProviderId());
|
||||
if (provider == null || Integer.valueOf(1).equals(provider.getIsDeleted())) {
|
||||
throw new IllegalArgumentException("AI_PROVIDER_NOT_FOUND");
|
||||
}
|
||||
return buildTemplate(sceneCode, endpoint, provider);
|
||||
}
|
||||
|
||||
private AiTestTemplateResponse buildTemplate(String sceneCode, AiEndpointConfig endpoint, AiProvider provider) {
|
||||
LinkedHashMap<String, Object> inputs = new LinkedHashMap<>();
|
||||
applyDefaultInputs(inputs, endpoint.getDefaultInputs());
|
||||
applyProviderSampleInputs(inputs, sceneCode, endpoint, provider);
|
||||
|
||||
List<AiTestTemplateResponse.ParamField> fields = new ArrayList<>();
|
||||
inputs.forEach((key, value) -> fields.add(AiTestTemplateResponse.ParamField.builder()
|
||||
.name(key)
|
||||
.label(paramLabel(key))
|
||||
.type(paramType(value))
|
||||
.value(value)
|
||||
.required(requiredParam(key, provider.getProviderType()))
|
||||
.placeholder(paramPlaceholder(key))
|
||||
.build()));
|
||||
|
||||
return AiTestTemplateResponse.builder()
|
||||
.sceneCode(sceneCode)
|
||||
.endpointId(endpoint.getId())
|
||||
.endpointCode(endpoint.getEndpointCode())
|
||||
.endpointName(endpoint.getEndpointName())
|
||||
.providerType(provider.getProviderType())
|
||||
.inputs(inputs)
|
||||
.paramFields(fields)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void applyDefaultInputs(LinkedHashMap<String, Object> inputs, String defaultInputs) {
|
||||
if (!StringUtils.hasText(defaultInputs)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
JSONObject parsed = JSON.parseObject(defaultInputs);
|
||||
parsed.forEach((key, value) -> {
|
||||
if (value instanceof JSONObject && ((JSONObject) value).containsKey("_meta")) {
|
||||
inputs.put(key, ((JSONObject) value).get("value"));
|
||||
} else {
|
||||
inputs.put(key, value);
|
||||
}
|
||||
});
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private void applyProviderSampleInputs(LinkedHashMap<String, Object> inputs,
|
||||
String sceneCode,
|
||||
AiEndpointConfig endpoint,
|
||||
AiProvider provider) {
|
||||
String providerType = provider.getProviderType();
|
||||
String sample = samplePrompt(sceneCode, endpoint);
|
||||
|
||||
if ("dify".equalsIgnoreCase(providerType)) {
|
||||
inputs.putIfAbsent("query", sample);
|
||||
inputs.putIfAbsent("inputs", Map.of("input", sample, "user_id", "admin-test-user"));
|
||||
inputs.putIfAbsent("response_mode", "streaming");
|
||||
inputs.putIfAbsent("conversation_id", "");
|
||||
inputs.putIfAbsent("user", "admin-test-user");
|
||||
inputs.putIfAbsent("user_id", "admin-test-user");
|
||||
return;
|
||||
}
|
||||
|
||||
inputs.putIfAbsent("input", sample);
|
||||
inputs.putIfAbsent("prompt", sample);
|
||||
inputs.putIfAbsent("message", sample);
|
||||
inputs.putIfAbsent("user_id", "admin-test-user");
|
||||
if ("chat".equalsIgnoreCase(endpoint.getEndpointType())) {
|
||||
inputs.putIfAbsent("conversationId", "admin-test-conversation");
|
||||
}
|
||||
}
|
||||
|
||||
private String samplePrompt(String sceneCode, AiEndpointConfig endpoint) {
|
||||
String code = StringUtils.hasText(sceneCode) ? sceneCode : endpoint.getEndpointCode();
|
||||
if (code == null) {
|
||||
return "请用一句中文回复测试成功。";
|
||||
}
|
||||
if (code.contains("script") || code.contains("life.generate")) {
|
||||
return "请生成一个关于普通人重新找回生活热情的短剧本,要求结构清晰、中文输出。";
|
||||
}
|
||||
if (code.contains("short_story") || code.contains("story")) {
|
||||
return "请生成一篇 300 字以内的治愈短篇小说,主题是雨后的新开始。";
|
||||
}
|
||||
if (code.contains("diary") || code.contains("healing") || code.contains("life")) {
|
||||
return "今天工作压力很大,但傍晚散步时感觉心情慢慢平静下来,请给我一段温和的回应。";
|
||||
}
|
||||
if (code.contains("summary")) {
|
||||
return "用户今天先表达焦虑,随后通过散步和朋友聊天逐渐恢复平静,请生成一段情绪总结。";
|
||||
}
|
||||
if (code.contains("chat")) {
|
||||
return "你好,请用一句中文回复:AI 接口测试成功。";
|
||||
}
|
||||
return "请用一句中文回复测试成功。";
|
||||
}
|
||||
|
||||
private String paramLabel(String key) {
|
||||
Map<String, String> labels = Map.ofEntries(
|
||||
Map.entry("input", "输入内容"),
|
||||
Map.entry("prompt", "提示词"),
|
||||
Map.entry("message", "消息内容"),
|
||||
Map.entry("query", "用户问题"),
|
||||
Map.entry("inputs", "Dify 变量"),
|
||||
Map.entry("response_mode", "响应模式"),
|
||||
Map.entry("conversation_id", "会话 ID"),
|
||||
Map.entry("user", "用户标识"),
|
||||
Map.entry("user_id", "用户标识"),
|
||||
Map.entry("conversationId", "会话 ID")
|
||||
);
|
||||
return labels.getOrDefault(key, key);
|
||||
}
|
||||
|
||||
private String paramType(Object value) {
|
||||
if (value instanceof Number) {
|
||||
return "number";
|
||||
}
|
||||
if (value instanceof Boolean) {
|
||||
return "boolean";
|
||||
}
|
||||
if (value instanceof Map || value instanceof JSONObject) {
|
||||
return "json";
|
||||
}
|
||||
String text = value == null ? "" : String.valueOf(value);
|
||||
return text.length() > 60 ? "textarea" : "string";
|
||||
}
|
||||
|
||||
private Boolean requiredParam(String key, String providerType) {
|
||||
if ("dify".equalsIgnoreCase(providerType)) {
|
||||
return "query".equals(key) || "response_mode".equals(key) || "user".equals(key);
|
||||
}
|
||||
return "input".equals(key) || "user_id".equals(key);
|
||||
}
|
||||
|
||||
private String paramPlaceholder(String key) {
|
||||
if ("inputs".equals(key)) {
|
||||
return "{\"input\":\"测试内容\"}";
|
||||
}
|
||||
return "请输入" + paramLabel(key);
|
||||
}
|
||||
|
||||
private RuntimeTarget resolveTarget(AiRuntimeRequest request) {
|
||||
if (!StringUtils.hasText(request.getSceneCode())) {
|
||||
throw new IllegalArgumentException("AI_SCENE_REQUIRED");
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# AI Endpoint Test Fix Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Make every AI provider endpoint in web-admin open with complete editable test parameters and make Coze/Dify workflow tests succeed through the backend runtime without breaking existing user-facing AI flows.
|
||||
|
||||
**Architecture:** Keep backend as the only place that knows provider credentials and provider-specific request formats. Add backend test sample generation for endpoint and scene tests, then update web-admin to load those samples when opening the dialog. Patch Coze/Dify provider adapters to match documented and historically working request/stream formats while preserving the current `/ai/runtime/stream` and `/ai/endpoint/stream` contracts.
|
||||
|
||||
**Tech Stack:** Spring Boot, FastJSON2, RestTemplate streaming, Element Plus, Vue 3, TypeScript, MySQL.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Backend Test Sample Contract
|
||||
|
||||
**Files:**
|
||||
- Create: `backend-single/src/main/java/com/emotion/dto/response/ai/AiTestTemplateResponse.java`
|
||||
- Modify: `backend-single/src/main/java/com/emotion/service/AiRuntimeService.java`
|
||||
- Modify: `backend-single/src/main/java/com/emotion/service/impl/AiRuntimeServiceImpl.java`
|
||||
- Modify: `backend-single/src/main/java/com/emotion/controller/AiRoutingController.java`
|
||||
|
||||
- [x] Add a response DTO with `sceneCode`, `endpointId`, `endpointCode`, `providerType`, `inputs`, and `paramFields`.
|
||||
- [x] Add service methods `buildEndpointTestTemplate(String endpointId)` and `buildSceneTestTemplate(String sceneCode)`.
|
||||
- [x] Generate complete sample inputs even when `defaultInputs` is `{}` or empty.
|
||||
- [x] Expose `GET /ai/endpoints/test-template?id=...` and `GET /ai/scenes/test-template?sceneCode=...`.
|
||||
|
||||
### Task 2: Provider Runtime Compatibility
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend-single/src/main/java/com/emotion/service/ai/ProviderHttpSupport.java`
|
||||
- Modify: `backend-single/src/main/java/com/emotion/service/ai/CozeProviderAdapter.java`
|
||||
- Modify: `backend-single/src/main/java/com/emotion/service/ai/DifyProviderAdapter.java`
|
||||
|
||||
- [x] Add `Accept: text/event-stream` for stream requests.
|
||||
- [x] Make Coze workflow body match historical calls: `workflow_id`, `user_id`, `stream`, `parameters`, and optional `bot_id`.
|
||||
- [x] Make Coze chat body include `type: question` in `additional_messages`.
|
||||
- [x] Parse Coze workflow events from `output`, `content`, `answer`, `data.output`, `data.content`, and nested message fields.
|
||||
- [x] Keep Dify chat body aligned with `docs/dify平台接口.md`: `query`, `inputs`, `response_mode`, `user`, optional `conversation_id`.
|
||||
- [x] Emit provider error events when upstream SSE includes an error event.
|
||||
|
||||
### Task 3: web-admin Test Dialog Defaults
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/types/aiconfig.ts`
|
||||
- Modify: `web-admin/src/api/aiconfig.ts`
|
||||
- Modify: `web-admin/src/views/aiconfig/AiRoutingList.vue`
|
||||
|
||||
- [x] Add `AiTestTemplateResponse` TypeScript type.
|
||||
- [x] Add API helpers for endpoint and scene test templates.
|
||||
- [x] When opening endpoint test dialog, request backend template and populate form fields plus JSON editor.
|
||||
- [x] When opening scene test dialog, request backend template and populate JSON editor.
|
||||
- [x] Validate required fields before running stream/non-stream tests.
|
||||
- [x] Keep the existing editable JSON advanced area so testers can modify payloads.
|
||||
|
||||
### Task 4: Database Default Input Backfill
|
||||
|
||||
**Files:**
|
||||
- Modify: `sql/2026-05-22-ai-scene-routing.sql`
|
||||
|
||||
- [x] Backfill `default_inputs` for existing Coze and Dify endpoints with realistic examples.
|
||||
- [x] Apply the SQL to the test database.
|
||||
- [x] Verify all enabled endpoints have non-empty `default_inputs`.
|
||||
|
||||
### Task 5: Verification
|
||||
|
||||
**Commands:**
|
||||
- `mvn test`
|
||||
- `mvn -DskipTests clean package`
|
||||
- `npm run build` in `web-admin`
|
||||
- MySQL query for endpoint default inputs.
|
||||
|
||||
- [x] Backend tests pass.
|
||||
- [x] Backend package succeeds.
|
||||
- [x] web-admin build succeeds.
|
||||
- [x] Coze endpoint test returns stream output or a provider business error with full request/log context.
|
||||
- [x] Dify endpoint test returns stream output or a provider business error with full request/log context.
|
||||
- [x] Existing scene runtime endpoints remain unchanged for web, life-script, and mini-program clients.
|
||||
@@ -144,6 +144,34 @@ WHERE c.`is_deleted` = 0
|
||||
ORDER BY c.`create_time` DESC
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO `t_ai_provider`
|
||||
(`id`, `provider_code`, `provider_name`, `provider_type`, `base_url`, `api_key`, `auth_type`, `default_headers`, `timeout_ms`, `is_enabled`, `description`)
|
||||
SELECT UUID(),
|
||||
CONCAT('coze_', REPLACE(c.`config_key`, '.', '_')),
|
||||
CONCAT(c.`config_name`, '服务商'),
|
||||
'coze',
|
||||
CASE
|
||||
WHEN c.`api_base_url` LIKE '%/v1/workflow/stream_run' THEN REPLACE(c.`api_base_url`, '/v1/workflow/stream_run', '')
|
||||
WHEN c.`api_base_url` LIKE '%/v3/chat' THEN REPLACE(c.`api_base_url`, '/v3/chat', '')
|
||||
ELSE c.`api_base_url`
|
||||
END,
|
||||
c.`api_token`,
|
||||
'bearer',
|
||||
NULL,
|
||||
COALESCE(c.`timeout_ms`, 60000),
|
||||
c.`is_enabled`,
|
||||
CONCAT('由旧 AI 配置迁移生成,配置键:', c.`config_key`)
|
||||
FROM `t_ai_config` c
|
||||
WHERE c.`is_deleted` = 0
|
||||
AND c.`provider` = 'coze'
|
||||
AND c.`api_token` IS NOT NULL
|
||||
AND c.`api_token` <> ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM `t_ai_provider` p
|
||||
WHERE p.`provider_code` COLLATE utf8mb4_unicode_ci = CONCAT('coze_', REPLACE(c.`config_key`, '.', '_')) COLLATE utf8mb4_unicode_ci
|
||||
AND p.`is_deleted` = 0
|
||||
);
|
||||
|
||||
INSERT INTO `t_ai_provider`
|
||||
(`id`, `provider_code`, `provider_name`, `provider_type`, `base_url`, `api_key`, `auth_type`, `default_headers`, `timeout_ms`, `is_enabled`, `description`)
|
||||
SELECT UUID(), 'dify_default', 'Dify 默认服务商', 'dify', 'http://49.232.138.53/v1', 'app-MqQOx09gCu9zzlKMpeLqHQHv',
|
||||
@@ -189,6 +217,12 @@ WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `t_ai_endpoint_config` e WHERE e.`endpoint_code` = seed.endpoint_code AND e.`is_deleted` = 0
|
||||
);
|
||||
|
||||
UPDATE `t_ai_endpoint_config` e
|
||||
JOIN `t_ai_provider` p ON p.`provider_code` COLLATE utf8mb4_unicode_ci = CONCAT('coze_', REPLACE(e.`endpoint_code`, '.', '_')) COLLATE utf8mb4_unicode_ci AND p.`is_deleted` = 0
|
||||
SET e.`provider_id` = p.`id`
|
||||
WHERE e.`is_deleted` = 0
|
||||
AND e.`endpoint_code` LIKE 'coze.%';
|
||||
|
||||
UPDATE `t_ai_scene_binding` s
|
||||
JOIN `t_ai_endpoint_config` e ON e.`endpoint_code` = 'coze.chat.default' AND e.`is_deleted` = 0
|
||||
SET s.`endpoint_id` = e.`id`, s.`is_enabled` = 1, s.`required_stream` = 1, s.`description` = '已绑定现有 Coze 聊天工作流'
|
||||
@@ -230,3 +264,39 @@ UPDATE `t_ai_scene_binding` s
|
||||
JOIN `t_ai_endpoint_config` e ON e.`endpoint_code` = 'coze.user.dairy.summary' AND e.`is_deleted` = 0
|
||||
SET s.`endpoint_id` = e.`id`, s.`is_enabled` = 1, s.`required_stream` = 1, s.`description` = '已绑定现有 Coze 人生事件疗愈工作流'
|
||||
WHERE s.`scene_code` = 'life_healing' AND s.`is_deleted` = 0;
|
||||
|
||||
UPDATE `t_ai_endpoint_config`
|
||||
SET `default_inputs` = '{"input":{"_meta":{"label":"输入内容","type":"textarea","required":true},"value":"你好,请用一句中文回复:AI 接口测试成功。"},"user_id":{"_meta":{"label":"用户标识","type":"string","required":true},"value":"admin-test-user"}}'
|
||||
WHERE `endpoint_code` = 'coze.chat.default'
|
||||
AND `is_deleted` = 0
|
||||
AND (`default_inputs` IS NULL OR `default_inputs` = '' OR `default_inputs` = '{}');
|
||||
|
||||
UPDATE `t_ai_endpoint_config`
|
||||
SET `default_inputs` = '{"input":{"_meta":{"label":"剧本需求","type":"textarea","required":true},"value":"请生成一个关于普通人重新找回生活热情的短剧本,要求结构清晰、中文输出。"},"user_id":{"_meta":{"label":"用户标识","type":"string","required":true},"value":"admin-test-user"}}'
|
||||
WHERE `endpoint_code` = 'coze.course.life.generate'
|
||||
AND `is_deleted` = 0
|
||||
AND (`default_inputs` IS NULL OR `default_inputs` = '' OR `default_inputs` = '{}');
|
||||
|
||||
UPDATE `t_ai_endpoint_config`
|
||||
SET `default_inputs` = '{"input":{"_meta":{"label":"短篇小说主题","type":"textarea","required":true},"value":"请生成一篇 300 字以内的治愈短篇小说,主题是雨后的新开始。"},"user_id":{"_meta":{"label":"用户标识","type":"string","required":true},"value":"admin-test-user"}}'
|
||||
WHERE `endpoint_code` = 'coze.user.chose.story'
|
||||
AND `is_deleted` = 0
|
||||
AND (`default_inputs` IS NULL OR `default_inputs` = '' OR `default_inputs` = '{}');
|
||||
|
||||
UPDATE `t_ai_endpoint_config`
|
||||
SET `default_inputs` = '{"query":{"_meta":{"label":"用户问题","type":"textarea","required":true},"value":"请生成一个关于普通人重新找回生活热情的短剧本,要求结构清晰、中文输出。"},"inputs":{"_meta":{"label":"Dify 变量","type":"json","required":false},"value":{"input":"请生成一个关于普通人重新找回生活热情的短剧本,要求结构清晰、中文输出。","user_id":"admin-test-user"}},"response_mode":{"_meta":{"label":"响应模式","type":"string","required":true},"value":"streaming"},"conversation_id":{"_meta":{"label":"会话 ID","type":"string","required":false},"value":""},"user":{"_meta":{"label":"用户标识","type":"string","required":true},"value":"admin-test-user"},"user_id":{"_meta":{"label":"Dify 变量用户标识","type":"string","required":true},"value":"admin-test-user"}}'
|
||||
WHERE `endpoint_code` = 'dify.script_generate.chat_messages'
|
||||
AND `is_deleted` = 0
|
||||
AND (`default_inputs` IS NULL OR `default_inputs` = '' OR `default_inputs` = '{}' OR `default_inputs` NOT LIKE '%"user_id"%');
|
||||
|
||||
UPDATE `t_ai_endpoint_config`
|
||||
SET `default_inputs` = '{"query":{"_meta":{"label":"用户问题","type":"textarea","required":true},"value":"请生成一篇 300 字以内的治愈短篇小说,主题是雨后的新开始。"},"inputs":{"_meta":{"label":"Dify 变量","type":"json","required":false},"value":{"input":"请生成一篇 300 字以内的治愈短篇小说,主题是雨后的新开始。","user_id":"admin-test-user"}},"response_mode":{"_meta":{"label":"响应模式","type":"string","required":true},"value":"streaming"},"conversation_id":{"_meta":{"label":"会话 ID","type":"string","required":false},"value":""},"user":{"_meta":{"label":"用户标识","type":"string","required":true},"value":"admin-test-user"},"user_id":{"_meta":{"label":"Dify 变量用户标识","type":"string","required":true},"value":"admin-test-user"}}'
|
||||
WHERE `endpoint_code` = 'dify.short_story_generate.chat_messages'
|
||||
AND `is_deleted` = 0
|
||||
AND (`default_inputs` IS NULL OR `default_inputs` = '' OR `default_inputs` = '{}' OR `default_inputs` NOT LIKE '%"user_id"%');
|
||||
|
||||
UPDATE `t_ai_endpoint_config`
|
||||
SET `default_inputs` = '{"input":{"_meta":{"label":"事件或日记内容","type":"textarea","required":true},"value":"今天工作压力很大,但傍晚散步时感觉心情慢慢平静下来,请给我一段温和的回应。"},"user_id":{"_meta":{"label":"用户标识","type":"string","required":true},"value":"admin-test-user"}}'
|
||||
WHERE `endpoint_code` IN ('coze.user.dairy.summary', 'coze.user.life.state', 'coze.user.query.polish')
|
||||
AND `is_deleted` = 0
|
||||
AND (`default_inputs` IS NULL OR `default_inputs` = '' OR `default_inputs` = '{}');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 开发环境配置
|
||||
VITE_APP_TITLE=情绪博物馆管理后台
|
||||
VITE_APP_BASE_API=http://localhost:19089/api
|
||||
VITE_APP_BASE_API=https://lifescript.happylifeos.com/api
|
||||
VITE_APP_PORT=5174
|
||||
|
||||
@@ -237,6 +237,10 @@ export function listAiEndpoints() {
|
||||
return request({ url: '/ai/endpoints', method: 'get' })
|
||||
}
|
||||
|
||||
export function getEndpointTestTemplate(id: string) {
|
||||
return request({ url: '/ai/endpoints/test-template', method: 'get', params: { id } })
|
||||
}
|
||||
|
||||
export function saveAiEndpoint(data: AiEndpointConfig) {
|
||||
return request({ url: '/ai/endpoints', method: data.id ? 'put' : 'post', data })
|
||||
}
|
||||
@@ -249,6 +253,10 @@ export function listAiScenes() {
|
||||
return request({ url: '/ai/scenes', method: 'get' })
|
||||
}
|
||||
|
||||
export function getSceneTestTemplate(sceneCode: string) {
|
||||
return request({ url: '/ai/scenes/test-template', method: 'get', params: { sceneCode } })
|
||||
}
|
||||
|
||||
export function saveAiScene(data: AiSceneBinding) {
|
||||
return request({ url: '/ai/scenes', method: data.id ? 'put' : 'post', data })
|
||||
}
|
||||
|
||||
@@ -273,16 +273,17 @@ export interface AiEndpointRuntimeRequest {
|
||||
export interface TestParamField {
|
||||
name: string
|
||||
label: string
|
||||
type: 'string' | 'textarea' | 'number' | 'boolean'
|
||||
type: 'string' | 'textarea' | 'number' | 'boolean' | 'json'
|
||||
value: any
|
||||
defaultValue: any
|
||||
required: boolean
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
export interface ParamDefinition {
|
||||
name: string
|
||||
label: string
|
||||
type: 'string' | 'textarea' | 'number' | 'boolean'
|
||||
type: 'string' | 'textarea' | 'number' | 'boolean' | 'json'
|
||||
defaultValue: any
|
||||
required: boolean
|
||||
}
|
||||
@@ -296,3 +297,13 @@ export interface AiRuntimeTestResponse {
|
||||
errorCode?: string
|
||||
errorMessage?: string
|
||||
}
|
||||
|
||||
export interface AiTestTemplateResponse {
|
||||
sceneCode?: string
|
||||
endpointId?: string
|
||||
endpointCode?: string
|
||||
endpointName?: string
|
||||
providerType?: string
|
||||
inputs: Record<string, any>
|
||||
paramFields: TestParamField[]
|
||||
}
|
||||
|
||||
@@ -218,6 +218,7 @@
|
||||
<el-option label="多行" value="textarea" />
|
||||
<el-option label="数字" value="number" />
|
||||
<el-option label="开关" value="boolean" />
|
||||
<el-option label="JSON" value="json" />
|
||||
</el-select>
|
||||
<el-input v-model="param.defaultValue" placeholder="默认值" style="width: 100px" />
|
||||
<el-checkbox v-model="param.required">必填</el-checkbox>
|
||||
@@ -298,6 +299,7 @@
|
||||
<el-input v-if="field.type === 'textarea'" v-model="field.value" type="textarea" :rows="3" :placeholder="field.name" @input="syncEndpointJsonFromFields" />
|
||||
<el-input-number v-if="field.type === 'number'" v-model="field.value" :placeholder="field.name" @change="syncEndpointJsonFromFields" style="width: 100%" />
|
||||
<el-switch v-if="field.type === 'boolean'" v-model="field.value" @change="syncEndpointJsonFromFields" />
|
||||
<el-input v-if="field.type === 'json'" v-model="field.value" type="textarea" :rows="4" :placeholder="field.placeholder || field.name" @input="syncEndpointJsonFromFields" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-collapse style="margin-top: 12px">
|
||||
@@ -336,6 +338,8 @@ import {
|
||||
deleteAiEndpoint,
|
||||
deleteAiProvider,
|
||||
deleteAiScene,
|
||||
getEndpointTestTemplate,
|
||||
getSceneTestTemplate,
|
||||
listAiCallLogs,
|
||||
listAiEndpoints,
|
||||
listAiProviders,
|
||||
@@ -348,7 +352,7 @@ import {
|
||||
testAiRuntime,
|
||||
testEndpointRuntime
|
||||
} from '@/api/aiconfig'
|
||||
import type { AiCallLog, AiEndpointConfig, AiProvider, AiRuntimeTestResponse, AiSceneBinding, TestParamField, ParamDefinition } from '@/types/aiconfig'
|
||||
import type { AiCallLog, AiEndpointConfig, AiProvider, AiRuntimeTestResponse, AiSceneBinding, TestParamField, ParamDefinition, AiTestTemplateResponse } from '@/types/aiconfig'
|
||||
|
||||
const activeTab = ref('providers')
|
||||
const loading = ref(false)
|
||||
@@ -478,27 +482,67 @@ function openScene(row?: AiSceneBinding) {
|
||||
sceneDialog.value = true
|
||||
}
|
||||
|
||||
function openRuntimeTest() {
|
||||
async function openRuntimeTest() {
|
||||
testForm.sceneCode = scenes.value.find(item => item.isEnabled === 1 && item.endpointId)?.sceneCode || scenes.value[0]?.sceneCode || ''
|
||||
testResult.value = null
|
||||
nonStreamResult.value = null
|
||||
await loadSceneTemplate(testForm.sceneCode)
|
||||
testDialog.value = true
|
||||
}
|
||||
|
||||
function openSceneRuntimeTest(row: AiSceneBinding) {
|
||||
async function openSceneRuntimeTest(row: AiSceneBinding) {
|
||||
testForm.sceneCode = row.sceneCode
|
||||
testResult.value = null
|
||||
nonStreamResult.value = null
|
||||
await loadSceneTemplate(row.sceneCode)
|
||||
testDialog.value = true
|
||||
}
|
||||
|
||||
function openEndpointTest(row: AiEndpointConfig) {
|
||||
async function openEndpointTest(row: AiEndpointConfig) {
|
||||
if (!row.id) return
|
||||
endpointTestRow.value = row
|
||||
endpointParamFields.value = parseParamFields(row.defaultInputs)
|
||||
endpointInputsJson.value = buildInputsJson(endpointParamFields.value)
|
||||
endpointTestResult.value = null
|
||||
endpointNonStreamResult.value = null
|
||||
endpointTestDialog.value = true
|
||||
endpointTesting.value = true
|
||||
try {
|
||||
const res = await getEndpointTestTemplate(row.id!)
|
||||
applyEndpointTemplate(res.data as AiTestTemplateResponse)
|
||||
} catch (error: any) {
|
||||
ElMessage.warning(error?.message || '测试样例加载失败,已使用本地默认参数')
|
||||
} finally {
|
||||
endpointTesting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSceneTemplate(sceneCode: string) {
|
||||
if (!sceneCode) {
|
||||
testInputsJson.value = '{\n "prompt": "请用一句中文回复测试成功。"\n}'
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await getSceneTestTemplate(sceneCode)
|
||||
const template = res.data as AiTestTemplateResponse
|
||||
testInputsJson.value = JSON.stringify(template.inputs || {}, null, 2)
|
||||
} catch (error: any) {
|
||||
testInputsJson.value = '{\n "prompt": "请用一句中文回复测试成功。"\n}'
|
||||
ElMessage.warning(error?.message || '场景测试样例加载失败,已使用通用测试参数')
|
||||
}
|
||||
}
|
||||
|
||||
function applyEndpointTemplate(template: AiTestTemplateResponse) {
|
||||
const inputs = template.inputs || {}
|
||||
endpointInputsJson.value = JSON.stringify(inputs, null, 2)
|
||||
endpointParamFields.value = (template.paramFields || []).map(field => ({
|
||||
...field,
|
||||
value: field.type === 'json'
|
||||
? JSON.stringify(field.value ?? {}, null, 2)
|
||||
: field.value,
|
||||
defaultValue: field.value,
|
||||
required: Boolean(field.required)
|
||||
}))
|
||||
}
|
||||
|
||||
function parseParamFields(defaultInputs?: string): TestParamField[] {
|
||||
@@ -533,16 +577,28 @@ function parseParamFields(defaultInputs?: string): TestParamField[] {
|
||||
}
|
||||
}
|
||||
|
||||
function inferParamType(val: any): 'string' | 'textarea' | 'number' | 'boolean' {
|
||||
function inferParamType(val: any): 'string' | 'textarea' | 'number' | 'boolean' | 'json' {
|
||||
if (typeof val === 'number') return 'number'
|
||||
if (typeof val === 'boolean') return 'boolean'
|
||||
if (val && typeof val === 'object') return 'json'
|
||||
if (typeof val === 'string' && val.length > 80) return 'textarea'
|
||||
return 'string'
|
||||
}
|
||||
|
||||
function buildInputsJson(fields: TestParamField[]): string {
|
||||
const obj: Record<string, any> = {}
|
||||
fields.forEach(f => { obj[f.name] = f.value })
|
||||
fields.forEach(f => {
|
||||
if (!f.name) return
|
||||
if (f.type === 'json') {
|
||||
try {
|
||||
obj[f.name] = typeof f.value === 'string' ? JSON.parse(f.value || '{}') : f.value
|
||||
} catch {
|
||||
obj[f.name] = f.value
|
||||
}
|
||||
} else {
|
||||
obj[f.name] = f.value
|
||||
}
|
||||
})
|
||||
return JSON.stringify(obj, null, 2)
|
||||
}
|
||||
|
||||
@@ -555,7 +611,9 @@ function syncEndpointFieldsFromJson() {
|
||||
const parsed = JSON.parse(endpointInputsJson.value || '{}')
|
||||
endpointParamFields.value.forEach(field => {
|
||||
if (field.name in parsed) {
|
||||
field.value = parsed[field.name]
|
||||
field.value = field.type === 'json'
|
||||
? JSON.stringify(parsed[field.name] ?? {}, null, 2)
|
||||
: parsed[field.name]
|
||||
}
|
||||
})
|
||||
} catch { /* ignore parse errors */ }
|
||||
@@ -726,6 +784,7 @@ async function submitRuntimeTest() {
|
||||
|
||||
async function submitEndpointNonStreamTest() {
|
||||
if (!endpointTestRow.value) return
|
||||
if (!validateEndpointFields()) return
|
||||
let inputs: Record<string, any>
|
||||
try {
|
||||
inputs = JSON.parse(endpointInputsJson.value || '{}')
|
||||
@@ -757,6 +816,7 @@ async function submitEndpointNonStreamTest() {
|
||||
|
||||
async function submitEndpointStreamTest() {
|
||||
if (!endpointTestRow.value) return
|
||||
if (!validateEndpointFields()) return
|
||||
let inputs: Record<string, any>
|
||||
try {
|
||||
inputs = JSON.parse(endpointInputsJson.value || '{}')
|
||||
@@ -796,6 +856,26 @@ async function submitEndpointStreamTest() {
|
||||
}
|
||||
}
|
||||
|
||||
function validateEndpointFields() {
|
||||
for (const field of endpointParamFields.value) {
|
||||
if (!field.required) continue
|
||||
const value = field.value
|
||||
if (value === null || value === undefined || String(value).trim() === '') {
|
||||
ElMessage.error(`请填写必填参数:${field.label || field.name}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
for (const field of endpointParamFields.value.filter(item => item.type === 'json')) {
|
||||
try {
|
||||
JSON.parse(field.value || '{}')
|
||||
} catch {
|
||||
ElMessage.error(`参数 ${field.label || field.name} 不是有效 JSON`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
onMounted(loadAll)
|
||||
</script>
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export default defineConfig({
|
||||
port: 5174,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:19089',
|
||||
target: 'https://lifescript.happylifeos.com',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ VITE_APP_TITLE=情绪博物馆 - 开发
|
||||
VITE_APP_VERSION=1.0.0
|
||||
|
||||
# API配置
|
||||
VITE_API_BASE_URL=http://localhost:19089/api
|
||||
VITE_WS_BASE_URL=ws://localhost:19089/api
|
||||
VITE_UPLOAD_URL=http://localhost:19089/api/upload
|
||||
VITE_API_BASE_URL=https://lifescript.happylifeos.com/api
|
||||
VITE_WS_BASE_URL=wss://lifescript.happylifeos.com/ws
|
||||
VITE_UPLOAD_URL=https://lifescript.happylifeos.com/api/upload
|
||||
|
||||
# 调试配置
|
||||
VITE_DEBUG=true
|
||||
|
||||
@@ -79,9 +79,9 @@ export const getEnvConfig = (): EnvConfig => {
|
||||
case 'local':
|
||||
return {
|
||||
name: '本地环境',
|
||||
apiBaseUrl: 'http://localhost:19089/api',
|
||||
wsBaseUrl: 'ws://localhost:19089/api',
|
||||
uploadUrl: 'http://localhost:19089/api/upload',
|
||||
apiBaseUrl: 'https://lifescript.happylifeos.com/api',
|
||||
wsBaseUrl: 'wss://lifescript.happylifeos.com/ws',
|
||||
uploadUrl: 'https://lifescript.happylifeos.com/api/upload',
|
||||
debug: true,
|
||||
mock: false,
|
||||
appTitle: '情绪博物馆 - 本地',
|
||||
@@ -91,9 +91,9 @@ export const getEnvConfig = (): EnvConfig => {
|
||||
case 'dev':
|
||||
return {
|
||||
name: '开发环境',
|
||||
apiBaseUrl: 'http://localhost:19089/api',
|
||||
wsBaseUrl: 'ws://localhost:19089/api',
|
||||
uploadUrl: 'http://localhost:19089/api/upload',
|
||||
apiBaseUrl: 'https://lifescript.happylifeos.com/api',
|
||||
wsBaseUrl: 'wss://lifescript.happylifeos.com/ws',
|
||||
uploadUrl: 'https://lifescript.happylifeos.com/api/upload',
|
||||
debug: true,
|
||||
mock: false,
|
||||
appTitle: '情绪博物馆 - 开发',
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ export default defineConfig({
|
||||
open: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:19089',
|
||||
target: 'https://lifescript.happylifeos.com',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user