feat: implement ERP AI Assistant Phase 1

Backend (FastAPI + SQLAlchemy + Claude API + RAG):
- Config management with Pydantic v2
- Database engine with connection pooling and SQL injection prevention
- AI engine with Claude API integration (support custom base URL)
- RAG engine with ChromaDB and sentence-transformers
- Requirement analysis service
- Config generation service
- Executor engine with SQL validation
- REST API endpoints: /analyze, /generate, /execute

Frontend (Vue 3 + Element Plus + Pinia):
- Complete 3-step workflow: analyze → generate → execute
- Step indicator with progress visualization
- Analysis result display with field table
- SQL preview with monospace font
- Execute confirmation dialog with safety warning
- Execution result display
- State management with Pinia
- API service integration

Security:
- SQL injection prevention with parameterized queries
- Dangerous SQL operation blocking
- Database password URL encoding
- Transaction auto-rollback
- Pydantic config validation

Features:
- Natural language requirement analysis
- Automated SQL configuration generation
- Safe execution with human review
- LAN access support
- Custom Claude API endpoint support

Documentation:
- README with quick start guide
- Quick start guide
- LAN access configuration
- Dependency fixes guide
- Claude API configuration
- Git operation guide
- Implementation report

Dependencies fixed:
- numpy<2.0.0 for chromadb compatibility
- sentence-transformers==2.7.0 for huggingface_hub compatibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 14:23:20 +00:00
commit acd73431ae
60 changed files with 11284 additions and 0 deletions

View File

@@ -0,0 +1,380 @@
<template>
<div class="create-function">
<!-- 步骤指示器 -->
<el-steps :active="currentStep" finish-status="success" simple class="steps">
<el-step title="需求分析" />
<el-step title="配置生成" />
<el-step title="执行配置" />
</el-steps>
<!-- 步骤 1: 需求输入 -->
<el-card v-if="currentStep === 0" class="section-card">
<template #header>
<div class="card-header">
<span>步骤 1: 输入需求</span>
</div>
</template>
<el-form :model="form" label-width="120px">
<el-form-item label="需求描述">
<el-input
v-model="form.requirement"
type="textarea"
:rows="6"
placeholder="请输入功能需求,例如:创建一个销售订单管理页面,包含订单号、客户、订单日期、金额等字段"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="handleAnalyze"
:loading="functionStore.loading.analyzing"
:disabled="!form.requirement.trim()"
>
开始分析需求
</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 步骤 2: 分析结果 -->
<el-card v-if="functionStore.analysisResult" class="section-card">
<template #header>
<div class="card-header">
<span>需求分析结果</span>
<el-tag type="success">已完成</el-tag>
</div>
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="功能名称">
{{ functionStore.analysisResult.功能名称 || '-' }}
</el-descriptions-item>
<el-descriptions-item label="功能类型">
{{ functionStore.analysisResult.功能类型 || '-' }}
</el-descriptions-item>
<el-descriptions-item label="功能号建议">
{{ functionStore.analysisResult.功能号建议 || '-' }}
</el-descriptions-item>
<el-descriptions-item label="窗体类型">
{{ functionStore.analysisResult.窗体类型 || '-' }}
</el-descriptions-item>
</el-descriptions>
<div v-if="functionStore.analysisResult.主表字段" class="field-list">
<h4>主表字段</h4>
<el-table :data="functionStore.analysisResult.主表字段" border size="small">
<el-table-column prop="字段名" label="字段名" />
<el-table-column prop="字段类型" label="字段类型" />
<el-table-column prop="必填" label="必填">
<template #default="{ row }">
<el-tag :type="row.必填 ? 'danger' : 'info'" size="small">
{{ row.必填 ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
</el-table>
</div>
<div class="button-group">
<el-button @click="resetAll">重新分析</el-button>
<el-button
type="primary"
@click="handleGenerate"
:loading="functionStore.loading.generating"
>
生成配置方案
</el-button>
</div>
</el-card>
<!-- 步骤 3: 配置方案 -->
<el-card v-if="functionStore.configResult" class="section-card">
<template #header>
<div class="card-header">
<span>配置方案</span>
<el-tag type="success">已生成</el-tag>
</div>
</template>
<el-alert
title="请仔细检查以下 SQL 配置,确认无误后执行"
type="warning"
:closable="false"
show-icon
class="alert-box"
/>
<div v-if="functionStore.configResult.配置方案" class="config-section">
<h4>SQL 配置语句</h4>
<el-input
v-model="sqlDisplay"
type="textarea"
:rows="15"
readonly
class="sql-textarea"
/>
</div>
<div class="button-group">
<el-button @click="backToAnalysis">返回修改</el-button>
<el-button
type="danger"
@click="showExecuteConfirm"
:loading="functionStore.loading.executing"
>
确认并执行
</el-button>
</div>
</el-card>
<!-- 执行结果 -->
<el-card v-if="functionStore.executeResult" class="section-card">
<template #header>
<div class="card-header">
<span>执行结果</span>
<el-tag :type="functionStore.executeResult.status === 'success' ? 'success' : 'danger'">
{{ functionStore.executeResult.status === 'success' ? '执行成功' : '执行失败' }}
</el-tag>
</div>
</template>
<el-result
:icon="functionStore.executeResult.status === 'success' ? 'success' : 'error'"
:title="functionStore.executeResult.message"
>
<template #extra>
<el-button type="primary" @click="resetAll">创建新功能</el-button>
</template>
</el-result>
</el-card>
<!-- 执行确认对话框 -->
<el-dialog
v-model="executeDialogVisible"
title="确认执行"
width="500px"
>
<el-alert
type="warning"
:closable="false"
show-icon
>
<template #title>
<strong>警告此操作将直接修改数据库</strong>
</template>
<div style="margin-top: 10px;">
<p> 请确认已仔细检查所有 SQL 语句</p>
<p> 建议在生产环境执行前先备份数据库</p>
<p> 执行后可通过历史记录查看详情</p>
</div>
</el-alert>
<template #footer>
<el-button @click="executeDialogVisible = false">取消</el-button>
<el-button type="danger" @click="handleExecute">
确认执行
</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { useFunctionStore } from '@/stores/function'
import { analyzeRequirement, generateConfig, executeConfig } from '@/api'
const functionStore = useFunctionStore()
const form = ref({
requirement: ''
})
const executeDialogVisible = ref(false)
// 当前步骤
const currentStep = computed(() => {
if (functionStore.executeResult) return 3
if (functionStore.configResult) return 2
if (functionStore.analysisResult) return 1
return 0
})
// SQL 显示
const sqlDisplay = computed(() => {
if (!functionStore.configResult?.配置方案?.sql_list) return ''
return functionStore.configResult.配置方案.sql_list.join('\n\n')
})
// 分析需求
const handleAnalyze = async () => {
if (!form.value.requirement.trim()) {
ElMessage.warning('请输入需求描述')
return
}
functionStore.loading.analyzing = true
try {
const result = await analyzeRequirement({
content: form.value.requirement,
session_id: null
})
functionStore.setSession(result.session_id)
functionStore.setAnalysisResult(result.data)
ElMessage.success('需求分析成功')
} catch (error) {
console.error('Analysis failed:', error)
ElMessage.error(
'需求分析失败: ' + (error.response?.data?.detail?.message || error.message)
)
} finally {
functionStore.loading.analyzing = false
}
}
// 生成配置
const handleGenerate = async () => {
if (!functionStore.currentSession || !functionStore.analysisResult) {
ElMessage.warning('请先完成需求分析')
return
}
functionStore.loading.generating = true
try {
const result = await generateConfig({
session_id: functionStore.currentSession,
requirements: functionStore.analysisResult
})
functionStore.setConfigResult(result.data)
ElMessage.success('配置生成成功')
} catch (error) {
console.error('Generation failed:', error)
ElMessage.error(
'配置生成失败: ' + (error.response?.data?.detail?.message || error.message)
)
} finally {
functionStore.loading.generating = false
}
}
// 显示执行确认对话框
const showExecuteConfirm = () => {
executeDialogVisible.value = true
}
// 执行配置
const handleExecute = async () => {
if (!functionStore.currentSession) {
ElMessage.warning('会话信息丢失,请重新开始')
return
}
executeDialogVisible.value = false
functionStore.loading.executing = true
try {
const result = await executeConfig({
session_id: functionStore.currentSession,
confirmed: true,
backup_enabled: true
})
functionStore.setExecuteResult(result)
if (result.status === 'success') {
ElMessage.success('配置执行成功')
} else {
ElMessage.error('配置执行失败')
}
} catch (error) {
console.error('Execution failed:', error)
ElMessage.error(
'配置执行失败: ' + (error.response?.data?.detail?.message || error.message)
)
} finally {
functionStore.loading.executing = false
}
}
// 返回分析步骤
const backToAnalysis = () => {
functionStore.setConfigResult(null)
}
// 重置所有状态
const resetAll = () => {
functionStore.reset()
form.value.requirement = ''
}
</script>
<style scoped>
.create-function {
max-width: 1200px;
margin: 0 auto;
}
.steps {
margin-bottom: 20px;
}
.section-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
font-weight: bold;
}
.field-list {
margin-top: 20px;
}
.field-list h4 {
margin-bottom: 10px;
color: #606266;
}
.alert-box {
margin-bottom: 20px;
}
.config-section {
margin-top: 20px;
}
.config-section h4 {
margin-bottom: 10px;
color: #606266;
}
.sql-textarea {
font-family: 'Courier New', monospace;
}
.sql-textarea :deep(textarea) {
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
}
.button-group {
display: flex;
gap: 10px;
margin-top: 20px;
justify-content: flex-end;
}
</style>