erp-backend/app/Http/Controllers/AIAssistantController.php
2026-04-01 17:07:04 +08:00

875 lines
29 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers;
use App\Http\Requests\AIChatRequest;
use App\Http\Requests\AITaskRequest;
use App\Models\AIConversation;
use App\Models\AIMessage;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class AIAssistantController extends Controller
{
/**
* AI服务
*/
protected $aiService;
/**
* 构造函数
*/
public function __construct()
{
$this->aiService = new \App\Services\AIService();
}
/**
* AI对话
*/
public function chat(AIChatRequest $request)
{
try {
$message = $request->input('message');
$conversationId = $request->input('conversation_id');
$context = $request->input('context', []);
// 获取或创建对话
if ($conversationId) {
$conversation = AIConversation::where('id', $conversationId)
->where('user_id', auth()->id())
->firstOrFail();
} else {
$conversation = AIConversation::create([
'user_id' => auth()->id(),
'title' => substr($message, 0, 50) . '...',
'model' => $this->config['model'],
'status' => 'active',
]);
}
// 添加上下文到消息
$messages = $this->buildMessages($message, $context, $conversation);
// 调用AI API
$response = $this->callAIAPI($messages);
if (!$response['success']) {
return response()->json([
'code' => 500,
'message' => 'AI服务暂时不可用: ' . $response['error']
], 500);
}
$aiResponse = $response['data'];
// 保存消息记录
$userMessage = AIMessage::create([
'conversation_id' => $conversation->id,
'role' => 'user',
'content' => $message,
'tokens' => $this->estimateTokens($message),
]);
$aiMessage = AIMessage::create([
'conversation_id' => $conversation->id,
'role' => 'assistant',
'content' => $aiResponse,
'tokens' => $this->estimateTokens($aiResponse),
]);
// 更新对话统计
$conversation->increment('message_count', 2);
$conversation->increment('total_tokens', $userMessage->tokens + $aiMessage->tokens);
$conversation->last_message_at = now();
$conversation->save();
return response()->json([
'code' => 200,
'data' => [
'conversation_id' => $conversation->id,
'response' => $aiResponse,
'message_id' => $aiMessage->id,
'tokens_used' => $userMessage->tokens + $aiMessage->tokens,
],
'message' => 'success'
]);
} catch (\Exception $e) {
Log::error('AI对话失败: ' . $e->getMessage());
return response()->json([
'code' => 500,
'message' => 'AI对话失败: ' . $e->getMessage()
], 500);
}
}
/**
* 构建消息数组
*/
private function buildMessages(string $message, array $context, AIConversation $conversation): array
{
$messages = [];
// 系统提示词
$systemPrompt = $this->getSystemPrompt($context);
$messages[] = ['role' => 'system', 'content' => $systemPrompt];
// 添加上下文消息
if (!empty($context)) {
foreach ($context as $ctx) {
if (isset($ctx['role']) && isset($ctx['content'])) {
$messages[] = ['role' => $ctx['role'], 'content' => $ctx['content']];
}
}
}
// 添加历史消息最近10条
$historyMessages = AIMessage::where('conversation_id', $conversation->id)
->orderBy('created_at', 'desc')
->limit(10)
->get()
->reverse();
foreach ($historyMessages as $history) {
$messages[] = ['role' => $history->role, 'content' => $history->content];
}
// 添加当前消息
$messages[] = ['role' => 'user', 'content' => $message];
return $messages;
}
/**
* 获取系统提示词
*/
private function getSystemPrompt(array $context): string
{
$basePrompt = "你是一个专业的ERP系统AI助手专门帮助用户处理企业资源管理相关的问题。";
// 根据上下文添加特定提示
if (isset($context['module'])) {
switch ($context['module']) {
case 'goods':
$basePrompt .= "你现在正在处理商品管理模块的问题。";
break;
case 'orders':
$basePrompt .= "你现在正在处理订单管理模块的问题。";
break;
case 'purchase':
$basePrompt .= "你现在正在处理采购管理模块的问题。";
break;
case 'inventory':
$basePrompt .= "你现在正在处理库存管理模块的问题。";
break;
case 'finance':
$basePrompt .= "你现在正在处理财务管理模块的问题。";
break;
}
}
$basePrompt .= "\n\n请用专业、准确、简洁的语言回答用户的问题。如果涉及具体操作,请提供清晰的步骤说明。";
return $basePrompt;
}
/**
* 调用AI API
*/
private function callAIAPI(array $messages): array
{
try {
// 这里使用模拟响应实际项目中需要配置真实的API密钥
$apiKey = config('services.ai.api_key', '');
if (empty($apiKey)) {
// 模拟AI响应开发环境
return $this->mockAIResponse($messages);
}
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $apiKey,
'Content-Type' => 'application/json',
])->timeout(30)->post($this->config['api_url'], [
'model' => $this->config['model'],
'messages' => $messages,
'max_tokens' => $this->config['max_tokens'],
'temperature' => $this->config['temperature'],
]);
if ($response->successful()) {
$data = $response->json();
$content = $data['choices'][0]['message']['content'] ?? '';
$tokens = $data['usage']['total_tokens'] ?? 0;
return [
'success' => true,
'data' => $content,
'tokens' => $tokens,
];
} else {
Log::error('AI API调用失败: ' . $response->body());
return [
'success' => false,
'error' => 'API调用失败: ' . $response->status(),
];
}
} catch (\Exception $e) {
Log::error('AI API异常: ' . $e->getMessage());
return [
'success' => false,
'error' => $e->getMessage(),
];
}
}
/**
* 模拟AI响应开发环境使用
*/
private function mockAIResponse(array $messages): array
{
$lastMessage = end($messages);
$userMessage = $lastMessage['content'] ?? '';
// 根据用户消息生成模拟响应
$responses = [
'商品' => "关于商品管理,我可以帮你:\n1. 查询商品信息\n2. 添加新商品\n3. 更新商品库存\n4. 设置商品价格\n5. 管理商品分类\n\n请告诉我具体需要什么帮助?",
'订单' => "关于订单管理,我可以帮你:\n1. 查看订单状态\n2. 处理新订单\n3. 发货操作\n4. 订单统计\n5. 退款处理\n\n请提供订单号或具体问题。",
'采购' => "关于采购管理,我可以帮你:\n1. 创建采购单\n2. 供应商管理\n3. 采购审批流程\n4. 收货入库\n5. 采购统计\n\n请告诉我你的具体需求。",
'库存' => "关于库存管理,我可以帮你:\n1. 库存查询\n2. 库存预警\n3. 盘点管理\n4. 出入库记录\n5. 库存调拨\n\n请提供仓库或商品信息。",
'财务' => "关于财务管理,我可以帮你:\n1. 收支记录\n2. 财务报表\n3. 发票管理\n4. 对账处理\n5. 预算控制\n\n请告诉我具体财务问题。",
'default' => "我是ERP系统AI助手可以帮你处理\n• 商品管理\n• 订单处理\n• 采购管理\n• 库存控制\n• 财务统计\n• 系统操作指导\n\n请详细描述你的问题,我会尽力提供帮助。"
];
$response = $responses['default'];
foreach ($responses as $key => $value) {
if (strpos($userMessage, $key) !== false) {
$response = $value;
break;
}
}
// 添加个性化问候
if (strpos(strtolower($userMessage), '你好') !== false ||
strpos(strtolower($userMessage), 'hi') !== false ||
strpos(strtolower($userMessage), 'hello') !== false) {
$response = "你好我是ERP系统AI助手。我可以帮你处理企业资源管理的各种问题包括商品、订单、采购、库存、财务等模块的操作和咨询。有什么可以帮你的吗";
}
return [
'success' => true,
'data' => $response,
'tokens' => $this->estimateTokens($response),
];
}
/**
* 执行AI任务
*/
public function executeTask(AITaskRequest $request)
{
try {
$task = $request->input('task');
$parameters = $request->input('parameters', []);
// 根据任务类型处理
$result = $this->processTask($task, $parameters);
// 记录任务执行
\App\Models\OperationLog::log([
'module' => 'AI助手',
'action' => '执行任务',
'method' => 'POST',
'path' => 'api/ai/tasks/execute',
'request_data' => ['task' => $task, 'parameters' => $parameters],
'response_data' => $result,
'remark' => 'AI任务执行',
]);
return response()->json([
'code' => 200,
'data' => $result,
'message' => '任务执行成功'
]);
} catch (\Exception $e) {
Log::error('AI任务执行失败: ' . $e->getMessage());
return response()->json([
'code' => 500,
'message' => '任务执行失败: ' . $e->getMessage()
], 500);
}
}
/**
* 处理具体任务
*/
private function processTask(string $task, array $parameters): array
{
switch ($task) {
case 'data_analysis':
return $this->analyzeData($parameters);
case 'report_generation':
return $this->generateReport($parameters);
case 'prediction':
return $this->makePrediction($parameters);
case 'recommendation':
return $this->provideRecommendation($parameters);
case 'document_summary':
return $this->summarizeDocument($parameters);
default:
return [
'success' => false,
'error' => '不支持的任务类型',
'task' => $task,
];
}
}
/**
* 数据分析
*/
private function analyzeData(array $parameters): array
{
$type = $parameters['type'] ?? 'sales';
$period = $parameters['period'] ?? 'month';
// 模拟数据分析结果
$analysis = [
'type' => $type,
'period' => $period,
'summary' => "根据{$period}数据,发现以下趋势:",
'insights' => [
'销售额增长15%',
'最畅销商品商品A',
'客户复购率45%',
'库存周转率2.5次',
],
'recommendations' => [
'增加商品A的库存',
'优化营销策略提高复购率',
'关注库存周转率提升',
],
];
return [
'success' => true,
'analysis' => $analysis,
'generated_at' => now()->toDateTimeString(),
];
}
/**
* 生成报告
*/
private function generateReport(array $parameters): array
{
$reportType = $parameters['report_type'] ?? 'sales';
$startDate = $parameters['start_date'] ?? now()->subMonth()->toDateString();
$endDate = $parameters['end_date'] ?? now()->toDateString();
// 模拟报告生成
$report = [
'type' => $reportType,
'period' => $startDate . ' 至 ' . $endDate,
'title' => ucfirst($reportType) . '分析报告',
'sections' => [
'executive_summary' => '报告摘要内容...',
'data_analysis' => '数据分析内容...',
'key_findings' => '主要发现...',
'recommendations' => '建议措施...',
],
'metrics' => [
'total_sales' => '¥125,000',
'order_count' => 156,
'average_order_value' => '¥801',
'growth_rate' => '15%',
],
];
return [
'success' => true,
'report' => $report,
'download_url' => '/api/reports/download/' . uniqid(),
'generated_at' => now()->toDateTimeString(),
];
}
/**
* 预测分析
*/
private function makePrediction(array $parameters): array
{
$target = $parameters['target'] ?? 'sales';
$periods = $parameters['periods'] ?? 3;
// 模拟预测结果
$predictions = [];
$current = 100000; // 基准值
for ($i = 1; $i <= $periods; $i++) {
$growth = rand(5, 15) / 100; // 5-15%增长
$predicted = $current * (1 + $growth);
$predictions[] = [
'period' => "{$i}",
'value' => number_format($predicted, 2),
'growth' => number_format($growth * 100, 1) . '%',
'confidence' => rand(70, 95) . '%',
];
$current = $predicted;
}
return [
'success' => true,
'target' => $target,
'predictions' => $predictions,
'notes' => '基于历史数据的趋势预测,实际结果可能因市场变化而有所不同。',
];
}
/**
* 提供建议
*/
private function provideRecommendation(array $parameters): array
{
$area = $parameters['area'] ?? 'inventory';
$recommendations = [
'inventory' => [
'优化库存水平,减少积压',
'建立安全库存机制',
'实施ABC分类管理',
'定期盘点,确保账实相符',
],
'sales' => [
'加强客户关系管理',
'优化产品定价策略',
'拓展销售渠道',
'提升客户服务质量',
],
'purchase' => [
'建立供应商评估体系',
'优化采购审批流程',
'实施集中采购降低成本',
'加强采购合同管理',
],
'finance' => [
'加强现金流管理',
'优化成本控制',
'完善财务报告体系',
'加强预算执行监控',
],
];
$areaRecommendations = $recommendations[$area] ?? $recommendations['inventory'];
return [
'success' => true,
'area' => $area,
'recommendations' => $areaRecommendations,
'priority' => '根据当前业务状况,建议优先实施前两项。',
];
}
/**
* 文档摘要
*/
private function summarizeDocument(array $parameters): array
{
$content = $parameters['content'] ?? '';
$maxLength = $parameters['max_length'] ?? 200;
if (empty($content)) {
return [
'success' => false,
'error' => '文档内容不能为空',
];
}
// 简单摘要算法实际应使用AI
$sentences = preg_split('/[。!?.!?]/', $content);
$sentences = array_filter($sentences, function($sentence) {
return strlen(trim($sentence)) > 10;
});
$summary = '';
$count = 0;
foreach ($sentences as $sentence) {
if (strlen($summary) + strlen($sentence) < $maxLength) {
$summary .= trim($sentence) . '。';
$count++;
}
if ($count >= 3) {
break;
}
}
if (empty($summary)) {
$summary = substr($content, 0, $maxLength) . '...';
}
$keywords = $this->extractKeywords($content);
return [
'success' => true,
'original_length' => strlen($content),
'summary_length' => strlen($summary),
'summary' => $summary,
'keywords' => $keywords,
'compression_rate' => round((1 - strlen($summary) / strlen($content)) * 100, 1) . '%',
];
}
/**
* 提取关键词
*/
private function extractKeywords(string $content): array
{
// 简单关键词提取(实际应使用更复杂的算法)
$words = preg_split('/\s+/', $content);
$wordCount = array_count_values($words);
arsort($wordCount);
$keywords = array_slice(array_keys($wordCount), 0, 10);
// 过滤常见词
$commonWords = ['的', '了', '在', '是', '和', '与', '或', '等', '这个', '那个'];
$keywords = array_diff($keywords, $commonWords);
return array_values($keywords);
}
/**
* 估算Token数量
*/
private function estimateTokens(string $text): int
{
// 简单估算英文约4字符=1token中文约1.5字符=1token
$chineseChars = preg_match_all('/[\x{4e00}-\x{9fa5}]/u', $text);
$otherChars = strlen($text) - $chineseChars * 3; // 中文字符占3字节
$tokens = ceil($chineseChars / 1.5 + $otherChars / 4);
return max(1, $tokens);
}
/**
* 获取对话列表
*/
public function getConversations(Request $request)
{
$query = AIConversation::where('user_id', auth()->id())
->orderBy('last_message_at', 'desc');
if ($request->filled('status')) {
$query->where('status', $request->status);
}
if ($request->filled('keyword')) {
$keyword = $request->keyword;
$query->where(function ($q) use ($keyword) {
$q->where('title', 'like', "%{$keyword}%")
->orWhereHas('messages', function ($q) use ($keyword) {
$q->where('content', 'like', "%{$keyword}%");
});
});
}
$perPage = $request->input('limit', 20);
$conversations = $query->paginate($perPage);
// 加载最后一条消息
$conversations->load(['lastMessage']);
return response()->json([
'code' => 200,
'data' => [
'list' => $conversations->items(),
'total' => $conversations->total(),
'current_page' => $conversations->currentPage(),
'last_page' => $conversations->lastPage(),
],
'message' => 'success'
]);
}
/**
* 获取对话详情
*/
public function getConversation(string $id)
{
$conversation = AIConversation::with(['messages' => function ($query) {
$query->orderBy('created_at', 'asc');
}])->where('id', $id)
->where('user_id', auth()->id())
->firstOrFail();
return response()->json([
'code' => 200,
'data' => $conversation,
'message' => 'success'
]);
}
/**
* 删除对话
*/
public function deleteConversation(string $id)
{
$conversation = AIConversation::where('id', $id)
->where('user_id', auth()->id())
->firstOrFail();
try {
// 删除相关消息
AIMessage::where('conversation_id', $id)->delete();
// 删除对话
$conversation->delete();
return response()->json([
'code' => 200,
'message' => '对话删除成功'
]);
} catch (\Exception $e) {
Log::error('删除对话失败: ' . $e->getMessage());
return response()->json([
'code' => 500,
'message' => '删除失败: ' . $e->getMessage()
], 500);
}
}
/**
* 清空对话消息
*/
public function clearConversation(string $id)
{
$conversation = AIConversation::where('id', $id)
->where('user_id', auth()->id())
->firstOrFail();
try {
// 删除所有消息
AIMessage::where('conversation_id', $id)->delete();
// 重置对话统计
$conversation->update([
'message_count' => 0,
'total_tokens' => 0,
'last_message_at' => null,
]);
return response()->json([
'code' => 200,
'message' => '对话消息已清空'
]);
} catch (\Exception $e) {
Log::error('清空对话失败: ' . $e->getMessage());
return response()->json([
'code' => 500,
'message' => '清空失败: ' . $e->getMessage()
], 500);
}
}
/**
* 获取AI能力列表
*/
public function getCapabilities()
{
$capabilities = [
'chat' => [
'name' => '智能对话',
'description' => '回答ERP系统相关问题提供操作指导',
'examples' => [
'如何创建采购单?',
'查看商品库存',
'订单处理流程',
],
],
'data_analysis' => [
'name' => '数据分析',
'description' => '分析销售、库存、财务等数据',
'examples' => [
'分析本月销售趋势',
'库存周转率分析',
'客户购买行为分析',
],
],
'report_generation' => [
'name' => '报告生成',
'description' => '自动生成各类业务报告',
'examples' => [
'生成销售日报',
'制作库存月报',
'财务季度分析报告',
],
],
'prediction' => [
'name' => '趋势预测',
'description' => '基于历史数据预测未来趋势',
'examples' => [
'预测下月销售额',
'库存需求预测',
'客户增长预测',
],
],
'recommendation' => [
'name' => '智能建议',
'description' => '提供业务优化建议',
'examples' => [
'库存优化建议',
'销售策略建议',
'成本控制建议',
],
],
'document_summary' => [
'name' => '文档摘要',
'description' => '自动提取文档关键信息',
'examples' => [
'合同摘要',
'会议纪要整理',
'报告要点提取',
],
],
];
return response()->json([
'code' => 200,
'data' => $capabilities,
'message' => 'success'
]);
}
/**
* 获取使用统计
*/
public function getUsageStatistics(Request $request)
{
$userId = auth()->id();
// 今日使用统计
$todayStart = now()->startOfDay();
$todayEnd = now()->endOfDay();
$todayConversations = AIConversation::where('user_id', $userId)
->whereBetween('created_at', [$todayStart, $todayEnd])
->count();
$todayMessages = AIMessage::whereHas('conversation', function ($query) use ($userId) {
$query->where('user_id', $userId);
})->whereBetween('created_at', [$todayStart, $todayEnd])
->count();
$todayTokens = AIMessage::whereHas('conversation', function ($query) use ($userId) {
$query->where('user_id', $userId);
})->whereBetween('created_at', [$todayStart, $todayEnd])
->sum('tokens');
// 月度统计
$monthStart = now()->startOfMonth();
$monthEnd = now()->endOfMonth();
$monthConversations = AIConversation::where('user_id', $userId)
->whereBetween('created_at', [$monthStart, $monthEnd])
->count();
$monthMessages = AIMessage::whereHas('conversation', function ($query) use ($userId) {
$query->where('user_id', $userId);
})->whereBetween('created_at', [$monthStart, $monthEnd])
->count();
$monthTokens = AIMessage::whereHas('conversation', function ($query) use ($userId) {
$query->where('user_id', $userId);
})->whereBetween('created_at', [$monthStart, $monthEnd])
->sum('tokens');
// 总体统计
$totalConversations = AIConversation::where('user_id', $userId)->count();
$totalMessages = AIMessage::whereHas('conversation', function ($query) use ($userId) {
$query->where('user_id', $userId);
})->count();
$totalTokens = AIMessage::whereHas('conversation', function ($query) use ($userId) {
$query->where('user_id', $userId);
})->sum('tokens');
$statistics = [
'today' => [
'conversations' => $todayConversations,
'messages' => $todayMessages,
'tokens' => $todayTokens,
],
'month' => [
'conversations' => $monthConversations,
'messages' => $monthMessages,
'tokens' => $monthTokens,
],
'total' => [
'conversations' => $totalConversations,
'messages' => $totalMessages,
'tokens' => $totalTokens,
],
];
return response()->json([
'code' => 200,
'data' => $statistics,
'message' => 'success'
]);
}
/**
* 测试AI连接
*/
public function testConnection()
{
try {
$testMessage = '你好,请回复"AI服务正常"以确认连接成功。';
$messages = [
['role' => 'system', 'content' => '你是一个测试助手,只需要按照要求回复。'],
['role' => 'user', 'content' => $testMessage],
];
$response = $this->callAIAPI($messages);
if ($response['success']) {
return response()->json([
'code' => 200,
'data' => [
'status' => 'connected',
'response' => $response['data'],
'response_time' => '正常',
],
'message' => 'AI服务连接正常'
]);
} else {
return response()->json([
'code' => 503,
'data' => [
'status' => 'disconnected',
'error' => $response['error'],
],
'message' => 'AI服务连接失败'
], 503);
}
} catch (\Exception $e) {
return response()->json([
'code' => 500,
'data' => [
'status' => 'error',
'error' => $e->getMessage(),
],
'message' => '测试过程中发生错误'
], 500);
}
}
}