authUser(); $page = max(1, (int) $this->request->get('page', 1)); $limit = min(50, max(1, (int) $this->request->get('limit', 20))); $query = ConversationModel::alias('c') ->leftJoin('ai_models m', 'c.model_id = m.id') ->where('c.user_id', $user['id']) ->whereNull('c.deleted_at') ->field('c.*,m.name as model_name,m.provider as model_provider') ->order(['c.is_pinned' => 'desc', 'c.updated_at' => 'desc']); $total = (clone $query)->count(); $list = $query->page($page, $limit)->select(); return $this->success([ 'list' => $list, 'total' => $total, 'page' => $page, 'limit' => $limit, ]); } public function create() { $user = $this->authUser(); PermissionService::checkConversationLimit($user); $input = $this->request->post(); $modelId = $input['model_id'] ?? null; if ($modelId !== null && $modelId !== '') { $modelId = (int) $modelId; if ($modelId <= 0) { $modelId = null; } } else { $modelId = null; } $conversation = ConversationModel::create([ 'user_id' => $user['id'], 'title' => trim($input['title'] ?? '新对话'), 'model_id' => $modelId, ]); return $this->success($this->findConversation((int) $conversation->id, (int) $user['id']), '创建成功'); } public function show($id) { $user = $this->authUser(); return $this->success($this->findConversation((int) $id, (int) $user['id'])); } public function update($id) { $user = $this->authUser(); $conversation = ConversationModel::where('id', $id) ->where('user_id', $user['id']) ->whereNull('deleted_at') ->find(); if (!$conversation) { return $this->error('会话不存在', 404); } $input = $this->request->put(); $data = []; if (isset($input['title'])) { $data['title'] = trim($input['title']); } if (isset($input['is_pinned'])) { $data['is_pinned'] = (int) $input['is_pinned']; } if (array_key_exists('model_id', $input)) { $mid = $input['model_id']; if ($mid === null || $mid === '') { $data['model_id'] = null; } else { $data['model_id'] = (int) $mid; } } if (empty($data)) { return $this->error('无更新内容'); } $conversation->save($data); return $this->success($this->findConversation((int) $id, (int) $user['id'])); } public function delete($id) { $user = $this->authUser(); $conversation = ConversationModel::where('id', $id) ->where('user_id', $user['id']) ->whereNull('deleted_at') ->find(); if (!$conversation) { return $this->error('会话不存在', 404); } $conversation->save(['deleted_at' => date('Y-m-d H:i:s')]); return $this->success(null, '删除成功'); } public function messages($id) { $user = $this->authUser(); $this->findConversation((int) $id, (int) $user['id']); $rows = Message::where('conversation_id', $id) ->field('id,role,content,content_type,attachments,created_at') ->order('created_at', 'asc') ->select(); $list = []; foreach ($rows as $item) { $attachments = $item->attachments; if (is_string($attachments)) { $attachments = json_decode($attachments, true) ?: []; } if (!is_array($attachments)) { $attachments = []; } // 刷新后恢复未完成的 Comfy 生图任务 $stillPending = false; $pendingJob = ComfyUIService::findPendingJob($attachments); if ($pendingJob && $item->role === 'assistant') { $modelId = (int) ($pendingJob['model_id'] ?? 0); $model = $modelId > 0 ? AiModel::find($modelId) : null; if ($model) { $recovered = ComfyUIService::recoverPendingMessage([ 'id' => (int) $item->id, 'content' => (string) $item->content, 'attachments' => $attachments, ], $model, (int) $user['id']); $item->content = $recovered['content']; $item->attachments = $recovered['attachments']; $attachments = $recovered['attachments']; $stillPending = empty($recovered['finished']); Message::where('id', $item->id)->update([ 'content' => $recovered['content'], 'attachments' => json_encode($recovered['attachments'], JSON_UNESCAPED_UNICODE), ]); } else { $stillPending = true; } } if ($item->role === 'assistant' && trim((string) $item->content) === '') { if (empty($attachments) || !ComfyUIService::hasImageAttachments($attachments)) { continue; } } // 前端只展示真实媒体附件;pending 任务信息已体现在 content 文案中 $visibleAttachments = array_values(array_filter( $attachments, fn ($a) => is_array($a) && ($a['type'] ?? '') !== ComfyUIService::JOB_TYPE )); $list[] = [ 'id' => $item->id, 'role' => $item->role, 'content' => $item->content, 'content_type' => $item->content_type, 'attachments' => $visibleAttachments, 'pending_job' => $stillPending, 'pending_image_count' => $stillPending ? (int) ($pendingJob['image_count'] ?? 0) : 0, 'created_at' => $item->created_at, ]; } return $this->success($list); } private function findConversation(int $id, int $userId): array { $conversation = ConversationModel::alias('c') ->leftJoin('ai_models m', 'c.model_id = m.id') ->where('c.id', $id) ->where('c.user_id', $userId) ->whereNull('c.deleted_at') ->field('c.*,m.name as model_name,m.provider as model_provider') ->find(); if (!$conversation) { throw new HttpResponseException(json([ 'code' => 1, 'message' => '会话不存在', 'data' => null, ], 404)); } return $conversation->toArray(); } }