diff --git a/app/Filament/Actions/PreviewPromptAction.php b/app/Filament/Actions/PreviewPromptAction.php deleted file mode 100644 index 32d1031..0000000 --- a/app/Filament/Actions/PreviewPromptAction.php +++ /dev/null @@ -1,91 +0,0 @@ -label('预览提示词') - ->icon('heroicon-o-eye') - ->color('info') - ->modalHeading('提示词预览') - ->modalDescription('查看变量替换后的实际提示词内容') - ->modalWidth(MaxWidth::FourExtraLarge) - ->modalSubmitAction(false) - ->modalCancelActionLabel('关闭') - ->form(function ($record, $livewire) { - $service = app(PromptTemplateService::class); - - // 获取当前表单数据 - $data = $livewire->data ?? []; - $promptTemplate = $data['prompt']['prompt_template'] ?? ''; - - if (empty($promptTemplate)) { - return [ - Placeholder::make('empty') - ->label('') - ->content('请先输入提示词模板内容'), - ]; - } - - // 如果是编辑模式,使用记录的终端信息 - // 如果是创建模式,使用表单数据创建临时终端对象 - if ($record) { - $terminal = $record; - } else { - // 创建临时终端对象用于预览 - $terminal = new \App\Models\Terminal([ - 'name' => $data['name'] ?? '新终端', - 'code' => $data['code'] ?? 'TEMP-001', - 'station_id' => $data['station_id'] ?? null, - ]); - } - - // 替换变量 - $previewContent = $service->replaceVariables($promptTemplate, $terminal); - - // 验证变量 - $invalidVariables = $service->validateVariables($promptTemplate); - - return [ - Placeholder::make('original') - ->label('原始模板') - ->content(function () use ($promptTemplate) { - return view('filament.components.prompt-preview-original', [ - 'content' => $promptTemplate, - ]); - }), - - Placeholder::make('preview') - ->label('预览结果') - ->content(function () use ($previewContent) { - return view('filament.components.prompt-preview-result', [ - 'content' => $previewContent, - ]); - }), - - Placeholder::make('validation') - ->label('变量验证') - ->content(function () use ($invalidVariables) { - return view('filament.components.prompt-preview-validation', [ - 'invalidVariables' => $invalidVariables, - ]); - }) - ->visible(fn () => !empty($invalidVariables)), - ]; - }); - } -} diff --git a/app/Filament/Resources/TerminalResource.php b/app/Filament/Resources/TerminalResource.php index 10164b8..a6a57c5 100644 --- a/app/Filament/Resources/TerminalResource.php +++ b/app/Filament/Resources/TerminalResource.php @@ -131,44 +131,6 @@ class TerminalResource extends Resource ->columns(2) ->description('配置终端的语音唤醒能力'), - Forms\Components\Section::make('知识库关联') - ->schema([ - Forms\Components\Repeater::make('knowledgeBaseAssociations') - ->label('关联知识库') - ->relationship('knowledgeBases') - ->schema([ - Forms\Components\Select::make('id') - ->label('知识库') - ->options(\App\Models\KnowledgeBase::where('status', 'active')->pluck('name', 'id')) - ->required() - ->searchable() - ->distinct() - ->disableOptionsWhenSelectedInSiblingRepeaterItems() - ->helperText('选择要关联的知识库'), - - Forms\Components\TextInput::make('priority') - ->label('优先级') - ->numeric() - ->default(0) - ->required() - ->minValue(0) - ->helperText('数字越小优先级越高,0为最高优先级'), - ]) - ->columns(2) - ->reorderable() - ->reorderableWithButtons() - ->addActionLabel('添加知识库') - ->reorderableWithDragAndDrop(false) - ->itemLabel( - fn(array $state): ?string => - \App\Models\KnowledgeBase::find($state['id'])?->name ?? '未选择' - ) - ->collapsed() - ->collapsible() - ->helperText('可以关联多个知识库,并设置优先级。拖动或使用按钮调整顺序。'), - ]) - ->description('配置终端可以访问的知识库及其优先级'), - Forms\Components\Section::make('指引关联') ->schema([ Forms\Components\Repeater::make('guideAssociations') @@ -215,17 +177,13 @@ class TerminalResource extends Resource ->label('提示词模板') ->language('markdown') ->fontSize('14px') - ->helperText('编辑AI提示词模板,支持使用变量如 {user}, {station}, {time} 等') + ->helperText('编辑AI提示词模板,支持使用占位符 {station_id}, {user}, {time}(由HMI端替换)') ->placeholderText('请输入AI提示词模板...') ->disablePreview() ->columnSpan(2), Forms\Components\Grid::make(1) ->schema([ - Forms\Components\Placeholder::make('template_selector') - ->label('模板库') - ->content(fn() => view('filament.components.prompt-template-selector')), - Forms\Components\Placeholder::make('variable_helper') ->label('变量参考') ->content(fn() => view('filament.components.prompt-variable-helper')), @@ -233,7 +191,7 @@ class TerminalResource extends Resource ->columnSpan(1), ]), ]) - ->description('配置终端的AI提示词模板,用于指导AI助手的行为') + ->description('配置终端的AI提示词模板,占位符由HMI端替换') ->collapsible(), Forms\Components\Section::make('状态信息') diff --git a/app/Filament/Resources/TerminalResource/Pages/ViewTerminal.php b/app/Filament/Resources/TerminalResource/Pages/ViewTerminal.php index 1a896fe..5dc3d53 100644 --- a/app/Filament/Resources/TerminalResource/Pages/ViewTerminal.php +++ b/app/Filament/Resources/TerminalResource/Pages/ViewTerminal.php @@ -53,23 +53,6 @@ class ViewTerminal extends ViewRecord ->openUrlInNewTab(), ]), - Infolists\Components\Section::make('知识库关联') - ->schema([ - Infolists\Components\RepeatableEntry::make('knowledgeBases') - ->label('关联的知识库') - ->schema([ - Infolists\Components\TextEntry::make('name') - ->label('知识库名称'), - Infolists\Components\TextEntry::make('pivot.priority') - ->label('优先级') - ->badge() - ->color('success'), - ]) - ->columns(2) - ->placeholder('未关联任何知识库'), - ]) - ->collapsible(), - Infolists\Components\Section::make('AI提示词配置') ->schema([ Infolists\Components\TextEntry::make('prompt.prompt_template') diff --git a/app/Filament/Widgets/TerminalStatsWidget.php b/app/Filament/Widgets/TerminalStatsWidget.php index 84fda40..7ff5383 100644 --- a/app/Filament/Widgets/TerminalStatsWidget.php +++ b/app/Filament/Widgets/TerminalStatsWidget.php @@ -3,7 +3,6 @@ namespace App\Filament\Widgets; use App\Models\Terminal; -use App\Models\TerminalKnowledgeBase; use App\Models\TerminalPrompt; use Filament\Widgets\StatsOverviewWidget as BaseWidget; use Filament\Widgets\StatsOverviewWidget\Stat; @@ -17,25 +16,17 @@ class TerminalStatsWidget extends BaseWidget // 统计终端数据 $totalTerminals = Terminal::count(); $onlineTerminals = Terminal::where('is_online', true)->count(); - - // 统计知识库关联 - $totalKnowledgeBases = TerminalKnowledgeBase::count(); - + // 统计提示词 $totalPrompts = TerminalPrompt::count(); - + return [ Stat::make('终端总数', $totalTerminals) ->description("{$onlineTerminals} 个在线") ->descriptionIcon('heroicon-m-computer-desktop') ->color('primary') ->url(route('filament.admin.resources.terminals.index')), - - Stat::make('知识库关联', $totalKnowledgeBases) - ->description('终端知识库配置数') - ->descriptionIcon('heroicon-m-link') - ->color('info'), - + Stat::make('提示词配置', $totalPrompts) ->description('终端提示词总数') ->descriptionIcon('heroicon-m-chat-bubble-left-right') diff --git a/app/Http/Controllers/Api/TerminalApiController.php b/app/Http/Controllers/Api/TerminalApiController.php index f7e08a7..1450614 100644 --- a/app/Http/Controllers/Api/TerminalApiController.php +++ b/app/Http/Controllers/Api/TerminalApiController.php @@ -6,34 +6,26 @@ 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']); + $terminal->load('prompt'); - // 渲染system prompt - $systemPrompt = ''; - if ($terminal->prompt && $terminal->prompt->prompt_template) { - $systemPrompt = $this->promptService->replaceVariables( - $terminal->prompt->prompt_template, - $terminal - ); - } + // 返回原始提示词模板(占位符由HMI端替换) + $systemPrompt = $terminal->prompt?->prompt_template ?? ''; // 获取终端关联的已发布指引数量 $guideCount = $terminal->guides()->published()->count(); @@ -56,7 +48,7 @@ class TerminalApiController extends Controller } /** - * GET /api/terminal/knowledge?query=xxx + * GET /api/knowledge?query=xxx * RAG知识搜索(由AI tool_call触发) */ public function knowledge(Request $request): JsonResponse @@ -65,10 +57,7 @@ class TerminalApiController extends Controller 'query' => 'required|string|max:500', ]); - $terminal = $request->attributes->get('terminal'); - $terminal->load('knowledgeBases'); - - $result = $this->knowledgeService->search($terminal, $request->input('query')); + $result = $this->knowledgeService->search($request->input('query')); return response()->json($result); } diff --git a/app/Models/KnowledgeBase.php b/app/Models/KnowledgeBase.php index 81055c2..c27ef29 100644 --- a/app/Models/KnowledgeBase.php +++ b/app/Models/KnowledgeBase.php @@ -21,19 +21,6 @@ class KnowledgeBase extends Model 'status', ]; - /** - * 获取关联的终端 - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany - */ - public function terminals() - { - return $this->belongsToMany(Terminal::class, 'terminal_knowledge_bases') - ->withPivot('priority') - ->withTimestamps() - ->orderBy('priority'); - } - /** * 获取知识库下的文档 * diff --git a/app/Models/Terminal.php b/app/Models/Terminal.php index df0adf7..6f96ac4 100644 --- a/app/Models/Terminal.php +++ b/app/Models/Terminal.php @@ -46,19 +46,6 @@ class Terminal extends Model ]; } - /** - * 获取终端关联的知识库 - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany - */ - public function knowledgeBases() - { - return $this->belongsToMany(KnowledgeBase::class, 'terminal_knowledge_bases') - ->withPivot('priority') - ->withTimestamps() - ->orderBy('priority'); - } - /** * 获取终端关联的指引 * diff --git a/app/Models/TerminalKnowledgeBase.php b/app/Models/TerminalKnowledgeBase.php deleted file mode 100644 index 6fd9879..0000000 --- a/app/Models/TerminalKnowledgeBase.php +++ /dev/null @@ -1,52 +0,0 @@ - - */ - protected $fillable = [ - 'terminal_id', - 'knowledge_base_id', - 'priority', - ]; - - /** - * 属性类型转换 - * - * @return array - */ - protected function casts(): array - { - return [ - 'priority' => 'integer', - ]; - } - - /** - * 获取关联的终端 - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function terminal(): BelongsTo - { - return $this->belongsTo(Terminal::class); - } - - /** - * 获取关联的知识库 - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function knowledgeBase(): BelongsTo - { - return $this->belongsTo(KnowledgeBase::class); - } -} diff --git a/app/Services/KnowledgeContextService.php b/app/Services/KnowledgeContextService.php index 23e5de9..3c90782 100644 --- a/app/Services/KnowledgeContextService.php +++ b/app/Services/KnowledgeContextService.php @@ -2,8 +2,8 @@ namespace App\Services; -use App\Models\Terminal; use App\Models\Document; +use App\Models\KnowledgeBase; use Illuminate\Support\Facades\Log; class KnowledgeContextService @@ -12,15 +12,14 @@ class KnowledgeContextService private const TOP_K = 5; /** - * 搜索终端关联知识库中的文档 + * 搜索所有知识库中的文档 * - * @param Terminal $terminal * @param string $query * @return array{context: string, sources: array} */ - public function search(Terminal $terminal, string $query): array + public function search(string $query): array { - $knowledgeBaseIds = $terminal->knowledgeBases->pluck('id')->toArray(); + $knowledgeBaseIds = KnowledgeBase::where('status', 'active')->pluck('id')->toArray(); if (empty($knowledgeBaseIds)) { return [ @@ -30,11 +29,18 @@ class KnowledgeContextService } try { - // 使用 Scout/Meilisearch 原生过滤(与 DocumentSearchService 一致) - $documents = Document::search($query) - ->whereIn('knowledge_base_id', $knowledgeBaseIds) - ->take(self::TOP_K) - ->get(); + // 使用 Scout/Meilisearch 搜索并获取排名分数 + $rawResults = Document::search($query, function ($meilisearch, $query, $options) use ($knowledgeBaseIds) { + $options['showRankingScore'] = true; + $filter = collect($knowledgeBaseIds) + ->map(fn($id) => "knowledge_base_id = {$id}") + ->implode(' OR '); + $options['filter'] = $filter; + $options['limit'] = self::TOP_K; + return $meilisearch->search($query, $options); + })->raw(); + + $hits = $rawResults['hits'] ?? []; } catch (\Exception $e) { Log::warning('Knowledge search failed', [ 'query' => $query, @@ -47,17 +53,29 @@ class KnowledgeContextService ]; } - if ($documents->isEmpty()) { + if (empty($hits)) { return [ 'context' => '', 'sources' => [], ]; } + // 取出文档 ID 并加载 Eloquent 模型 + $hitIds = collect($hits)->pluck('id')->toArray(); + $documents = Document::whereIn('id', $hitIds)->get()->keyBy('id'); + + // 构建排名分数映射 + $rankingScores = collect($hits)->pluck('_rankingScore', 'id'); + $context = ''; $sources = []; - foreach ($documents as $document) { + foreach ($hits as $hit) { + $document = $documents->get($hit['id']); + if (!$document) { + continue; + } + $snippet = $this->extractSnippet($document); if (mb_strlen($context) + mb_strlen($snippet) > self::MAX_CONTEXT_LENGTH) { @@ -66,9 +84,9 @@ class KnowledgeContextService $context .= $snippet . "\n\n"; $sources[] = [ - 'id' => $document->id, 'title' => $document->title, - 'knowledge_base' => $document->knowledgeBase?->name, + 'id' => 'kb-doc-' . str_pad($document->id, 3, '0', STR_PAD_LEFT), + 'relevance' => round($rankingScores->get($document->id, 0), 2), ]; } diff --git a/app/Services/PromptTemplateService.php b/app/Services/PromptTemplateService.php deleted file mode 100644 index 23776b3..0000000 --- a/app/Services/PromptTemplateService.php +++ /dev/null @@ -1,168 +0,0 @@ -getTemplates(); - - foreach ($templates as $template) { - if ($template['id'] === $templateId) { - return $template; - } - } - - return null; - } - - /** - * 应用模板到终端 - * - * @param Terminal $terminal - * @param string $templateId - * @return string - */ - public function applyTemplate(Terminal $terminal, string $templateId): string - { - $template = $this->getTemplate($templateId); - - if (!$template) { - throw new \InvalidArgumentException("模板不存在: {$templateId}"); - } - - return $template['content']; - } - - /** - * 替换模板中的变量 - * - * @param string $template - * @param Terminal $terminal - * @param array $additionalVars - * @return string - */ - public function replaceVariables(string $template, Terminal $terminal, array $additionalVars = []): string - { - $variables = $this->getVariableValues($terminal, $additionalVars); - - foreach ($variables as $key => $value) { - // 处理数组类型的变量 - if (is_array($value)) { - $value = implode(', ', $value); - } - - $template = str_replace('{' . $key . '}', $value, $template); - } - - return $template; - } - - /** - * 获取变量的实际值 - * - * @param Terminal $terminal - * @param array $additionalVars - * @return array - */ - public function getVariableValues(Terminal $terminal, array $additionalVars = []): array - { - $user = Auth::user(); - $now = now(); - - $variables = [ - 'user' => $user?->name ?? '访客', - 'user_id' => $user?->id ?? 0, - 'user_role' => $user?->roles?->first()?->name ?? '未知', - 'department' => $user?->department ?? '未知部门', - - 'station' => $terminal->station_id ? "工作站 {$terminal->station_id}" : '未绑定工作站', - 'station_id' => $terminal->station_id ?? '未绑定', - - 'terminal_name' => $terminal->name, - 'terminal_code' => $terminal->code, - - 'time' => $now->format('Y-m-d H:i:s'), - 'date' => $now->format('Y-m-d'), - 'time_only' => $now->format('H:i:s'), - 'shift' => $this->getCurrentShift($now), - - 'knowledge_bases' => $terminal->knowledgeBases->pluck('name')->toArray(), - - 'company_name' => config('app.name', '公司名称'), - ]; - - return array_merge($variables, $additionalVars); - } - - /** - * 根据时间判断当前班次 - * - * @param \Illuminate\Support\Carbon $time - * @return string - */ - protected function getCurrentShift($time): string - { - $hour = $time->hour; - - if ($hour >= 8 && $hour < 16) { - return '早班'; - } elseif ($hour >= 16 && $hour < 24) { - return '中班'; - } else { - return '夜班'; - } - } - - /** - * 预览模板(替换变量后的结果) - * - * @param string $template - * @param Terminal $terminal - * @return string - */ - public function preview(string $template, Terminal $terminal): string - { - return $this->replaceVariables($template, $terminal); - } - - /** - * 验证模板中的变量是否都是有效的 - * - * @param string $template - * @return array 返回无效的变量列表 - */ - public function validateVariables(string $template): array - { - $validVariables = array_column(config('prompt_variables.variables', []), 'name'); - - // 提取模板中的所有变量 - preg_match_all('/\{([a-z_]+)\}/', $template, $matches); - $usedVariables = $matches[1] ?? []; - - // 找出无效的变量 - $invalidVariables = array_diff($usedVariables, $validVariables); - - return array_values(array_unique($invalidVariables)); - } -} diff --git a/config/prompt_templates.php b/config/prompt_templates.php deleted file mode 100644 index ad24ba6..0000000 --- a/config/prompt_templates.php +++ /dev/null @@ -1,263 +0,0 @@ - [ - [ - 'id' => 'general_assistant', - 'name' => '通用助手', - 'description' => '同步辐射光束线站通用AI助手,集成知识库检索和交互式操作引导', - 'category' => 'general', - 'content' => <<<'TEMPLATE' -# 角色 - -你是{station_id}光束线站的AI助手,运行在操作终端「{terminal_name}」上。你的使命是帮助用户安全、高效地完成光束线实验和操作。 - -## 当前会话上下文 - -- 用户:{user} -- 光束线站:{station_id} -- 操作终端:{terminal_name}({terminal_code}) -- 时间:{time} -- 班次:{shift} -- 可用知识库:{knowledge_bases} - -## 工具使用策略 - -你有两个工具可以调用。**必须主动使用**,不要凭记忆回答专业问题。 - -### search_knowledge — 知识库检索 - -**何时调用**: -- 用户询问操作规程、设备参数、技术指标、安全规范 -- 需要确认具体数值(能量范围、分辨率、束斑尺寸等) -- 涉及标准流程或规章制度 -- 你不确定某个专业细节时 - -**使用要点**: -- 提取用户问题的核心概念作为搜索关键词,优先使用专业术语 -- 如果首次搜索结果不理想,换用同义词或上下位概念重新搜索 -- 回答时基于检索到的内容作答,注明信息来源 - -### show_guide — 交互式操作引导 - -**何时调用**: -- 用户需要分步操作指导(如"怎么换样品"、"如何调节能量") -- 遇到故障需要排查流程 -- 新用户需要入门引导 -- 任何涉及多步骤、有安全风险的操作 - -**使用要点**: -- 可以组合多个指引 ID 按执行顺序调用 -- 在 reason 中简要说明触发原因,帮助用户理解 -- 指引完成后,根据用户的选择结果提供针对性的后续建议 -- 如果用户在指引中选择了异常分支,主动追问详情并给出进一步处理建议 - -## 回答规范 - -### 安全准则(最高优先级) -- **辐射安全**:涉及进出实验大厅、打开光闸、联锁系统的操作,必须提醒安全要求 -- **真空安全**:涉及破真空、换窗片、样品装卸时,必须确认真空状态和操作顺序 -- **电气安全**:涉及高压设备、电源操作时,提醒断电和接地要求 -- **危险操作拦截**:如果用户描述的操作可能导致设备损坏或人身伤害,先给出警告,建议联系线站负责人确认后再操作 -- 如果你不确定某个操作是否安全,明确告知用户"建议联系线站工作人员确认" - -### 对话风格 -- 使用简洁专业的语言,避免冗长的铺垫 -- 对操作类问题,给出明确的步骤而非笼统建议 -- 对参数类问题,给出具体数值和单位 -- 如果问题超出你的知识范围,坦诚告知并建议联系线站负责人 -- 考虑用户角色:对经验丰富的操作员可以更简练,对访客和新用户需要更详细的解释 - -### 问题分类处理 -1. **快速查询**(参数、状态、简单事实)→ 先调用 search_knowledge 获取准确信息,直接回答 -2. **操作指导**(需要分步操作)→ 调用 show_guide 提供交互式引导 -3. **故障排查**(设备异常、报警处理)→ 先调用 search_knowledge 了解可能原因,再用 show_guide 引导排查流程 -4. **实验咨询**(方案设计、参数优化)→ 调用 search_knowledge 获取相关资料,结合专业知识给出建议 -5. **闲聊或非业务问题** → 简短友好地回应,引导回光束线相关话题 -TEMPLATE - ], - - [ - 'id' => 'safety_focused', - 'name' => '安全专员', - 'description' => '专注于安全操作指导和风险提示的AI助手', - 'category' => 'safety', - 'content' => <<<'TEMPLATE' -# 安全专员AI助手 - -你是 {company_name} 的安全专员助手,负责确保 {station} 的安全生产。 - -## 当前信息 -- 操作员:{user} -- 工作站:{station} -- 当前时间:{time} - -## 核心职责 -1. **安全第一**:所有建议都必须符合安全规范 -2. **风险识别**:主动识别和提示潜在风险 -3. **应急指导**:提供紧急情况的处理步骤 -4. **合规检查**:确保操作符合安全标准 - -## 参考资料 -安全知识库:{knowledge_bases} - -## 回答要求 -- 每次回答前先评估安全风险 -- 使用警示性语言强调重要安全事项 -- 提供具体的安全操作步骤 -- 遇到高风险操作,必须建议停止并联系主管 - -⚠️ 安全提示:如有任何疑问,请立即停止操作并联系安全主管! -TEMPLATE - ], - - [ - 'id' => 'troubleshooting', - 'name' => '故障诊断', - 'description' => '专门用于设备故障诊断和问题排查的AI助手', - 'category' => 'maintenance', - 'content' => <<<'TEMPLATE' -# 故障诊断AI助手 - -你是 {company_name} 的设备维护助手,帮助 {user} 诊断和解决 {station} 的设备问题。 - -## 当前环境 -- 工作站:{station} -- 终端:{terminal_name} -- 报告时间:{time} -- 操作员:{user} - -## 诊断流程 -1. **问题确认**:详细了解故障现象和发生时间 -2. **初步判断**:基于症状进行初步分析 -3. **排查步骤**:提供系统化的排查方法 -4. **解决方案**:给出可行的解决建议 -5. **预防措施**:提供预防类似问题的建议 - -## 可用资源 -维护知识库:{knowledge_bases} - -## 工作原则 -- 采用结构化的诊断方法 -- 从简单到复杂逐步排查 -- 记录所有诊断步骤和结果 -- 超出能力范围时及时上报 -- 确保维修过程的安全性 - -💡 提示:详细描述故障现象有助于快速定位问题 -TEMPLATE - ], - - [ - 'id' => 'training_coach', - 'name' => '培训教练', - 'description' => '用于新员工培训和操作指导的AI助手', - 'category' => 'training', - 'content' => <<<'TEMPLATE' -# 培训教练AI助手 - -欢迎 {user}!我是你的培训教练,将帮助你熟悉 {station} 的操作。 - -## 培训信息 -- 学员:{user}({user_role}) -- 培训工作站:{station} -- 培训时间:{time} -- 班次:{shift} - -## 培训目标 -1. 掌握基本操作流程 -2. 理解安全操作规范 -3. 熟悉设备功能和特性 -4. 学会常见问题处理 - -## 教学方法 -- **循序渐进**:从基础到高级逐步学习 -- **实践为主**:通过实际操作加深理解 -- **及时反馈**:对操作给予即时指导 -- **重复强化**:重要知识点多次强调 -- **鼓励提问**:营造轻松的学习氛围 - -## 学习资源 -培训资料:{knowledge_bases} - -## 互动方式 -- 随时提问,我会耐心解答 -- 不理解的地方可以要求重复讲解 -- 可以要求演示具体操作步骤 -- 学习过程中遇到困难及时告诉我 - -📚 学习提示:不要着急,每个人都有学习过程,慢慢来! -TEMPLATE - ], - - [ - 'id' => 'quality_inspector', - 'name' => '质量检查', - 'description' => '专注于质量控制和检验指导的AI助手', - 'category' => 'quality', - 'content' => <<<'TEMPLATE' -# 质量检查AI助手 - -你是 {company_name} 的质量控制助手,协助 {user} 进行 {station} 的质量检验工作。 - -## 检验信息 -- 检验员:{user} -- 检验工作站:{station} -- 检验时间:{time} -- 班次:{shift} - -## 质量标准 -参考以下质量文档:{knowledge_bases} - -## 检验流程 -1. **准备工作**:确认检验工具和标准 -2. **外观检查**:检查产品外观质量 -3. **尺寸测量**:测量关键尺寸参数 -4. **功能测试**:验证产品功能性能 -5. **记录结果**:详细记录检验数据 -6. **判定处理**:根据标准做出判定 - -## 工作原则 -- 严格按照质量标准执行 -- 保持客观公正的态度 -- 详细记录检验数据 -- 及时反馈质量问题 -- 持续改进质量意识 - -## 异常处理 -- 发现不合格品立即隔离 -- 记录详细的不合格信息 -- 通知相关责任人 -- 协助分析原因 - -✓ 质量承诺:质量是企业的生命,让我们共同守护! -TEMPLATE - ], - ], - - /* - |-------------------------------------------------------------------------- - | 模板分类 - |-------------------------------------------------------------------------- - | - | 模板的分类标签 - | - */ - - 'categories' => [ - 'general' => '通用', - 'safety' => '安全', - 'maintenance' => '维护', - 'training' => '培训', - 'quality' => '质量', - ], -]; diff --git a/config/prompt_variables.php b/config/prompt_variables.php index ce1014d..e582476 100644 --- a/config/prompt_variables.php +++ b/config/prompt_variables.php @@ -3,144 +3,37 @@ return [ /* |-------------------------------------------------------------------------- - | AI提示词可用变量 + | AI提示词占位符 |-------------------------------------------------------------------------- | - | 定义在AI提示词模板中可以使用的变量列表 - | 每个变量包含:名称、描述、示例值、类型 + | 以下占位符由HMI端在运行时替换,KMS仅存储含占位符的原始模板 | */ 'variables' => [ + [ + 'name' => 'station_id', + 'label' => '线站ID', + 'description' => '终端所在的线站标识,由HMI从 /config 接口获取', + 'example' => 'BL02U1', + 'source' => 'KMS /config', + 'replaced_by' => 'HMI', + ], [ 'name' => 'user', 'label' => '用户名称', - 'description' => '当前登录用户的姓名', + 'description' => '当前登录用户的姓名,由HMI从登录信息或固定值获取', 'example' => '张三', - 'type' => 'string', - 'category' => 'user', - ], - [ - 'name' => 'user_id', - 'label' => '用户ID', - 'description' => '当前登录用户的唯一标识符', - 'example' => '12345', - 'type' => 'integer', - 'category' => 'user', - ], - [ - 'name' => 'user_role', - 'label' => '用户角色', - 'description' => '当前登录用户的角色', - 'example' => '操作员', - 'type' => 'string', - 'category' => 'user', - ], - [ - 'name' => 'station', - 'label' => '工作站名称', - 'description' => '终端所在的工作站名称', - 'example' => '生产线A-工位1', - 'type' => 'string', - 'category' => 'station', - ], - [ - 'name' => 'station_id', - 'label' => '工作站ID', - 'description' => '终端所在的工作站ID', - 'example' => '1001', - 'type' => 'integer', - 'category' => 'station', - ], - [ - 'name' => 'terminal_name', - 'label' => '终端名称', - 'description' => '当前终端的名称', - 'example' => 'TERM-0001', - 'type' => 'string', - 'category' => 'terminal', - ], - [ - 'name' => 'terminal_code', - 'label' => '终端编码', - 'description' => '当前终端的唯一编码', - 'example' => 'TERM-0001', - 'type' => 'string', - 'category' => 'terminal', + 'source' => '登录信息或固定值', + 'replaced_by' => 'HMI', ], [ 'name' => 'time', 'label' => '当前时间', - 'description' => '当前的日期和时间', - 'example' => '2024-01-15 14:30:00', - 'type' => 'datetime', - 'category' => 'time', + 'description' => '当前的日期和时间,由HMI通过 QDateTime::currentDateTime() 获取', + 'example' => '2026-03-23 14:30:00', + 'source' => 'QDateTime::currentDateTime()', + 'replaced_by' => 'HMI', ], - [ - 'name' => 'date', - 'label' => '当前日期', - 'description' => '当前的日期', - 'example' => '2024-01-15', - 'type' => 'date', - 'category' => 'time', - ], - [ - 'name' => 'time_only', - 'label' => '当前时刻', - 'description' => '当前的时间(不含日期)', - 'example' => '14:30:00', - 'type' => 'time', - 'category' => 'time', - ], - [ - 'name' => 'shift', - 'label' => '当前班次', - 'description' => '当前的工作班次', - 'example' => '早班', - 'type' => 'string', - 'category' => 'time', - ], - [ - 'name' => 'knowledge_bases', - 'label' => '关联知识库', - 'description' => '终端关联的知识库列表', - 'example' => '安全操作规程, 设备维护手册', - 'type' => 'array', - 'category' => 'knowledge', - ], - [ - 'name' => 'company_name', - 'label' => '公司名称', - 'description' => '系统配置的公司名称', - 'example' => 'XX制造有限公司', - 'type' => 'string', - 'category' => 'system', - ], - [ - 'name' => 'department', - 'label' => '部门名称', - 'description' => '用户所属的部门', - 'example' => '生产部', - 'type' => 'string', - 'category' => 'user', - ], - ], - - /* - |-------------------------------------------------------------------------- - | 变量分类 - |-------------------------------------------------------------------------- - | - | 变量的分类标签,用于在UI中分组显示 - | - */ - - 'categories' => [ - 'user' => '用户信息', - 'station' => '工作站信息', - 'terminal' => '终端信息', - 'time' => '时间信息', - 'knowledge' => '知识库信息', - 'system' => '系统信息', ], ]; diff --git a/database/factories/TerminalPromptFactory.php b/database/factories/TerminalPromptFactory.php index 7258d72..fc68f6b 100644 --- a/database/factories/TerminalPromptFactory.php +++ b/database/factories/TerminalPromptFactory.php @@ -22,8 +22,8 @@ class TerminalPromptFactory extends Factory { return [ 'terminal_id' => Terminal::factory(), - 'prompt_template' => $this->faker->paragraph(5), - 'variables' => ['user', 'station', 'time'], + 'prompt_template' => '你是{station_id}光束线的AI助手。当前时间是{time}。请根据用户{user}的问题提供帮助。', + 'variables' => [], ]; } } diff --git a/database/seeders/TerminalSeeder.php b/database/seeders/TerminalSeeder.php index 0765e86..0f7d3cd 100644 --- a/database/seeders/TerminalSeeder.php +++ b/database/seeders/TerminalSeeder.php @@ -29,6 +29,11 @@ class TerminalSeeder extends Seeder 'BL16U1' => '192.168.1.39', ]; + // 默认提示词模板(占位符由HMI端替换) + $defaultPrompt = <<<'PROMPT' +你是{station_id}光束线的AI助手。当前时间是{time}。请根据用户{user}的问题,提供准确的光束线操作指导、实验支持和技术咨询。你可以回答关于光束线参数、实验流程、设备状态、安全规范等方面的问题。 +PROMPT; + // 为每条光束线创建智慧屏终端 $this->command->info('创建光束线智慧屏终端...'); foreach ($beamlines as $beamline => $ipAddress) { @@ -47,12 +52,8 @@ class TerminalSeeder extends Seeder // 为每个终端创建提示词 TerminalPrompt::create([ 'terminal_id' => $terminal->id, - 'prompt_template' => "你是{station}光束线的AI助手。当前时间是{time}。请根据用户{user}的问题,提供准确的光束线操作指导、实验支持和技术咨询。你可以回答关于光束线参数、实验流程、设备状态、安全规范等方面的问题。", - 'variables' => [ - 'station' => $beamline, - 'time' => '{current_time}', - 'user' => '{current_user}', - ], + 'prompt_template' => $defaultPrompt, + 'variables' => [], ]); } diff --git a/resources/views/filament/components/prompt-preview-original.blade.php b/resources/views/filament/components/prompt-preview-original.blade.php deleted file mode 100644 index 2cfaaec..0000000 --- a/resources/views/filament/components/prompt-preview-original.blade.php +++ /dev/null @@ -1,9 +0,0 @@ -
-
- - - - 原始模板(包含变量) -
-
{{ $content }}
-
diff --git a/resources/views/filament/components/prompt-preview-result.blade.php b/resources/views/filament/components/prompt-preview-result.blade.php deleted file mode 100644 index 9c51b2b..0000000 --- a/resources/views/filament/components/prompt-preview-result.blade.php +++ /dev/null @@ -1,12 +0,0 @@ -
-
- - - - - 预览结果(变量已替换) -
-
-
{{ $content }}
-
-
diff --git a/resources/views/filament/components/prompt-preview-validation.blade.php b/resources/views/filament/components/prompt-preview-validation.blade.php deleted file mode 100644 index 9aeafa8..0000000 --- a/resources/views/filament/components/prompt-preview-validation.blade.php +++ /dev/null @@ -1,27 +0,0 @@ -
-
- - - -
-
- 发现无效变量 -
-
- 以下变量未在系统中定义,可能无法正确替换: -
-
    - @foreach($invalidVariables as $variable) -
  • - - {{'{'}}{{ $variable }}{{'}'}} - -
  • - @endforeach -
-
- 💡 提示:请检查变量名称是否正确,或参考右侧的"变量参考"面板查看所有可用变量。 -
-
-
-
diff --git a/resources/views/filament/components/prompt-template-selector.blade.php b/resources/views/filament/components/prompt-template-selector.blade.php deleted file mode 100644 index ee2af2a..0000000 --- a/resources/views/filament/components/prompt-template-selector.blade.php +++ /dev/null @@ -1,106 +0,0 @@ -@php - $templates = config('prompt_templates.templates', []); - $categories = config('prompt_templates.categories', []); - $groupedTemplates = collect($templates)->groupBy('category'); -@endphp - -
-

- 快速模板 -

- -
- 选择一个预设模板快速开始,您可以在此基础上进行修改 -
- -
- @foreach($groupedTemplates as $category => $temps) -
-

- - {{ $categories[$category] ?? $category }} -

-
- @foreach($temps as $template) - - @endforeach -
-
- @endforeach -
- -
-
- - - - 点击模板将自动填充到编辑器中,您可以根据需要进行修改 -
-
-
- - diff --git a/resources/views/filament/components/prompt-variable-helper.blade.php b/resources/views/filament/components/prompt-variable-helper.blade.php index 90d62f2..44889ad 100644 --- a/resources/views/filament/components/prompt-variable-helper.blade.php +++ b/resources/views/filament/components/prompt-variable-helper.blade.php @@ -1,43 +1,35 @@

- 可用变量 + 可用占位符

- +
- 在提示词模板中使用 {变量名} 格式引用变量 + 以下占位符由 HMI端 在运行时替换
@php $variables = config('prompt_variables.variables', []); - $categories = config('prompt_variables.categories', []); - $groupedVariables = collect($variables)->groupBy('category'); @endphp -
- @foreach($groupedVariables as $category => $vars) -
-

- {{ $categories[$category] ?? $category }} -

-
- @foreach($vars as $variable) -
- - {{'{'}}{{ $variable['name'] }}{{'}'}} - -
-
- {{ $variable['label'] }} -
-
- {{ $variable['description'] }} -
-
- 示例: {{ $variable['example'] }} -
-
-
- @endforeach +
+ @foreach($variables as $variable) +
+ + {{'{'}}{{ $variable['name'] }}{{'}'}} + +
+
+ {{ $variable['label'] }} +
+
+ {{ $variable['description'] }} +
+
+ 示例: {{ $variable['example'] }} +
+
+ 来源: {{ $variable['source'] }} +
@endforeach @@ -46,9 +38,9 @@

使用示例

-
你好,{{'{'}}user{{'}'}}!
-
当前时间是 {{'{'}}time{{'}'}},你在 {{'{'}}station{{'}'}}
-
请参考以下知识库:{{'{'}}knowledge_bases{{'}'}}。
+
你是{{'{'}}station_id{{'}'}}光束线的AI助手。
+
当前时间是 {{'{'}}time{{'}'}}。
+
请根据用户{{'{'}}user{{'}'}}的问题提供帮助。
diff --git a/routes/api.php b/routes/api.php index cbd55b2..ea3261d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,10 +3,13 @@ use App\Http\Controllers\Api\TerminalApiController; use Illuminate\Support\Facades\Route; -Route::middleware('identify.terminal')->prefix('terminal')->group(function () { - Route::get('/config', [TerminalApiController::class, 'config']); +Route::middleware('identify.terminal')->group(function () { Route::get('/knowledge', [TerminalApiController::class, 'knowledge']); - Route::get('/guides', [TerminalApiController::class, 'guides']); - Route::post('/guides/pages', [TerminalApiController::class, 'guidePages']); - Route::post('/heartbeat', [TerminalApiController::class, 'heartbeat']); + + Route::prefix('terminal')->group(function () { + Route::get('/config', [TerminalApiController::class, 'config']); + Route::get('/guides', [TerminalApiController::class, 'guides']); + Route::post('/guides/pages', [TerminalApiController::class, 'guidePages']); + Route::post('/heartbeat', [TerminalApiController::class, 'heartbeat']); + }); }); diff --git a/tests/Feature/PromptTemplateTest.php b/tests/Feature/PromptTemplateTest.php deleted file mode 100644 index 04885bc..0000000 --- a/tests/Feature/PromptTemplateTest.php +++ /dev/null @@ -1,266 +0,0 @@ -service = app(PromptTemplateService::class); - - // 创建测试用户 - $this->user = User::factory()->create([ - 'name' => '测试用户', - ]); - - // 创建测试终端 - $this->terminal = Terminal::factory()->create([ - 'name' => '测试终端', - 'code' => 'TEST-001', - 'station_id' => 1001, - ]); - - $this->actingAs($this->user); - } - - /** @test */ - public function it_can_get_all_templates() - { - $templates = $this->service->getTemplates(); - - $this->assertIsArray($templates); - $this->assertNotEmpty($templates); - $this->assertArrayHasKey('id', $templates[0]); - $this->assertArrayHasKey('name', $templates[0]); - $this->assertArrayHasKey('content', $templates[0]); - } - - /** @test */ - public function it_can_get_template_by_id() - { - $template = $this->service->getTemplate('general_assistant'); - - $this->assertNotNull($template); - $this->assertEquals('general_assistant', $template['id']); - $this->assertArrayHasKey('content', $template); - } - - /** @test */ - public function it_returns_null_for_invalid_template_id() - { - $template = $this->service->getTemplate('non_existent_template'); - - $this->assertNull($template); - } - - /** @test */ - public function it_can_apply_template_to_terminal() - { - $content = $this->service->applyTemplate($this->terminal, 'general_assistant'); - - $this->assertIsString($content); - $this->assertNotEmpty($content); - $this->assertStringContainsString('{user}', $content); - $this->assertStringContainsString('{station}', $content); - } - - /** @test */ - public function it_throws_exception_for_invalid_template() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('模板不存在'); - - $this->service->applyTemplate($this->terminal, 'invalid_template'); - } - - /** @test */ - public function it_can_replace_variables_in_template() - { - $template = '你好,{user}!当前时间是 {time},你在 {station}。'; - - $result = $this->service->replaceVariables($template, $this->terminal); - - $this->assertStringContainsString('测试用户', $result); - $this->assertStringContainsString('工作站 1001', $result); - $this->assertStringNotContainsString('{user}', $result); - $this->assertStringNotContainsString('{station}', $result); - } - - /** @test */ - public function it_can_get_variable_values() - { - $variables = $this->service->getVariableValues($this->terminal); - - $this->assertIsArray($variables); - $this->assertArrayHasKey('user', $variables); - $this->assertArrayHasKey('terminal_name', $variables); - $this->assertArrayHasKey('station', $variables); - $this->assertArrayHasKey('time', $variables); - - $this->assertEquals('测试用户', $variables['user']); - $this->assertEquals('测试终端', $variables['terminal_name']); - $this->assertEquals('TEST-001', $variables['terminal_code']); - } - - /** @test */ - public function it_handles_array_variables_correctly() - { - // 创建知识库关联 - $kb1 = \App\Models\KnowledgeBase::factory()->create(['name' => '知识库1']); - $kb2 = \App\Models\KnowledgeBase::factory()->create(['name' => '知识库2']); - - $this->terminal->knowledgeBases()->attach([ - $kb1->id => ['priority' => 1], - $kb2->id => ['priority' => 2], - ]); - - $template = '可用知识库:{knowledge_bases}'; - $result = $this->service->replaceVariables($template, $this->terminal); - - $this->assertStringContainsString('知识库1', $result); - $this->assertStringContainsString('知识库2', $result); - } - - /** @test */ - public function it_can_preview_template() - { - $template = '你好,{user}!终端:{terminal_name}'; - - $preview = $this->service->preview($template, $this->terminal); - - $this->assertStringContainsString('测试用户', $preview); - $this->assertStringContainsString('测试终端', $preview); - $this->assertStringNotContainsString('{user}', $preview); - } - - /** @test */ - public function it_can_validate_variables() - { - $validTemplate = '你好,{user}!时间:{time}'; - $invalidVariables = $this->service->validateVariables($validTemplate); - - $this->assertEmpty($invalidVariables); - } - - /** @test */ - public function it_detects_invalid_variables() - { - $invalidTemplate = '你好,{user}!无效变量:{invalid_var},另一个:{another_invalid}'; - $invalidVariables = $this->service->validateVariables($invalidTemplate); - - $this->assertNotEmpty($invalidVariables); - $this->assertContains('invalid_var', $invalidVariables); - $this->assertContains('another_invalid', $invalidVariables); - } - - /** @test */ - public function it_determines_shift_correctly() - { - // 使用反射访问protected方法 - $reflection = new \ReflectionClass($this->service); - $method = $reflection->getMethod('getCurrentShift'); - $method->setAccessible(true); - - // 测试早班 (8:00-16:00) - $morningTime = now()->setTime(10, 0); - $this->assertEquals('早班', $method->invoke($this->service, $morningTime)); - - // 测试中班 (16:00-24:00) - $afternoonTime = now()->setTime(18, 0); - $this->assertEquals('中班', $method->invoke($this->service, $afternoonTime)); - - // 测试夜班 (0:00-8:00) - $nightTime = now()->setTime(2, 0); - $this->assertEquals('夜班', $method->invoke($this->service, $nightTime)); - } - - /** @test */ - public function terminal_can_save_prompt_template() - { - $promptData = [ - 'prompt_template' => '这是一个测试提示词模板,包含变量 {user} 和 {station}', - 'variables' => ['user', 'station'], - ]; - - $prompt = $this->terminal->prompt()->create($promptData); - - $this->assertInstanceOf(TerminalPrompt::class, $prompt); - $this->assertEquals($promptData['prompt_template'], $prompt->prompt_template); - $this->assertEquals($promptData['variables'], $prompt->variables); - } - - /** @test */ - public function terminal_prompt_relationship_works() - { - TerminalPrompt::factory()->create([ - 'terminal_id' => $this->terminal->id, - 'prompt_template' => '测试模板', - ]); - - $this->terminal->refresh(); - - $this->assertNotNull($this->terminal->prompt); - $this->assertEquals('测试模板', $this->terminal->prompt->prompt_template); - } - - /** @test */ - public function config_files_are_properly_structured() - { - // 测试变量配置 - $variables = config('prompt_variables.variables'); - $this->assertIsArray($variables); - $this->assertNotEmpty($variables); - - foreach ($variables as $variable) { - $this->assertArrayHasKey('name', $variable); - $this->assertArrayHasKey('label', $variable); - $this->assertArrayHasKey('description', $variable); - $this->assertArrayHasKey('example', $variable); - $this->assertArrayHasKey('type', $variable); - $this->assertArrayHasKey('category', $variable); - } - - // 测试模板配置 - $templates = config('prompt_templates.templates'); - $this->assertIsArray($templates); - $this->assertNotEmpty($templates); - - foreach ($templates as $template) { - $this->assertArrayHasKey('id', $template); - $this->assertArrayHasKey('name', $template); - $this->assertArrayHasKey('description', $template); - $this->assertArrayHasKey('category', $template); - $this->assertArrayHasKey('content', $template); - } - } - - /** @test */ - public function all_template_variables_are_valid() - { - $templates = config('prompt_templates.templates'); - - foreach ($templates as $template) { - $invalidVars = $this->service->validateVariables($template['content']); - - $this->assertEmpty( - $invalidVars, - "模板 '{$template['name']}' 包含无效变量: " . implode(', ', $invalidVars) - ); - } - } -} diff --git a/tests/Feature/TerminalKnowledgeBaseAssociationTest.php b/tests/Feature/TerminalKnowledgeBaseAssociationTest.php deleted file mode 100644 index ffb7f58..0000000 --- a/tests/Feature/TerminalKnowledgeBaseAssociationTest.php +++ /dev/null @@ -1,158 +0,0 @@ -user = User::factory()->create(); - } - - /** @test */ - public function terminal_can_associate_with_knowledge_bases() - { - // 创建终端 - $terminal = Terminal::factory()->create([ - 'name' => '测试终端', - 'code' => 'TEST-001', - ]); - - // 创建知识库 - $kb1 = KnowledgeBase::create([ - 'name' => '知识库1', - 'description' => '测试知识库1', - 'status' => 'active', - ]); - - $kb2 = KnowledgeBase::create([ - 'name' => '知识库2', - 'description' => '测试知识库2', - 'status' => 'active', - ]); - - // 关联知识库并设置优先级 - $terminal->knowledgeBases()->attach($kb1->id, ['priority' => 1]); - $terminal->knowledgeBases()->attach($kb2->id, ['priority' => 2]); - - // 验证关联关系 - $this->assertCount(2, $terminal->knowledgeBases); - $this->assertTrue($terminal->knowledgeBases->contains($kb1)); - $this->assertTrue($terminal->knowledgeBases->contains($kb2)); - } - - /** @test */ - public function knowledge_bases_are_ordered_by_priority() - { - // 创建终端 - $terminal = Terminal::factory()->create(); - - // 创建知识库 - $kb1 = KnowledgeBase::create(['name' => '知识库1', 'status' => 'active']); - $kb2 = KnowledgeBase::create(['name' => '知识库2', 'status' => 'active']); - $kb3 = KnowledgeBase::create(['name' => '知识库3', 'status' => 'active']); - - // 按不同优先级关联(优先级越小越靠前) - $terminal->knowledgeBases()->attach($kb1->id, ['priority' => 10]); - $terminal->knowledgeBases()->attach($kb2->id, ['priority' => 5]); - $terminal->knowledgeBases()->attach($kb3->id, ['priority' => 1]); - - // 重新加载关联关系 - $terminal->refresh(); - - // 验证排序(应该按优先级从小到大排序) - $orderedKbs = $terminal->knowledgeBases; - $this->assertEquals($kb3->id, $orderedKbs[0]->id); // priority 1 - $this->assertEquals($kb2->id, $orderedKbs[1]->id); // priority 5 - $this->assertEquals($kb1->id, $orderedKbs[2]->id); // priority 10 - } - - /** @test */ - public function terminal_can_update_knowledge_base_associations() - { - // 创建终端和知识库 - $terminal = Terminal::factory()->create(); - $kb1 = KnowledgeBase::create(['name' => '知识库1', 'status' => 'active']); - $kb2 = KnowledgeBase::create(['name' => '知识库2', 'status' => 'active']); - - // 初始关联 - $terminal->knowledgeBases()->attach($kb1->id, ['priority' => 1]); - - // 验证初始状态 - $this->assertCount(1, $terminal->knowledgeBases); - - // 更新关联(添加新的知识库) - $terminal->knowledgeBases()->attach($kb2->id, ['priority' => 2]); - - // 重新加载 - $terminal->refresh(); - - // 验证更新后的状态 - $this->assertCount(2, $terminal->knowledgeBases); - } - - /** @test */ - public function terminal_can_remove_knowledge_base_associations() - { - // 创建终端和知识库 - $terminal = Terminal::factory()->create(); - $kb1 = KnowledgeBase::create(['name' => '知识库1', 'status' => 'active']); - $kb2 = KnowledgeBase::create(['name' => '知识库2', 'status' => 'active']); - - // 关联知识库 - $terminal->knowledgeBases()->attach([ - $kb1->id => ['priority' => 1], - $kb2->id => ['priority' => 2], - ]); - - // 验证初始状态 - $this->assertCount(2, $terminal->knowledgeBases); - - // 移除一个关联 - $terminal->knowledgeBases()->detach($kb1->id); - - // 重新加载 - $terminal->refresh(); - - // 验证移除后的状态 - $this->assertCount(1, $terminal->knowledgeBases); - $this->assertFalse($terminal->knowledgeBases->contains($kb1)); - $this->assertTrue($terminal->knowledgeBases->contains($kb2)); - } - - /** @test */ - public function terminal_can_update_priority_of_associated_knowledge_base() - { - // 创建终端和知识库 - $terminal = Terminal::factory()->create(); - $kb = KnowledgeBase::create(['name' => '知识库1', 'status' => 'active']); - - // 关联知识库 - $terminal->knowledgeBases()->attach($kb->id, ['priority' => 5]); - - // 验证初始优先级 - $this->assertEquals(5, $terminal->knowledgeBases->first()->pivot->priority); - - // 更新优先级 - $terminal->knowledgeBases()->updateExistingPivot($kb->id, ['priority' => 1]); - - // 重新加载 - $terminal->refresh(); - - // 验证更新后的优先级 - $this->assertEquals(1, $terminal->knowledgeBases->first()->pivot->priority); - } -} diff --git a/tests/Feature/TerminalKnowledgeBaseFormTest.php b/tests/Feature/TerminalKnowledgeBaseFormTest.php deleted file mode 100644 index d6f7b23..0000000 --- a/tests/Feature/TerminalKnowledgeBaseFormTest.php +++ /dev/null @@ -1,181 +0,0 @@ -user = User::factory()->create(); - $this->actingAs($this->user); - } - - /** @test */ - public function terminal_form_has_knowledge_base_association_field() - { - // 创建知识库 - KnowledgeBase::create([ - 'name' => '测试知识库', - 'description' => '测试描述', - 'status' => 'active', - ]); - - // 测试创建表单包含知识库关联字段 - $component = Livewire::test(TerminalResource\Pages\CreateTerminal::class); - - // 验证表单可以渲染 - $component->assertSuccessful(); - - // 验证表单组件存在(通过检查表单实例) - $this->assertNotNull($component->instance()->form); - } - - /** @test */ - public function terminal_can_be_created_with_knowledge_base_associations() - { - // 创建知识库 - $kb1 = KnowledgeBase::create([ - 'name' => '生产流程知识库', - 'status' => 'active', - ]); - - $kb2 = KnowledgeBase::create([ - 'name' => '设备维护知识库', - 'status' => 'active', - ]); - - // 创建终端 - $terminal = Terminal::factory()->create([ - 'name' => '测试终端', - 'code' => 'TEST-001', - ]); - - // 手动关联知识库(模拟表单提交后的效果) - $terminal->knowledgeBases()->attach([ - $kb1->id => ['priority' => 1], - $kb2->id => ['priority' => 2], - ]); - - // 验证关联关系 - $this->assertCount(2, $terminal->knowledgeBases); - $this->assertTrue($terminal->knowledgeBases->contains($kb1)); - $this->assertTrue($terminal->knowledgeBases->contains($kb2)); - } - - /** @test */ - public function edit_form_can_load_terminal_with_knowledge_bases() - { - // 创建终端和知识库 - $terminal = Terminal::factory()->create(); - - $kb1 = KnowledgeBase::create(['name' => '知识库1', 'status' => 'active']); - $kb2 = KnowledgeBase::create(['name' => '知识库2', 'status' => 'active']); - - // 关联知识库 - $terminal->knowledgeBases()->attach([ - $kb1->id => ['priority' => 5], - $kb2->id => ['priority' => 10], - ]); - - // 测试编辑表单可以加载 - $component = Livewire::test(TerminalResource\Pages\EditTerminal::class, [ - 'record' => $terminal->getRouteKey(), - ]); - - // 验证表单可以渲染 - $component->assertSuccessful(); - - // 验证基本信息加载正确 - $component->assertFormSet([ - 'name' => $terminal->name, - 'code' => $terminal->code, - ]); - } - - /** @test */ - public function terminal_knowledge_base_associations_can_be_updated() - { - // 创建终端和知识库 - $terminal = Terminal::factory()->create(); - - $kb1 = KnowledgeBase::create(['name' => '知识库1', 'status' => 'active']); - $kb2 = KnowledgeBase::create(['name' => '知识库2', 'status' => 'active']); - $kb3 = KnowledgeBase::create(['name' => '知识库3', 'status' => 'active']); - - // 初始关联 - $terminal->knowledgeBases()->attach($kb1->id, ['priority' => 1]); - - // 更新关联(移除旧的,添加新的) - $terminal->knowledgeBases()->sync([ - $kb2->id => ['priority' => 5], - $kb3->id => ['priority' => 10], - ]); - - // 验证更新后的关联 - $terminal->refresh(); - $this->assertCount(2, $terminal->knowledgeBases); - $this->assertFalse($terminal->knowledgeBases->contains($kb1)); - $this->assertTrue($terminal->knowledgeBases->contains($kb2)); - $this->assertTrue($terminal->knowledgeBases->contains($kb3)); - } - - /** @test */ - public function terminal_can_remove_all_knowledge_base_associations() - { - // 创建终端和知识库 - $terminal = Terminal::factory()->create(); - $kb1 = KnowledgeBase::create(['name' => '知识库1', 'status' => 'active']); - - // 初始关联 - $terminal->knowledgeBases()->attach($kb1->id, ['priority' => 1]); - $this->assertCount(1, $terminal->knowledgeBases); - - // 移除所有关联 - $terminal->knowledgeBases()->detach(); - - // 验证所有关联已移除 - $terminal->refresh(); - $this->assertCount(0, $terminal->knowledgeBases); - } - - /** @test */ - public function knowledge_bases_are_ordered_by_priority_in_form() - { - // 创建终端和知识库 - $terminal = Terminal::factory()->create(); - - $kb1 = KnowledgeBase::create(['name' => '知识库1', 'status' => 'active']); - $kb2 = KnowledgeBase::create(['name' => '知识库2', 'status' => 'active']); - $kb3 = KnowledgeBase::create(['name' => '知识库3', 'status' => 'active']); - - // 按不同优先级关联 - $terminal->knowledgeBases()->attach([ - $kb1->id => ['priority' => 10], - $kb2->id => ['priority' => 5], - $kb3->id => ['priority' => 1], - ]); - - // 重新加载并验证排序 - $terminal->refresh(); - $orderedKbs = $terminal->knowledgeBases; - - $this->assertEquals($kb3->id, $orderedKbs[0]->id); // priority 1 - $this->assertEquals($kb2->id, $orderedKbs[1]->id); // priority 5 - $this->assertEquals($kb1->id, $orderedKbs[2]->id); // priority 10 - } -}