기능구현중
This commit is contained in:
@@ -138,9 +138,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 오른쪽: 회의 내용 -->
|
||||
<!-- 오른쪽: 회의 내용 + AI 분석 -->
|
||||
<div class="col-md-8">
|
||||
<div class="card h-100">
|
||||
<!-- 회의 내용 -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<strong>회의 내용</strong>
|
||||
</div>
|
||||
@@ -149,9 +150,9 @@
|
||||
v-if="isEditing"
|
||||
class="form-control border-0 h-100"
|
||||
v-model="form.rawContent"
|
||||
style="min-height: 500px; resize: none;"
|
||||
style="min-height: 300px; resize: none;"
|
||||
></textarea>
|
||||
<div v-else class="p-3" style="min-height: 500px; white-space: pre-wrap;">{{ meeting.rawContent || '(내용 없음)' }}</div>
|
||||
<div v-else class="p-3" style="min-height: 200px; white-space: pre-wrap;">{{ meeting.rawContent || '(내용 없음)' }}</div>
|
||||
</div>
|
||||
<div v-if="isEditing" class="card-footer d-flex justify-content-end">
|
||||
<button class="btn btn-secondary me-2" @click="cancelEdit">취소</button>
|
||||
@@ -163,6 +164,94 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI 분석 결과 -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<i class="bi bi-robot me-2"></i><strong>AI 분석 결과</strong>
|
||||
<span v-if="meeting.aiStatus === 'CONFIRMED'" class="badge bg-success ms-2">확정됨</span>
|
||||
<span v-else-if="meeting.aiStatus === 'PENDING'" class="badge bg-warning ms-2">미확정</span>
|
||||
<span v-else class="badge bg-secondary ms-2">미분석</span>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
v-if="meeting.aiStatus !== 'CONFIRMED'"
|
||||
class="btn btn-sm btn-outline-primary me-2"
|
||||
@click="analyzeContent"
|
||||
:disabled="isAnalyzing"
|
||||
>
|
||||
<span v-if="isAnalyzing"><span class="spinner-border spinner-border-sm me-1"></span></span>
|
||||
<i v-else class="bi bi-magic me-1"></i>
|
||||
{{ meeting.aiStatus === 'PENDING' ? '재분석' : 'AI 분석' }}
|
||||
</button>
|
||||
<button
|
||||
v-if="meeting.aiStatus === 'PENDING' && aiResult"
|
||||
class="btn btn-sm btn-success"
|
||||
@click="confirmAnalysis"
|
||||
:disabled="isConfirming"
|
||||
>
|
||||
<span v-if="isConfirming"><span class="spinner-border spinner-border-sm me-1"></span></span>
|
||||
<i v-else class="bi bi-check-lg me-1"></i>확정하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- 분석 결과 없음 -->
|
||||
<div v-if="!aiResult" class="text-center text-muted py-4">
|
||||
<i class="bi bi-robot display-4"></i>
|
||||
<p class="mt-2">AI 분석을 실행해주세요.</p>
|
||||
</div>
|
||||
|
||||
<!-- 분석 결과 표시 -->
|
||||
<div v-else>
|
||||
<!-- 요약 -->
|
||||
<div class="alert alert-info mb-3">
|
||||
<i class="bi bi-lightbulb me-2"></i>
|
||||
<strong>요약:</strong> {{ aiResult.summary }}
|
||||
</div>
|
||||
|
||||
<!-- 안건 목록 -->
|
||||
<div v-for="agenda in aiResult.agendas" :key="agenda.no" class="border rounded p-3 mb-3">
|
||||
<h6 class="mb-2">
|
||||
<span class="badge bg-secondary me-2">{{ agenda.no }}</span>
|
||||
{{ agenda.title }}
|
||||
<span :class="getStatusBadge(agenda.status)" class="ms-2">{{ getStatusText(agenda.status) }}</span>
|
||||
</h6>
|
||||
<p class="text-muted small mb-2">{{ agenda.content }}</p>
|
||||
<p v-if="agenda.decision" class="mb-2">
|
||||
<i class="bi bi-check-circle text-success me-1"></i>
|
||||
<strong>결정:</strong> {{ agenda.decision }}
|
||||
</p>
|
||||
|
||||
<!-- TODO 항목 -->
|
||||
<div v-if="agenda.todos && agenda.todos.length > 0" class="mt-2">
|
||||
<div v-for="(todo, tIdx) in agenda.todos" :key="tIdx" class="form-check">
|
||||
<input
|
||||
v-if="meeting.aiStatus === 'PENDING'"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
:id="`todo-${agenda.no}-${tIdx}`"
|
||||
v-model="selectedTodos"
|
||||
:value="{ agendaNo: agenda.no, todoIndex: tIdx, title: todo.title, assignee: todo.assignee }"
|
||||
/>
|
||||
<label class="form-check-label" :for="`todo-${agenda.no}-${tIdx}`">
|
||||
<i class="bi bi-check2-square text-primary me-1"></i>
|
||||
{{ todo.title }}
|
||||
<span v-if="todo.assignee" class="badge bg-light text-dark ms-1">@{{ todo.assignee }}</span>
|
||||
</label>
|
||||
<small class="d-block text-muted ms-4">{{ todo.reason }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="meeting.aiStatus === 'PENDING'" class="alert alert-warning mb-0">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
TODO로 생성할 항목을 선택한 후 [확정하기]를 클릭하세요.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -241,6 +330,12 @@ const isLoading = ref(true)
|
||||
const isEditing = ref(false)
|
||||
const isSaving = ref(false)
|
||||
|
||||
// AI 분석 관련
|
||||
const aiResult = ref<any>(null)
|
||||
const isAnalyzing = ref(false)
|
||||
const isConfirming = ref(false)
|
||||
const selectedTodos = ref<any[]>([])
|
||||
|
||||
const form = ref({
|
||||
meetingTitle: '',
|
||||
meetingType: 'PROJECT' as 'PROJECT' | 'INTERNAL',
|
||||
@@ -285,6 +380,20 @@ async function loadMeeting() {
|
||||
attendees.value = res.attendees || []
|
||||
agendas.value = res.agendas || []
|
||||
|
||||
// AI 분석 결과 로드
|
||||
if (res.meeting.aiSummary) {
|
||||
try {
|
||||
aiResult.value = typeof res.meeting.aiSummary === 'string'
|
||||
? JSON.parse(res.meeting.aiSummary)
|
||||
: res.meeting.aiSummary
|
||||
} catch (e) {
|
||||
aiResult.value = null
|
||||
}
|
||||
} else {
|
||||
aiResult.value = null
|
||||
}
|
||||
selectedTodos.value = []
|
||||
|
||||
// 폼 초기화
|
||||
form.value = {
|
||||
meetingTitle: res.meeting.meetingTitle,
|
||||
@@ -428,6 +537,63 @@ function formatDate(dateStr: string) {
|
||||
const d = new Date(dateStr)
|
||||
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`
|
||||
}
|
||||
|
||||
// AI 분석
|
||||
async function analyzeContent() {
|
||||
if (!meeting.value?.rawContent) {
|
||||
alert('분석할 회의 내용이 없습니다.')
|
||||
return
|
||||
}
|
||||
|
||||
isAnalyzing.value = true
|
||||
try {
|
||||
const res = await $fetch<{ result: any }>(`/api/meeting/${meetingId.value}/analyze`, { method: 'POST' })
|
||||
aiResult.value = res.result
|
||||
meeting.value.aiStatus = 'PENDING'
|
||||
selectedTodos.value = []
|
||||
alert('AI 분석이 완료되었습니다.')
|
||||
} catch (e: any) {
|
||||
alert(e.data?.message || 'AI 분석에 실패했습니다.')
|
||||
} finally {
|
||||
isAnalyzing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmAnalysis() {
|
||||
if (selectedTodos.value.length === 0) {
|
||||
if (!confirm('선택된 TODO가 없습니다. 그래도 확정하시겠습니까?')) return
|
||||
}
|
||||
|
||||
isConfirming.value = true
|
||||
try {
|
||||
const res = await $fetch<{ message: string }>(`/api/meeting/${meetingId.value}/confirm`, {
|
||||
method: 'POST',
|
||||
body: { selectedTodos: selectedTodos.value }
|
||||
})
|
||||
meeting.value.aiStatus = 'CONFIRMED'
|
||||
alert(res.message)
|
||||
} catch (e: any) {
|
||||
alert(e.data?.message || '확정에 실패했습니다.')
|
||||
} finally {
|
||||
isConfirming.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusBadge(status: string) {
|
||||
return {
|
||||
'DECIDED': 'badge bg-success',
|
||||
'PENDING': 'badge bg-warning',
|
||||
'IN_PROGRESS': 'badge bg-info'
|
||||
}[status] || 'badge bg-secondary'
|
||||
}
|
||||
|
||||
function getStatusText(status: string) {
|
||||
return {
|
||||
'DECIDED': '결정됨',
|
||||
'PENDING': '미결정',
|
||||
'IN_PROGRESS': '진행중'
|
||||
}[status] || status
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user