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

Large diffs are not rendered by default.

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;600;700;800&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<script type="module" crossorigin src="/assets/index-BjVENS-h.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DyZZSuCn.css">
<script type="module" crossorigin src="/assets/index-BsDnb84E.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DI6fGfGA.css">
</head>
<body>
<div id="root"></div>
Expand Down
7 changes: 0 additions & 7 deletions src/api/insightMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ export interface InsightMapResponse {
edges: InsightMapEdge[]
}

export interface SourceLinkResponse {
url: string
}

export interface EvidenceItem {
title: string
content: string
Expand Down Expand Up @@ -53,9 +49,6 @@ export const insightMapApi = {
mode,
}),

getSourceLink: (caseIndexId: string) =>
apiClient.get<SourceLinkResponse>(`/soap-case-indexes/${caseIndexId}/source-link`),

getConceptEvidence: (conceptKey: string) =>
apiClient.get<EvidenceResponse>(`/insight-map/concepts/${conceptKey}/evidence`),
}
32 changes: 21 additions & 11 deletions src/pages/InsightMapPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { useNavigate, useSearchParams } from 'react-router-dom'
import * as d3 from 'd3'
import {
insightMapApi,
Expand Down Expand Up @@ -42,6 +42,7 @@ interface SimLink extends d3.SimulationLinkDatum<SimNode> {
}

export default function InsightMapPage() {
const navigate = useNavigate()
const [searchParams] = useSearchParams()
const reportId = searchParams.get('report_id') ?? searchParams.get('report_draft_id')
const initialFocus = searchParams.get('focus') ?? ''
Expand Down Expand Up @@ -131,14 +132,15 @@ export default function InsightMapPage() {
}
}, [reportId, mode])

const handleViewSoapNote = async (node: InsightMapNode) => {
const caseIndexId = node.id.replace(/^caseidx_/, '')
try {
const { data } = await insightMapApi.getSourceLink(caseIndexId)
window.open(data.url, '_blank', 'noopener,noreferrer')
} catch {
const handleViewSoapNote = (node: InsightMapNode) => {
const sessionId = node.data.session_id
if (typeof sessionId !== 'string') {
showToast({ title: 'SOAP note를 열 수 없습니다', kind: 'error' })
return
}
// 리포트 상세는 별도 페이지가 아니라 세션 상세 화면(4단계 워크플로) 안에 있다 —
// ReportsPage의 "보기" 버튼과 동일하게 세션 상세로 이동한다.
navigate(`/sessions/${sessionId}`)
}

// ── Obsidian 스타일 force-directed 그래프 렌더링 ────────────────────────────
Expand Down Expand Up @@ -210,6 +212,11 @@ export default function InsightMapPage() {
return `url(#arrow-${source?.source_layer ?? 'ontology'})`
})

// 관계 라벨(related_to 등)을 모든 엣지에 항상 그리면 그래프가 난잡해지므로
// 기본 상태에서는 숨기고, 노드 선택으로 하이라이트된 엣지에서만 보여준다.
// 호버 시에는 네이티브 <title> 툴팁으로 확인 가능.
linkSel.append('title').text((d) => d.relation)

const edgeLabelSel = g
.append('g')
.selectAll('text')
Expand All @@ -218,6 +225,7 @@ export default function InsightMapPage() {
.attr('font-size', 9)
.attr('fill', '#45475a')
.attr('pointer-events', 'none')
.attr('opacity', 0)
.text((d) => d.relation)

const nodeSel = g
Expand Down Expand Up @@ -286,6 +294,7 @@ export default function InsightMapPage() {
nodeSel.select('text').attr('opacity', (n) => (ids.has(n.id) ? 1 : 0.15))
linkSel.attr('stroke', (_, i) => (linkIdx.has(i) ? '#89b4fa' : '#313244'))
linkSel.attr('stroke-width', (_, i) => (linkIdx.has(i) ? 2.5 : 1.5))
edgeLabelSel.attr('opacity', (_, i) => (linkIdx.has(i) ? 1 : 0))
selectNode(d)
})
.on('mouseover', function () {
Expand All @@ -299,6 +308,7 @@ export default function InsightMapPage() {
nodeSel.select('circle').attr('opacity', 1)
nodeSel.select('text').attr('opacity', 1)
linkSel.attr('stroke', '#313244').attr('stroke-width', 1.5)
edgeLabelSel.attr('opacity', 0)
})

sim.on('tick', () => {
Expand Down Expand Up @@ -331,14 +341,14 @@ export default function InsightMapPage() {
<p className="text-[13px] font-bold">근거 기반 임상 인사이트맵</p>

<div className="flex-1 flex items-center gap-2 max-w-md ml-4">
<div className="flex-1 flex items-center gap-2 bg-[#1e1e2e] border border-[#313244] rounded-lg px-3 py-1.5">
<div className="flex-1 flex items-center gap-2 bg-[#f4faf6] border border-[#d0d7de] rounded-lg px-3 py-1.5">
<Icon name="search" size={14} className="text-[#6c7086]" />
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch(query)}
placeholder="예: 낮은 MLU, 짧은 발화"
className="flex-1 bg-transparent text-[12px] outline-none placeholder-[#6c7086] text-[#cdd6f4]"
className="flex-1 bg-transparent text-[12px] outline-none placeholder-[#6c7086] text-[#1e1e2e]"
/>
</div>
</div>
Expand Down Expand Up @@ -376,15 +386,15 @@ export default function InsightMapPage() {
)}

{/* legend */}
<div className="absolute top-16 left-4 z-10 bg-[#1e1e2e]/85 border border-[#313244] rounded-lg px-3.5 py-2.5 backdrop-blur-sm">
<div className="absolute top-16 left-4 z-10 bg-[#f4faf6]/90 border border-[#d0d7de] rounded-lg px-3.5 py-2.5 backdrop-blur-sm">
<h3 className="text-[10px] tracking-[0.12em] uppercase text-[#6c7086] mb-2">데이터 범위</h3>
{(presentLayers.length ? presentLayers : ['ontology']).map((layer) => (
<div key={layer} className="flex items-center gap-1.5 mb-1 last:mb-0">
<div
className="w-2.5 h-2.5 rounded-full flex-shrink-0"
style={{ background: LAYER_COLOR[layer] ?? '#89b4fa' }}
/>
<span className="text-[11px] text-[#a6adc8]">{LAYER_LABEL[layer] ?? layer}</span>
<span className="text-[11px] text-[#45475a]">{LAYER_LABEL[layer] ?? layer}</span>
</div>
))}
</div>
Expand Down
Loading