diff --git a/apps/client/app/features/knowledge/api/knowledgeApi.test.ts b/apps/client/app/features/knowledge/api/knowledgeApi.test.ts index e892cf9d9..d8d559448 100644 --- a/apps/client/app/features/knowledge/api/knowledgeApi.test.ts +++ b/apps/client/app/features/knowledge/api/knowledgeApi.test.ts @@ -371,6 +371,31 @@ describe('knowledgeApi safe failure logging', () => { }); describe('knowledgeApi collection management', () => { + it('loads the route-safe Workflow Collection picker projection', async () => { + vi.mocked(apiClient.get).mockResolvedValueOnce({ + data: { + collections: [ + { + id: '11111111-1111-1111-1111-111111111111', + safe_label: '사내 문서', + }, + ], + }, + }); + + const response = await knowledgeApi.getLLMSelectableKnowledgeCollections(); + + expect(apiClient.get).toHaveBeenCalledWith( + '/knowledge/llm-selectable-collections', + ); + expect(response.collections).toEqual([ + { + id: '11111111-1111-1111-1111-111111111111', + safe_label: '사내 문서', + }, + ]); + }); + it('updates Knowledge Base safe metadata', async () => { vi.mocked(apiClient.patch).mockResolvedValueOnce({ data: { diff --git a/apps/client/app/features/knowledge/api/knowledgeApi.ts b/apps/client/app/features/knowledge/api/knowledgeApi.ts index 1f5cbdbf6..2eb88201c 100644 --- a/apps/client/app/features/knowledge/api/knowledgeApi.ts +++ b/apps/client/app/features/knowledge/api/knowledgeApi.ts @@ -12,6 +12,8 @@ import { KnowledgeCollectionRoleBundle, KnowledgeCollectionResponse, KnowledgeCollectionListResponse, + KnowledgeCollectionLLMSelectableItem, + KnowledgeCollectionLLMSelectableResponse, KnowledgeCollectionItemResponse, KnowledgeCollectionItemsResponse, KnowledgeCollectionLinkCandidate, @@ -174,6 +176,8 @@ export type { KnowledgeCollectionRoleBundle, KnowledgeCollectionResponse, KnowledgeCollectionListResponse, + KnowledgeCollectionLLMSelectableItem, + KnowledgeCollectionLLMSelectableResponse, KnowledgeCollectionItemResponse, KnowledgeCollectionItemsResponse, KnowledgeCollectionLinkCandidate, @@ -335,6 +339,18 @@ export const knowledgeApi = { } }, + // LLM 노드에서 route 권한으로 선택 가능한 Collection의 최소 projection 조회 + getLLMSelectableKnowledgeCollections: + async (): Promise => { + try { + const response = await api.get('/knowledge/llm-selectable-collections'); + return response.data; + } catch (error) { + logKnowledgeApiFailure('getLLMSelectableKnowledgeCollections', error); + throw error; + } + }, + // 지식 상세 조회 getKnowledgeBase: async ( id: string, diff --git a/apps/client/app/features/knowledge/types/Knowledge.ts b/apps/client/app/features/knowledge/types/Knowledge.ts index efde2a700..bae11096b 100644 --- a/apps/client/app/features/knowledge/types/Knowledge.ts +++ b/apps/client/app/features/knowledge/types/Knowledge.ts @@ -191,6 +191,15 @@ export interface KnowledgeCollectionListResponse { can_change_public_visibility: boolean; } +export interface KnowledgeCollectionLLMSelectableItem { + id: string; + safe_label?: string | null; +} + +export interface KnowledgeCollectionLLMSelectableResponse { + collections: KnowledgeCollectionLLMSelectableItem[]; +} + export interface KnowledgeCollectionItemResponse { item_id: string; knowledge_base_id: string; diff --git a/apps/client/app/features/workflow/components/deployment/SuccessStep.test.tsx b/apps/client/app/features/workflow/components/deployment/SuccessStep.test.tsx index f4f15f901..a6d43f22a 100644 --- a/apps/client/app/features/workflow/components/deployment/SuccessStep.test.tsx +++ b/apps/client/app/features/workflow/components/deployment/SuccessStep.test.tsx @@ -14,6 +14,28 @@ afterEach(() => { }); describe('SuccessStep', () => { + it('shows a non-blocking preflight warning after a successful deployment', () => { + render( + , + ); + + expect(screen.getByRole('status')).toHaveTextContent( + '배포 전 검사 경고가 있습니다.', + ); + expect(screen.getByRole('status')).toHaveTextContent( + '실행 시 지식 후보가 제한될 수 있습니다.', + ); + }); + it('distinguishes public chatbot link from authenticated internal run link', () => { render(
+ {result.message && ( +
+ {result.message} +
+ )} + {/* Web App / Chatbot Share Link */} {result.webAppUrl && (
diff --git a/apps/client/app/features/workflow/components/deployment/types.ts b/apps/client/app/features/workflow/components/deployment/types.ts index 032edb903..414c2fb26 100644 --- a/apps/client/app/features/workflow/components/deployment/types.ts +++ b/apps/client/app/features/workflow/components/deployment/types.ts @@ -15,7 +15,7 @@ export interface DeploymentResult { isWorkflowNode?: boolean; input_schema?: InputSchema | null; output_schema?: OutputSchema | null; - message?: string; // Error message + message?: string; // Error or non-blocking preflight warning cronExpression?: string; // Schedule trigger용 timezone?: string; // Schedule trigger용 graph_snapshot?: any; // 노드 타입 확인용 (webhookTrigger 등) diff --git a/apps/client/app/features/workflow/components/nodes/llm/components/LLMNodePanel.tsx b/apps/client/app/features/workflow/components/nodes/llm/components/LLMNodePanel.tsx index 85b1b4511..14718f848 100644 --- a/apps/client/app/features/workflow/components/nodes/llm/components/LLMNodePanel.tsx +++ b/apps/client/app/features/workflow/components/nodes/llm/components/LLMNodePanel.tsx @@ -17,11 +17,6 @@ import { import { PromptWizardModal } from '../../../modals/PromptWizardModal'; import { ModelSelectDropdown } from './ModelSelectDropdown'; import { LLMParameterSidePanel } from './LLMParameterSidePanel'; -import { - fetchEligibleKnowledgeBases, - sanitizeSelectedKnowledgeBases, - isSameKnowledgeSelection, -} from '@/app/features/workflow/utils/llmKnowledgeBaseSelection'; import { resolveWorkflowWizardOrganizationId } from '@/app/features/workflow/utils/resolveWorkflowWizardOrganizationId'; import { DraggedOutputVariable, @@ -897,35 +892,6 @@ export function LLMNodePanel({ void loadRoutingPolicy(); }, [data.auto_model_routing, loadRoutingPolicy]); - useEffect(() => { - if (!data.knowledgeBases || data.knowledgeBases.length === 0) return; - let active = true; - const syncKnowledgeBases = async () => { - try { - const { bases, preserveSelectionIds = [] } = - await fetchEligibleKnowledgeBases(); - if (!active) return; - const nextSelected = sanitizeSelectedKnowledgeBases( - data.knowledgeBases || [], - bases, - { preserveMissingIds: preserveSelectionIds }, - ); - if ( - !isSameKnowledgeSelection(nextSelected, data.knowledgeBases || []) - ) { - updateNodeData(nodeId, { knowledgeBases: nextSelected }); - } - } catch { - // 지식 베이스 동기화 실패는 노드 편집 자체를 막지 않습니다. - } - }; - - syncKnowledgeBases(); - return () => { - active = false; - }; - }, [data.knowledgeBases, nodeId, updateNodeData]); - useEffect(() => { if (!activeHelp) return; @@ -1435,7 +1401,7 @@ export function LLMNodePanel({
- {/* 2.5 지식 베이스 버튼 (지식 베이스 그룹 통합) */} + {/* 2.5 Knowledge 선택 버튼 */}