22 changed files with 966 additions and 173 deletions
@ -0,0 +1,65 @@ |
|||
services: |
|||
postgres: |
|||
image: postgres:16-alpine |
|||
container_name: ent-postgres |
|||
restart: always |
|||
environment: |
|||
POSTGRES_USER: enterprise |
|||
POSTGRES_PASSWORD: enterprise123 |
|||
POSTGRES_DB: enterprise_ai |
|||
volumes: |
|||
- postgres_data:/var/lib/postgresql/data |
|||
- ./init-db:/docker-entrypoint-initdb.d |
|||
ports: |
|||
- "5431:5432" |
|||
healthcheck: |
|||
test: ["CMD-SHELL", "pg_isready -U enterprise"] |
|||
interval: 5s |
|||
timeout: 5s |
|||
retries: 5 |
|||
|
|||
redis: |
|||
image: redis:7-alpine |
|||
container_name: ent-redis |
|||
restart: always |
|||
command: redis-server --appendonly yes --requirepass redis123 |
|||
volumes: |
|||
- redis_data:/data |
|||
ports: |
|||
- "6378:6379" |
|||
|
|||
backend: |
|||
build: |
|||
context: ./backend |
|||
dockerfile: Dockerfile |
|||
container_name: ent-backend |
|||
restart: always |
|||
depends_on: |
|||
postgres: |
|||
condition: service_healthy |
|||
redis: |
|||
condition: service_started |
|||
environment: |
|||
DATABASE_URL: postgresql+asyncpg://enterprise:enterprise123@postgres:5432/enterprise_ai |
|||
REDIS_URL: redis://:redis123@redis:6379/0 |
|||
JWT_SECRET: dev-secret-key-change-in-production-32chars |
|||
LLM_API_KEY: ${LLM_API_KEY:-sk-placeholder} |
|||
LLM_API_BASE: ${LLM_API_BASE:-https://api.openai.com/v1} |
|||
LLM_MODEL: ${LLM_MODEL:-gpt-4o-mini} |
|||
ports: |
|||
- "8100:8000" |
|||
|
|||
frontend: |
|||
build: |
|||
context: ./frontend |
|||
dockerfile: Dockerfile.prod |
|||
container_name: ent-frontend |
|||
restart: always |
|||
depends_on: |
|||
- backend |
|||
ports: |
|||
- "80:80" |
|||
|
|||
volumes: |
|||
postgres_data: |
|||
redis_data: |
|||
@ -1,12 +1,10 @@ |
|||
FROM node:20-alpine AS build |
|||
FROM node:20-alpine |
|||
|
|||
WORKDIR /app |
|||
|
|||
COPY package*.json ./ |
|||
RUN npm install |
|||
COPY . . |
|||
RUN npm run build |
|||
|
|||
FROM nginx:alpine |
|||
COPY --from=build /app/dist /usr/share/nginx/html |
|||
COPY nginx.conf /etc/nginx/conf.d/default.conf |
|||
EXPOSE 80 |
|||
EXPOSE 80 |
|||
|
|||
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "80"] |
|||
|
|||
@ -0,0 +1,10 @@ |
|||
FROM node:20-alpine |
|||
|
|||
WORKDIR /app |
|||
|
|||
COPY package*.json ./ |
|||
RUN npm install |
|||
|
|||
EXPOSE 80 |
|||
|
|||
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "80"] |
|||
@ -0,0 +1,16 @@ |
|||
FROM node:20-alpine AS builder |
|||
|
|||
WORKDIR /app |
|||
COPY package*.json ./ |
|||
RUN npm install |
|||
COPY . . |
|||
RUN npm run build |
|||
|
|||
FROM nginx:alpine |
|||
|
|||
COPY --from=builder /app/dist /usr/share/nginx/html |
|||
COPY ../nginx/nginx.prod.conf /etc/nginx/nginx.conf |
|||
|
|||
EXPOSE 80 |
|||
|
|||
CMD ["nginx", "-g", "daemon off;"] |
|||
@ -0,0 +1,64 @@ |
|||
<template> |
|||
<div class="node-config"> |
|||
<el-divider content-position="left">条件配置</el-divider> |
|||
|
|||
<el-form-item label="条件类型"> |
|||
<el-select v-model="config.condition_type" @change="$emit('change')"> |
|||
<el-option label="表达式" value="expression" /> |
|||
<el-option label="JSON路径" value="json_path" /> |
|||
<el-option label="正则匹配" value="regex" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="条件表达式"> |
|||
<el-input v-model="config.condition" placeholder="如: status == 'done'" @change="$emit('change')" /> |
|||
<div class="condition-hint">支持变量: {{input_data}}, {{status}}, {{result}}</div> |
|||
</el-form-item> |
|||
|
|||
<el-divider content-position="left">分支配置</el-divider> |
|||
|
|||
<el-form-item label="True分支"> |
|||
<el-input v-model="config.true_label" placeholder="条件满足时的输出标签" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="False分支"> |
|||
<el-input v-model="config.false_label" placeholder="条件不满足时的输出标签" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-divider content-position="left">高级选项</el-divider> |
|||
|
|||
<el-form-item label="短路评估"> |
|||
<el-switch v-model="config.short_circuit" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="默认分支"> |
|||
<el-select v-model="config.default_branch" @change="$emit('change')"> |
|||
<el-option label="True" value="true" /> |
|||
<el-option label="False" value="false" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
const config = defineModel<any>({ |
|||
default: () => ({ |
|||
condition_type: 'expression', |
|||
condition: '', |
|||
true_label: '是', |
|||
false_label: '否', |
|||
short_circuit: true, |
|||
default_branch: 'false' |
|||
}) |
|||
}) |
|||
|
|||
defineEmits(['change']) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.condition-hint { |
|||
font-size: 12px; |
|||
color: #909399; |
|||
margin-top: 4px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,92 @@ |
|||
<template> |
|||
<div class="node-config"> |
|||
<el-divider content-position="left">Agent配置</el-divider> |
|||
|
|||
<el-form-item label="选择Agent"> |
|||
<el-select v-model="config.agent_id" @change="onAgentSelect" placeholder="手动配置或选择已有Agent" clearable> |
|||
<el-option v-for="a in agentList" :key="a.id" :label="a.name" :value="a.id" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-divider content-position="left">模型配置</el-divider> |
|||
|
|||
<el-form-item label="系统提示词"> |
|||
<el-input v-model="config.system_prompt" type="textarea" :rows="4" @change="$emit('change')" placeholder="输入系统提示词,定义AI角色和行为" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="模型"> |
|||
<el-select v-model="config.model" @change="$emit('change')"> |
|||
<el-option label="GPT-4o-mini" value="gpt-4o-mini" /> |
|||
<el-option label="GPT-4o" value="gpt-4o" /> |
|||
<el-option label="GPT-3.5-turbo" value="gpt-3.5-turbo" /> |
|||
<el-option label="DeepSeek-V3" value="deepseek-chat" /> |
|||
<el-option label="DeepSeek-R1" value="deepseek-reasoner" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="温度"> |
|||
<el-slider v-model="config.temperature" :min="0" :max="2" :step="0.1" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="最大Token数"> |
|||
<el-input-number v-model="config.max_tokens" :min="1" :max="4000" :step="100" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-divider content-position="left">记忆配置</el-divider> |
|||
|
|||
<el-form-item label="上下文长度"> |
|||
<el-input-number v-model="config.context_length" :min="1" :max="20" :step="1" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="记忆模式"> |
|||
<el-select v-model="config.memory_mode" @change="$emit('change')"> |
|||
<el-option label="无记忆" value="none" /> |
|||
<el-option label="短期记忆" value="short_term" /> |
|||
<el-option label="长期记忆" value="long_term" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-divider content-position="left">高级选项</el-divider> |
|||
|
|||
<el-form-item label="流式输出"> |
|||
<el-switch v-model="config.stream" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="函数调用"> |
|||
<el-switch v-model="config.tool_call" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
const config = defineModel<any>({ |
|||
default: () => ({ |
|||
system_prompt: '', |
|||
model: 'gpt-4o-mini', |
|||
temperature: 0.7, |
|||
agent_id: '', |
|||
max_tokens: 2000, |
|||
context_length: 5, |
|||
memory_mode: 'short_term', |
|||
stream: true, |
|||
tool_call: false |
|||
}) |
|||
}) |
|||
|
|||
const props = defineProps<{ |
|||
agentList: any[] |
|||
}>() |
|||
|
|||
defineEmits(['change']) |
|||
|
|||
function onAgentSelect(val: string) { |
|||
if (!val) return |
|||
const agent = props.agentList.find(a => a.id === val) |
|||
if (agent) { |
|||
config.value.system_prompt = agent.system_prompt || config.value.system_prompt |
|||
config.value.model = agent.model || config.value.model |
|||
config.value.temperature = agent.temperature ?? config.value.temperature |
|||
config.value.max_tokens = agent.max_tokens || config.value.max_tokens |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,63 @@ |
|||
<template> |
|||
<div class="node-config"> |
|||
<el-divider content-position="left">MCP服务配置</el-divider> |
|||
|
|||
<el-form-item label="MCP服务器"> |
|||
<el-select v-model="config.mcp_server" @change="$emit('change')" placeholder="选择MCP服务"> |
|||
<el-option label="未选择" value="" /> |
|||
<el-option v-for="s in mcpServers" :key="s.id" :label="s.name" :value="s.name" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="工具名"> |
|||
<el-input v-model="config.tool_name" placeholder="如: search" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-divider content-position="left">请求参数</el-divider> |
|||
|
|||
<el-form-item label="输入参数"> |
|||
<el-input v-model="config.input_params" type="textarea" :rows="3" @change="$emit('change')" placeholder='{"param1": "value1"}' /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="超时时间(秒)"> |
|||
<el-input-number v-model="config.timeout" :min="1" :max="300" :step="10" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-divider content-position="left">响应处理</el-divider> |
|||
|
|||
<el-form-item label="响应解析"> |
|||
<el-select v-model="config.response_parser" @change="$emit('change')"> |
|||
<el-option label="JSON解析" value="json" /> |
|||
<el-option label="文本提取" value="text" /> |
|||
<el-option label="自定义解析" value="custom" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="错误处理"> |
|||
<el-select v-model="config.error_handling" @change="$emit('change')"> |
|||
<el-option label="抛出错误" value="throw" /> |
|||
<el-option label="返回默认值" value="default" /> |
|||
<el-option label="跳过继续" value="skip" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
const config = defineModel<any>({ |
|||
default: () => ({ |
|||
mcp_server: '', |
|||
tool_name: '', |
|||
input_params: '{}', |
|||
timeout: 30, |
|||
response_parser: 'json', |
|||
error_handling: 'throw' |
|||
}) |
|||
}) |
|||
|
|||
defineProps<{ |
|||
mcpServers: any[] |
|||
}>() |
|||
|
|||
defineEmits(['change']) |
|||
</script> |
|||
@ -0,0 +1,56 @@ |
|||
<template> |
|||
<div class="node-config"> |
|||
<el-divider content-position="left">输出配置</el-divider> |
|||
|
|||
<el-form-item label="输出格式"> |
|||
<el-select v-model="config.format" @change="$emit('change')"> |
|||
<el-option label="纯文本" value="text" /> |
|||
<el-option label="JSON" value="json" /> |
|||
<el-option label="XML" value="xml" /> |
|||
<el-option label="HTML" value="html" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="输出模板"> |
|||
<el-input v-model="config.output_template" type="textarea" :rows="3" @change="$emit('change')" placeholder="可选,自定义输出格式" /> |
|||
</el-form-item> |
|||
|
|||
<el-divider content-position="left">格式化选项</el-divider> |
|||
|
|||
<el-form-item label="JSON缩进"> |
|||
<el-input-number v-model="config.indent" :min="0" :max="4" :step="1" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="字符编码"> |
|||
<el-select v-model="config.encoding" @change="$emit('change')"> |
|||
<el-option label="UTF-8" value="utf-8" /> |
|||
<el-option label="GBK" value="gbk" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-divider content-position="left">输出行为</el-divider> |
|||
|
|||
<el-form-item label="截断长输出"> |
|||
<el-switch v-model="config.truncate" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="最大输出长度"> |
|||
<el-input-number v-model="config.max_length" :min="100" :max="10000" :step="100" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
const config = defineModel<any>({ |
|||
default: () => ({ |
|||
format: 'text', |
|||
output_template: '', |
|||
indent: 2, |
|||
encoding: 'utf-8', |
|||
truncate: false, |
|||
max_length: 2000 |
|||
}) |
|||
}) |
|||
|
|||
defineEmits(['change']) |
|||
</script> |
|||
@ -0,0 +1,56 @@ |
|||
<template> |
|||
<div class="node-config"> |
|||
<el-divider content-position="left">知识库配置</el-divider> |
|||
|
|||
<el-form-item label="知识库"> |
|||
<el-input v-model="config.knowledge_base" placeholder="知识库ID" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="TopK"> |
|||
<el-input-number v-model="config.top_k" :min="1" :max="20" :step="1" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-divider content-position="left">检索配置</el-divider> |
|||
|
|||
<el-form-item label="检索模式"> |
|||
<el-select v-model="config.search_mode" @change="$emit('change')"> |
|||
<el-option label="向量检索" value="vector" /> |
|||
<el-option label="关键词检索" value="keyword" /> |
|||
<el-option label="混合检索" value="hybrid" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="相似度阈值"> |
|||
<el-slider v-model="config.similarity_threshold" :min="0" :max="1" :step="0.05" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-divider content-position="left">结果处理</el-divider> |
|||
|
|||
<el-form-item label="结果排序"> |
|||
<el-select v-model="config.result_sort" @change="$emit('change')"> |
|||
<el-option label="按相似度" value="similarity" /> |
|||
<el-option label="按相关性" value="relevance" /> |
|||
<el-option label="按时间" value="time" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="包含元数据"> |
|||
<el-switch v-model="config.include_metadata" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
const config = defineModel<any>({ |
|||
default: () => ({ |
|||
knowledge_base: '', |
|||
top_k: 5, |
|||
search_mode: 'hybrid', |
|||
similarity_threshold: 0.7, |
|||
result_sort: 'similarity', |
|||
include_metadata: true |
|||
}) |
|||
}) |
|||
|
|||
defineEmits(['change']) |
|||
</script> |
|||
@ -0,0 +1,89 @@ |
|||
<template> |
|||
<div class="node-config"> |
|||
<el-divider content-position="left">工具选择</el-divider> |
|||
|
|||
<el-form-item label="工具类型"> |
|||
<el-select v-model="config.tool_type" @change="$emit('change')" placeholder="选择工具类型"> |
|||
<el-option label="企微消息" value="wecom_message" /> |
|||
<el-option label="任务管理" value="task_management" /> |
|||
<el-option label="文档处理" value="document_processing" /> |
|||
<el-option label="格式处理" value="format_processing" /> |
|||
<el-option label="数据查询" value="data_query" /> |
|||
<el-option label="报表生成" value="report_generation" /> |
|||
<el-option label="HTTP请求" value="http_request" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="工具名称"> |
|||
<el-select v-model="config.tool_name" @change="$emit('change')" placeholder="选择具体工具"> |
|||
<template v-if="config.tool_type === 'wecom_message'"> |
|||
<el-option label="发送企微通知" value="send_notification" /> |
|||
<el-option label="查询企微用户" value="query_wecom_user" /> |
|||
<el-option label="推送任务至企微" value="push_task_to_wecom" /> |
|||
<el-option label="查询下属" value="list_subordinates" /> |
|||
</template> |
|||
<template v-else-if="config.tool_type === 'task_management'"> |
|||
<el-option label="查询任务列表" value="list_tasks" /> |
|||
<el-option label="创建任务" value="create_task" /> |
|||
<el-option label="查询任务详情" value="get_task" /> |
|||
<el-option label="更新任务" value="update_task" /> |
|||
</template> |
|||
<template v-else-if="config.tool_type === 'document_processing'"> |
|||
<el-option label="解析文档" value="parse_document" /> |
|||
</template> |
|||
<template v-else-if="config.tool_type === 'format_processing'"> |
|||
<el-option label="修正格式" value="format_correction" /> |
|||
</template> |
|||
<template v-else-if="config.tool_type === 'data_query'"> |
|||
<el-option label="查询用户数据" value="query_user_data" /> |
|||
<el-option label="查询部门信息" value="query_department_info" /> |
|||
</template> |
|||
<template v-else-if="config.tool_type === 'report_generation'"> |
|||
<el-option label="生成效率报告" value="generate_efficiency_report" /> |
|||
<el-option label="任务统计" value="get_task_statistics" /> |
|||
<el-option label="员工看板" value="get_employee_dashboard" /> |
|||
</template> |
|||
<template v-else-if="config.tool_type === 'http_request'"> |
|||
<el-option label="自定义HTTP请求" value="custom_http_request" /> |
|||
</template> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-divider content-position="left">参数配置</el-divider> |
|||
|
|||
<el-form-item label="参数映射"> |
|||
<el-input v-model="config.param_mapping" type="textarea" :rows="3" @change="$emit('change')" placeholder='{"input_key": "output_key"}' /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="超时时间(秒)"> |
|||
<el-input-number v-model="config.timeout" :min="1" :max="300" :step="10" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="重试次数"> |
|||
<el-input-number v-model="config.retry_count" :min="0" :max="5" :step="1" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="错误处理"> |
|||
<el-select v-model="config.error_handling" @change="$emit('change')"> |
|||
<el-option label="抛出错误" value="throw" /> |
|||
<el-option label="返回默认值" value="default" /> |
|||
<el-option label="跳过继续" value="skip" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
const config = defineModel<any>({ |
|||
default: () => ({ |
|||
tool_type: '', |
|||
tool_name: '', |
|||
param_mapping: '{}', |
|||
timeout: 30, |
|||
retry_count: 0, |
|||
error_handling: 'throw' |
|||
}) |
|||
}) |
|||
|
|||
defineEmits(['change']) |
|||
</script> |
|||
@ -0,0 +1,22 @@ |
|||
<template> |
|||
<div class="node-config"> |
|||
<el-form-item label="事件类型"> |
|||
<el-select v-model="config.event_type" @change="$emit('change')" placeholder="企微触发事件"> |
|||
<el-option label="文本消息" value="text_message" /> |
|||
<el-option label="按钮点击" value="button_click" /> |
|||
<el-option label="进入聊天" value="enter_chat" /> |
|||
<el-option label="图片消息" value="image_message" /> |
|||
<el-option label="语音消息" value="voice_message" /> |
|||
<el-option label="文件消息" value="file_message" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="回调地址"> |
|||
<el-input v-model="config.callback_url" @change="$emit('change')" placeholder="可选,默认使用系统回调" /> |
|||
</el-form-item> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
const config = defineModel<any>({ default: () => ({}) }) |
|||
defineEmits(['change']) |
|||
</script> |
|||
@ -0,0 +1,49 @@ |
|||
<template> |
|||
<div class="node-config"> |
|||
<el-divider content-position="left">消息配置</el-divider> |
|||
|
|||
<el-form-item label="消息模板"> |
|||
<el-input v-model="config.message_template" type="textarea" :rows="3" @change="$emit('change')" placeholder="支持变量: {user_name}, {task_name}, {status}" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="目标用户"> |
|||
<el-input v-model="config.target" placeholder="@all 或用户ID" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="消息类型"> |
|||
<el-select v-model="config.message_type" @change="$emit('change')"> |
|||
<el-option label="文本消息" value="text" /> |
|||
<el-option label="Markdown消息" value="markdown" /> |
|||
<el-option label="卡片消息" value="card" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-divider content-position="left">发送选项</el-divider> |
|||
|
|||
<el-form-item label="异步发送"> |
|||
<el-switch v-model="config.async_send" @change="$emit('change')" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="发送失败处理"> |
|||
<el-select v-model="config.error_handling" @change="$emit('change')"> |
|||
<el-option label="抛出错误" value="throw" /> |
|||
<el-option label="记录日志" value="log" /> |
|||
<el-option label="重试" value="retry" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
const config = defineModel<any>({ |
|||
default: () => ({ |
|||
message_template: '', |
|||
target: '', |
|||
message_type: 'text', |
|||
async_send: false, |
|||
error_handling: 'throw' |
|||
}) |
|||
}) |
|||
|
|||
defineEmits(['change']) |
|||
</script> |
|||
Binary file not shown.
@ -0,0 +1,52 @@ |
|||
events { |
|||
worker_connections 1024; |
|||
} |
|||
|
|||
http { |
|||
include /etc/nginx/mime.types; |
|||
default_type application/octet-stream; |
|||
|
|||
upstream backend_api { |
|||
server backend:8000; |
|||
} |
|||
|
|||
upstream frontend_app { |
|||
server frontend:80; |
|||
} |
|||
|
|||
server { |
|||
listen 80; |
|||
|
|||
location / { |
|||
proxy_pass http://frontend_app; |
|||
proxy_set_header Host $host; |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
|||
|
|||
# WebSocket support for Vite HMR |
|||
proxy_http_version 1.1; |
|||
proxy_set_header Upgrade $http_upgrade; |
|||
proxy_set_header Connection "upgrade"; |
|||
proxy_read_timeout 86400s; |
|||
proxy_send_timeout 86400s; |
|||
} |
|||
|
|||
location /api/ { |
|||
proxy_pass http://backend_api/api/; |
|||
proxy_set_header Host $host; |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
|||
|
|||
proxy_http_version 1.1; |
|||
proxy_set_header Upgrade $http_upgrade; |
|||
proxy_set_header Connection "upgrade"; |
|||
proxy_read_timeout 86400s; |
|||
} |
|||
|
|||
location /wecom/ { |
|||
proxy_pass http://backend_api/wecom/; |
|||
proxy_set_header Host $host; |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
events { |
|||
worker_connections 1024; |
|||
} |
|||
|
|||
http { |
|||
include /etc/nginx/mime.types; |
|||
default_type application/octet-stream; |
|||
|
|||
upstream backend_api { |
|||
server backend:8000; |
|||
} |
|||
|
|||
server { |
|||
listen 80; |
|||
|
|||
root /usr/share/nginx/html; |
|||
index index.html; |
|||
|
|||
# SPA fallback - try file first, then fallback to index.html |
|||
location / { |
|||
try_files $uri $uri/ /index.html; |
|||
} |
|||
|
|||
location /api/ { |
|||
proxy_pass http://backend_api/api/; |
|||
proxy_set_header Host $host; |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
|||
|
|||
proxy_http_version 1.1; |
|||
proxy_set_header Upgrade $http_upgrade; |
|||
proxy_set_header Connection "upgrade"; |
|||
proxy_read_timeout 86400s; |
|||
} |
|||
|
|||
location /wecom/ { |
|||
proxy_pass http://backend_api/wecom/; |
|||
proxy_set_header Host $host; |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
} |
|||
|
|||
# Cache static assets |
|||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { |
|||
expires 1y; |
|||
add_header Cache-Control "public, immutable"; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue