request->param('force', 0)) === 1; $delay = max(0, intval($this->request->param('delay', 1))); $svc = new ExpertFieldAiService(); $started = $svc->startChain($force, $delay); return jsonSuccess([ 'started' => $started, 'queue' => ExpertFieldAiService::QUEUE_NAME, 'force' => $force, 'msg' => $started ? 'chain enqueued' : 'no pending experts', ]); } /** 兼容旧接口名 */ public function startLinkChain() { return $this->startChain(); } /** * 同步处理单个 expert(关联 + AI) */ public function processOne() { $expertId = intval($this->request->param('expert_id', 0)); $force = intval($this->request->param('force', 0)) === 1; if ($expertId <= 0) { return jsonError('expert_id required'); } $svc = new ExpertFieldAiService(); $result = $svc->processExpert($expertId, $force); if (empty($result['ok'])) { return jsonError(isset($result['error']) ? $result['error'] : 'failed'); } return jsonSuccess($result); } /** * 同步批量处理 * expert_ids 逗号分隔,或 limit 扫描待处理前 N 条 */ public function processBatch() { $force = intval($this->request->param('force', 0)) === 1; $ids = $this->resolveExpertIds(); if (empty($ids)) { return jsonError('expert_ids 或 limit 必填'); } $svc = new ExpertFieldAiService(); return jsonSuccess($svc->batchProcess($ids, $force)); } /** * 仅 user 关联(不 AI) */ public function linkOne() { $expertId = intval($this->request->param('expert_id', 0)); $force = intval($this->request->param('force', 0)) === 1; if ($expertId <= 0) { return jsonError('expert_id required'); } $svc = new ExpertFieldAiService(); $result = $svc->linkFromUser($expertId, $force); if (empty($result['ok'])) { return jsonError(isset($result['error']) ? $result['error'] : 'failed'); } return jsonSuccess($result); } public function linkBatch() { $force = intval($this->request->param('force', 0)) === 1; $ids = $this->resolveExpertIds(true); if (empty($ids)) { return jsonError('expert_ids 或 limit 必填'); } $svc = new ExpertFieldAiService(); return jsonSuccess($svc->batchLinkFromUser($ids, $force)); } public function syncByUser() { $userId = intval($this->request->param('user_id', 0)); $force = intval($this->request->param('force', 0)) === 1; if ($userId <= 0) { return jsonError('user_id required'); } $svc = new ExpertFieldAiService(); $result = $svc->syncExpertsByUserId($userId, $force); if (empty($result['ok'])) { return jsonError(isset($result['error']) ? $result['error'] : 'failed'); } return jsonSuccess($result); } /** * 预览:是否可 user 关联、是否可 AI、上下文摘要 */ public function preview() { $expertId = intval($this->request->param('expert_id', 0)); if ($expertId <= 0) { return jsonError('expert_id required'); } $svc = new ExpertFieldAiService(); $result = $svc->preview($expertId); if (empty($result['ok'])) { return jsonError(isset($result['error']) ? $result['error'] : 'failed'); } $result['field_ai_status_text'] = $svc->statusLabel(intval($result['expert_field_ai_status'])); return jsonSuccess($result); } public function statistics() { $base = Db::name('expert')->where('state', '<>', 5); $total = (clone $base)->count(); $done = (clone $base)->where('field_ai_status', ExpertFieldAiService::STATUS_DONE)->count(); $userLink = (clone $base)->where('field_ai_source', ExpertFieldAiService::SOURCE_USER_LINK)->count(); $aiDone = (clone $base)->where('field_ai_source', ExpertFieldAiService::SOURCE_AI)->count(); $pending = (clone $base)->where('field_ai_status', ExpertFieldAiService::STATUS_PENDING)->count(); $noUserLink = (clone $base)->where('field_ai_status', ExpertFieldAiService::STATUS_NO_USER_LINK)->count(); $insufficient = (clone $base)->where('field_ai_status', ExpertFieldAiService::STATUS_INSUFFICIENT)->count(); $failed = (clone $base)->where('field_ai_status', ExpertFieldAiService::STATUS_FAILED)->count(); return jsonSuccess([ 'total' => $total, 'done' => $done, 'user_link' => $userLink, 'ai_done' => $aiDone, 'pending' => $pending, 'no_user_link' => $noUserLink, 'insufficient' => $insufficient, 'failed' => $failed, 'coverage_rate' => $total > 0 ? round($done / $total * 100, 2) . '%' : '0%', ]); } private function resolveExpertIds($linkOnly = false) { $idsRaw = trim((string)$this->request->param('expert_ids', '')); $limit = min(max(intval($this->request->param('limit', 0)), 0), 200); if ($idsRaw !== '') { return array_filter(array_map('intval', explode(',', $idsRaw))); } if ($limit <= 0) { return []; } $query = Db::name('expert')->where('state', '<>', 5); if ($linkOnly) { $query->where(function ($q) { $q->where('field_ai_status', ExpertFieldAiService::STATUS_PENDING) ->whereOr('field_ai_status', ExpertFieldAiService::STATUS_FAILED); }); } else { $query->where(function ($q) { $q->where('field_ai_status', ExpertFieldAiService::STATUS_PENDING) ->whereOr('field_ai_status', ExpertFieldAiService::STATUS_FAILED) ->whereOr('field_ai_status', ExpertFieldAiService::STATUS_NO_USER_LINK); }); } return $query->order('expert_id asc')->limit($limit)->column('expert_id'); } }