Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions apps/client/app/features/knowledge/api/knowledgeApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
16 changes: 16 additions & 0 deletions apps/client/app/features/knowledge/api/knowledgeApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
KnowledgeCollectionRoleBundle,
KnowledgeCollectionResponse,
KnowledgeCollectionListResponse,
KnowledgeCollectionLLMSelectableItem,
KnowledgeCollectionLLMSelectableResponse,
KnowledgeCollectionItemResponse,
KnowledgeCollectionItemsResponse,
KnowledgeCollectionLinkCandidate,
Expand Down Expand Up @@ -174,6 +176,8 @@ export type {
KnowledgeCollectionRoleBundle,
KnowledgeCollectionResponse,
KnowledgeCollectionListResponse,
KnowledgeCollectionLLMSelectableItem,
KnowledgeCollectionLLMSelectableResponse,
KnowledgeCollectionItemResponse,
KnowledgeCollectionItemsResponse,
KnowledgeCollectionLinkCandidate,
Expand Down Expand Up @@ -335,6 +339,18 @@ export const knowledgeApi = {
}
},

// LLM 노드에서 route 권한으로 선택 가능한 Collection의 최소 projection 조회
getLLMSelectableKnowledgeCollections:
async (): Promise<KnowledgeCollectionLLMSelectableResponse> => {
try {
const response = await api.get('/knowledge/llm-selectable-collections');
return response.data;
} catch (error) {
logKnowledgeApiFailure('getLLMSelectableKnowledgeCollections', error);
throw error;
}
},

// 지식 상세 조회
getKnowledgeBase: async (
id: string,
Expand Down
9 changes: 9 additions & 0 deletions apps/client/app/features/knowledge/types/Knowledge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@ afterEach(() => {
});

describe('SuccessStep', () => {
it('shows a non-blocking preflight warning after a successful deployment', () => {
render(
<SuccessStep
deploymentType="api"
onClose={vi.fn()}
result={{
success: true,
version: 1,
message:
'배포 전 검사 경고가 있습니다.\n실행 시 지식 후보가 제한될 수 있습니다.',
}}
/>,
);

expect(screen.getByRole('status')).toHaveTextContent(
'배포 전 검사 경고가 있습니다.',
);
expect(screen.getByRole('status')).toHaveTextContent(
'실행 시 지식 후보가 제한될 수 있습니다.',
);
});

it('distinguishes public chatbot link from authenticated internal run link', () => {
render(
<SuccessStep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,15 @@ ${authHeader} -d '{
</div>

<div className="p-6 space-y-6 max-h-[60vh] overflow-y-auto">
{result.message && (
<div
role="status"
className="whitespace-pre-line rounded-lg border border-amber-300 bg-amber-50 p-4 text-sm text-amber-900"
>
{result.message}
</div>
)}

{/* Web App / Chatbot Share Link */}
{result.webAppUrl && (
<div className="border-2 border-blue-200 rounded-lg p-4 bg-blue-50">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 등)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -1435,7 +1401,7 @@ export function LLMNodePanel({
</div>
</CollapsibleSection>

{/* 2.5 지식 베이스 버튼 (지식 베이스 그룹 통합) */}
{/* 2.5 Knowledge 선택 버튼 */}
<div className="my-2 group">
<button
type="button"
Expand All @@ -1451,7 +1417,8 @@ export function LLMNodePanel({
window.dispatchEvent(event);
}}
className={`relative w-full py-4 px-5 rounded-xl border-2 border-dashed transition-all duration-300 flex items-center gap-4 active:scale-[0.98] ${
(data.knowledgeBases?.length ?? 0) > 0
(data.knowledgeBases?.length ?? 0) > 0 ||
(data.knowledgeCollections?.length ?? 0) > 0
? 'border-indigo-400 bg-indigo-50/80 text-indigo-800 shadow-sm hover:shadow-md hover:bg-indigo-50 hover:border-indigo-500'
: 'border-gray-300 bg-gray-50/50 text-gray-600 hover:border-indigo-400 hover:bg-indigo-50/30 hover:text-indigo-700 hover:shadow-sm'
}`}
Expand All @@ -1462,7 +1429,8 @@ export function LLMNodePanel({
{/* 왼쪽 아이콘 (책) */}
<div
className={`p-2 rounded-lg transition-colors duration-300 ${
(data.knowledgeBases?.length ?? 0) > 0
(data.knowledgeBases?.length ?? 0) > 0 ||
(data.knowledgeCollections?.length ?? 0) > 0
? 'bg-indigo-200 text-indigo-700'
: 'bg-gray-200 text-gray-500 group-hover:bg-indigo-100 group-hover:text-indigo-600'
}`}
Expand All @@ -1473,17 +1441,21 @@ export function LLMNodePanel({
{/* 텍스트 내용 */}
<div className="flex flex-col items-start flex-1 gap-0.5">
<span className="font-bold text-sm tracking-tight">
지식 베이스 설정
Knowledge 설정
</span>
<span
className={`text-xs transition-colors duration-300 ${
(data.knowledgeBases?.length ?? 0) > 0
(data.knowledgeBases?.length ?? 0) > 0 ||
(data.knowledgeCollections?.length ?? 0) > 0
? 'text-indigo-600 font-medium'
: 'text-gray-400 group-hover:text-indigo-500'
}`}
>
{(data.knowledgeBases?.length ?? 0) > 0
? `${data.knowledgeBases!.length}개 그룹 연결됨`
{(data.knowledgeBases?.length ?? 0) > 0 ||
(data.knowledgeCollections?.length ?? 0) > 0
? `고정 KB ${data.knowledgeBases?.length ?? 0}개 · Collection ${
data.knowledgeCollections?.length ?? 0
}개`
: 'LLM에 지식을 연결하세요'}
</span>
</div>
Expand Down
Loading
Loading