attributes->get('terminal'); $terminal->load('prompt'); // 返回原始提示词模板(占位符由HMI端替换) $systemPrompt = $terminal->prompt?->prompt_template ?? ''; // 获取终端关联的已发布指引数量 $guideCount = $terminal->guides()->published()->count(); return response()->json([ 'terminal' => [ 'id' => $terminal->id, 'name' => $terminal->name, 'code' => $terminal->code, 'station_id' => $terminal->station_id, 'diagram_url' => $terminal->diagram_url, 'scada_data_url' => $terminal->scada_data_url, 'scada_tags_url' => $terminal->scada_tags_url, 'voice_wakeup_enabled' => $terminal->voice_wakeup_enabled, 'voice_wakeup_word' => $terminal->voice_wakeup_word, ], 'system_prompt' => $systemPrompt, 'guide_count' => $guideCount, ]); } /** * GET /api/knowledge?query=xxx * RAG知识搜索(由AI tool_call触发) */ public function knowledge(Request $request): JsonResponse { $request->validate([ 'query' => 'required|string|max:500', ]); $result = $this->knowledgeService->search($request->input('query')); return response()->json($result); } /** * GET /api/terminal/guides?category=operation * 已发布的指引列表 */ public function guides(Request $request): JsonResponse { $terminal = $request->attributes->get('terminal'); $query = $terminal->guides()->published()->withCount('pages'); if ($category = $request->input('category')) { $query->where('category', $category); } $guides = $query->orderBy('name')->get()->map(fn(Guide $guide) => [ 'id' => $guide->id, 'name' => $guide->name, 'description' => $guide->description, 'category' => $guide->category, 'tags' => $guide->tags, 'page_count' => $guide->pages_count, ]); return response()->json(['guides' => $guides]); } /** * POST /api/terminal/guides/pages * 组合多个指引的页面,返回递归树形结构 */ public function guidePages(Request $request): JsonResponse { $request->validate([ 'guide_ids' => 'required|array|min:1', 'guide_ids.*' => 'integer|exists:guides,id', ]); $terminal = $request->attributes->get('terminal'); $accessibleIds = $terminal->guides()->published()->pluck('guides.id')->toArray(); $guideIds = $request->input('guide_ids'); $pages = []; foreach ($guideIds as $guideId) { if (!in_array($guideId, $accessibleIds)) { continue; } $guide = Guide::with( $this->buildEagerLoadArray('trunkPages', 5) )->find($guideId); if (!$guide) { continue; } foreach ($guide->trunkPages as $page) { $pages = array_merge($pages, $this->flattenSequentialPages($page, $guide->name, $guide->id)); } } return response()->json([ 'pages' => $pages, 'total_pages' => count($pages), ]); } /** * 将树形页面结构展平:顺序节点(无 options)平铺,分支节点保留嵌套 */ private function flattenSequentialPages(GuidePage $page, string $guideName, int $guideId): array { $data = [ 'id' => $page->id, 'guide_id' => $guideId, 'guide_name' => $guideName, 'page_number' => $page->page_number, 'title' => $page->title, 'html_url' => $page->html_url, ]; if ($page->options && $page->branchChildren->isNotEmpty()) { $data['options'] = $page->options; $branches = []; foreach ($page->branchChildren as $child) { $branches[$child->branch_option][] = $this->buildPageTree($child, $guideName, $guideId); } $data['branches'] = $branches; return [$data]; } if ($page->branchChildren->isNotEmpty()) { $result = [$data]; foreach ($page->branchChildren as $child) { $result = array_merge($result, $this->flattenSequentialPages($child, $guideName, $guideId)); } return $result; } return [$data]; } private function buildPageTree(GuidePage $page, string $guideName, int $guideId): array { $data = [ 'id' => $page->id, 'guide_id' => $guideId, 'guide_name' => $guideName, 'page_number' => $page->page_number, 'title' => $page->title, 'html_url' => $page->html_url, ]; if ($page->options && $page->branchChildren->isNotEmpty()) { $data['options'] = $page->options; $branches = []; foreach ($page->branchChildren as $child) { $branches[$child->branch_option][] = $this->buildPageTree($child, $guideName, $guideId); } $data['branches'] = $branches; } return $data; } private function buildEagerLoadArray(string $base, int $depth): array { $loads = [$base => fn($q) => $q->orderBy('sort_order')]; $current = $base; for ($i = 0; $i < $depth; $i++) { $current .= '.branchChildren'; $loads[$current] = fn($q) => $q->orderBy('sort_order'); } return $loads; } /** * POST /api/terminal/heartbeat * 终端心跳上报 */ public function heartbeat(Request $request): JsonResponse { $terminal = $request->attributes->get('terminal'); $terminal->update([ 'is_online' => true, 'last_online_at' => now(), ]); return response()->json(['status' => 'ok']); } }