Files
KnowledgeBase/app/Http/Controllers/Api/TerminalApiController.php

228 lines
7.0 KiB
PHP
Raw 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\Api;
use App\Http\Controllers\Controller;
use App\Models\Guide;
use App\Models\GuidePage;
use App\Services\KnowledgeContextService;
use App\Services\PromptTemplateService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class TerminalApiController extends Controller
{
public function __construct(
private PromptTemplateService $promptService,
private KnowledgeContextService $knowledgeService,
) {}
/**
* GET /api/terminal/config
* 返回终端配置含渲染后的system prompt
*/
public function config(Request $request): JsonResponse
{
$terminal = $request->attributes->get('terminal');
$terminal->load(['prompt', 'knowledgeBases']);
// 渲染system prompt
$systemPrompt = '';
if ($terminal->prompt && $terminal->prompt->prompt_template) {
$systemPrompt = $this->promptService->replaceVariables(
$terminal->prompt->prompt_template,
$terminal
);
}
// 获取终端关联的已发布指引数量
$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,
'display_config' => $terminal->display_config,
],
'system_prompt' => $systemPrompt,
'guide_count' => $guideCount,
]);
}
/**
* GET /api/terminal/knowledge?query=xxx
* RAG知识搜索由AI tool_call触发
*/
public function knowledge(Request $request): JsonResponse
{
$request->validate([
'query' => 'required|string|max:500',
]);
$terminal = $request->attributes->get('terminal');
$terminal->load('knowledgeBases');
$result = $this->knowledgeService->search($terminal, $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']);
}
}