mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 10:16:24 +00:00
Merge branch 'main' of https://github.com/hwanny1128/HGZero
This commit is contained in:
commit
038fa40c2d
11
.claude/settings.local.json
Normal file
11
.claude/settings.local.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(git add:*)",
|
||||||
|
"Bash(git commit:*)",
|
||||||
|
"Bash(git push)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,27 +6,22 @@ title AI Service 내부 시퀀스 - 결정사항제안
|
|||||||
participant "SuggestionController" as Controller
|
participant "SuggestionController" as Controller
|
||||||
participant "DecisionSuggestionService" as Service
|
participant "DecisionSuggestionService" as Service
|
||||||
participant "LLMClient" as LLM
|
participant "LLMClient" as LLM
|
||||||
participant "SuggestionRepository" as Repo
|
|
||||||
participant "TranscriptRepository" as TranscriptRepo
|
participant "TranscriptRepository" as TranscriptRepo
|
||||||
database "Azure OpenAI<<E>>" as OpenAI
|
database "Azure OpenAI<<E>>" as OpenAI
|
||||||
|
database "Redis Cache<<E>>" as Cache
|
||||||
database "PostgreSQL<<E>>" as DB
|
database "PostgreSQL<<E>>" as DB
|
||||||
|
|
||||||
== 실시간 결정사항 제안 요청 ==
|
== 실시간 결정사항 제안 요청 ==
|
||||||
|
|
||||||
note over Controller
|
note over Controller
|
||||||
TranscriptService로부터 호출
|
TranscriptService로부터 호출
|
||||||
또는 API 직접 호출:
|
(회의록 자동작성 프로세스 내부)
|
||||||
POST /api/ai/suggestions/decision
|
|
||||||
Body: {
|
|
||||||
"meetingId": "{meetingId}",
|
|
||||||
"transcriptText": "최근 대화 내용"
|
|
||||||
}
|
|
||||||
end note
|
end note
|
||||||
|
|
||||||
Controller -> Service: suggestDecisions(meetingId, transcriptText)
|
Controller -> Service: suggestDecisions(meetingId, transcriptText)
|
||||||
activate Service
|
activate Service
|
||||||
|
|
||||||
== 회의 맥락 및 이전 결정 조회 ==
|
== 회의 맥락 조회 ==
|
||||||
|
|
||||||
Service -> TranscriptRepo: getMeetingContext(meetingId)
|
Service -> TranscriptRepo: getMeetingContext(meetingId)
|
||||||
activate TranscriptRepo
|
activate TranscriptRepo
|
||||||
@ -40,17 +35,15 @@ deactivate DB
|
|||||||
TranscriptRepo --> Service: meetingContext
|
TranscriptRepo --> Service: meetingContext
|
||||||
deactivate TranscriptRepo
|
deactivate TranscriptRepo
|
||||||
|
|
||||||
Service -> Repo: getPreviousDecisions(meetingId)
|
Service -> Cache: GET decisions:{meetingId}
|
||||||
activate Repo
|
activate Cache
|
||||||
|
note right
|
||||||
|
이전에 감지한 결정사항 조회
|
||||||
|
(중복 제거용)
|
||||||
|
end note
|
||||||
|
|
||||||
Repo -> DB: SELECT content FROM ai_suggestions\nWHERE meeting_id = {meetingId}\nAND suggestion_type = 'DECISION'\nAND status = 'APPLIED'
|
Cache --> Service: previousDecisions
|
||||||
activate DB
|
deactivate Cache
|
||||||
|
|
||||||
DB --> Repo: 이미 확정된 결정사항
|
|
||||||
deactivate DB
|
|
||||||
|
|
||||||
Repo --> Service: previousDecisions
|
|
||||||
deactivate Repo
|
|
||||||
|
|
||||||
== LLM 기반 결정사항 패턴 감지 ==
|
== LLM 기반 결정사항 패턴 감지 ==
|
||||||
|
|
||||||
@ -69,7 +62,7 @@ note right
|
|||||||
|
|
||||||
사용자 프롬프트:
|
사용자 프롬프트:
|
||||||
- 회의 참석자: {participants}
|
- 회의 참석자: {participants}
|
||||||
- 이미 확정된 결정: {previousDecisions}
|
- 이미 감지한 결정: {previousDecisions}
|
||||||
- 현재 대화 내용: {transcriptText}
|
- 현재 대화 내용: {transcriptText}
|
||||||
|
|
||||||
지시사항:
|
지시사항:
|
||||||
@ -140,7 +133,7 @@ Service -> Service: 결정사항 검증
|
|||||||
note right
|
note right
|
||||||
검증 기준:
|
검증 기준:
|
||||||
- 신뢰도 70% 이상만 선택
|
- 신뢰도 70% 이상만 선택
|
||||||
- 중복 제거 (이미 확정된 결정)
|
- 중복 제거 (이미 감지한 결정)
|
||||||
- 명확성 검증
|
- 명확성 검증
|
||||||
* 주어, 목적어가 명확한가?
|
* 주어, 목적어가 명확한가?
|
||||||
* 결정 내용이 구체적인가?
|
* 결정 내용이 구체적인가?
|
||||||
@ -155,46 +148,32 @@ loop 각 제안마다
|
|||||||
추가 정보:
|
추가 정보:
|
||||||
- 생성 시각
|
- 생성 시각
|
||||||
- 회의 진행 시점 (분)
|
- 회의 진행 시점 (분)
|
||||||
- 원문 위치 (라인 번호)
|
- 원문 위치 정보
|
||||||
- 상태: PENDING
|
- 고유 ID (UUID)
|
||||||
- 관련 논의사항 참조
|
|
||||||
end note
|
end note
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
== 제안 저장 ==
|
== 임시 캐시 저장 (선택적) ==
|
||||||
|
|
||||||
loop 각 검증된 제안마다
|
Service -> Cache: APPEND decisions:{meetingId}
|
||||||
|
activate Cache
|
||||||
|
note right
|
||||||
|
Redis에 임시 저장:
|
||||||
|
- Key: decisions:{meetingId}
|
||||||
|
- Value: JSON array (제안 목록)
|
||||||
|
- TTL: 2시간 (회의 시간)
|
||||||
|
- APPEND로 기존 목록에 추가
|
||||||
|
|
||||||
Service -> Repo: saveSuggestion(meetingId, decision)
|
목적:
|
||||||
activate Repo
|
- 중복 감지용
|
||||||
|
- 재접속 시 복원용
|
||||||
|
end note
|
||||||
|
|
||||||
Repo -> DB: INSERT INTO ai_suggestions
|
Cache --> Service: 저장 완료
|
||||||
activate DB
|
deactivate Cache
|
||||||
note right
|
|
||||||
저장 데이터:
|
|
||||||
- meeting_id
|
|
||||||
- suggestion_type: 'DECISION'
|
|
||||||
- content: {decision 내용}
|
|
||||||
- category: 결정 카테고리
|
|
||||||
- decision_maker: 결정자
|
|
||||||
- participants: 참여자 목록 (JSON)
|
|
||||||
- confidence_score: 0.0-1.0
|
|
||||||
- extracted_from: 원문
|
|
||||||
- context: 결정 배경
|
|
||||||
- status: PENDING
|
|
||||||
- created_at
|
|
||||||
end note
|
|
||||||
|
|
||||||
DB --> Repo: suggestionId
|
== 응답 반환 ==
|
||||||
deactivate DB
|
|
||||||
|
|
||||||
Repo --> Service: suggestionId
|
|
||||||
deactivate Repo
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
== 응답 구성 ==
|
|
||||||
|
|
||||||
Service -> Service: 응답 데이터 구성
|
Service -> Service: 응답 데이터 구성
|
||||||
note right
|
note right
|
||||||
@ -208,20 +187,22 @@ note right
|
|||||||
"decisionMaker": "김철수",
|
"decisionMaker": "김철수",
|
||||||
"confidence": 0.85,
|
"confidence": 0.85,
|
||||||
"extractedFrom": "원문 발췌",
|
"extractedFrom": "원문 발췌",
|
||||||
"context": "결정 배경 설명",
|
"context": "결정 배경 설명"
|
||||||
"canApply": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"totalCount": 제안 개수,
|
"totalCount": 제안 개수,
|
||||||
"displayHint": "오른쪽 탭 '결정사항' 섹션"
|
"timestamp": "생성 시각"
|
||||||
}
|
}
|
||||||
end note
|
end note
|
||||||
|
|
||||||
Service --> Controller: 결정사항 제안 생성 완료
|
Service --> Controller: 결정사항 제안 목록
|
||||||
deactivate Service
|
deactivate Service
|
||||||
|
|
||||||
Controller --> Controller: 200 OK 응답 반환
|
Controller --> Controller: 이벤트 데이터에 포함하여 반환
|
||||||
note right
|
note right
|
||||||
|
TranscriptSummaryCreated 이벤트에
|
||||||
|
decisionSuggestions 필드로 포함
|
||||||
|
|
||||||
프론트엔드 처리:
|
프론트엔드 처리:
|
||||||
- 오른쪽 "추천" 탭의 "결정사항" 섹션 표시
|
- 오른쪽 "추천" 탭의 "결정사항" 섹션 표시
|
||||||
- "적용" 버튼 활성화
|
- "적용" 버튼 활성화
|
||||||
@ -234,49 +215,36 @@ end note
|
|||||||
|
|
||||||
note over Controller
|
note over Controller
|
||||||
사용자가 "적용" 버튼 클릭 시:
|
사용자가 "적용" 버튼 클릭 시:
|
||||||
PUT /api/ai/suggestions/{suggestionId}/apply
|
프론트엔드에서 직접 Meeting Service 호출
|
||||||
end note
|
|
||||||
|
|
||||||
Controller -> Service: applySuggestion(suggestionId, meetingId)
|
PUT /api/meetings/{meetingId}/transcript
|
||||||
activate Service
|
Body: {
|
||||||
|
"addDecisionSection": {
|
||||||
|
"content": "결정 내용",
|
||||||
|
"category": "기술",
|
||||||
|
"decisionMaker": "김철수"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Service -> Repo: updateSuggestionStatus(suggestionId, "APPLIED")
|
Meeting Service에서 회의록의
|
||||||
activate Repo
|
|
||||||
|
|
||||||
Repo -> DB: UPDATE ai_suggestions\nSET status = 'APPLIED',\napplied_at = NOW()
|
|
||||||
activate DB
|
|
||||||
|
|
||||||
DB --> Repo: 업데이트 완료
|
|
||||||
deactivate DB
|
|
||||||
|
|
||||||
Repo --> Service: 완료
|
|
||||||
deactivate Repo
|
|
||||||
|
|
||||||
Service -> Service: 결정사항을 회의록에 추가
|
|
||||||
note right
|
|
||||||
Meeting Service API 호출하여
|
|
||||||
"결정사항" 섹션에 항목 추가
|
"결정사항" 섹션에 항목 추가
|
||||||
end note
|
end note
|
||||||
|
|
||||||
Service --> Controller: 적용 완료
|
|
||||||
deactivate Service
|
|
||||||
|
|
||||||
Controller --> Controller: 200 OK
|
|
||||||
|
|
||||||
note over Controller, DB
|
note over Controller, DB
|
||||||
처리 시간:
|
처리 시간:
|
||||||
- 맥락 조회: 100-200ms
|
- 맥락 조회: 100-200ms
|
||||||
- LLM 패턴 감지: 2-3초
|
- LLM 패턴 감지: 2-3초
|
||||||
- 검증 및 필터링: 100-200ms
|
- 검증 및 필터링: 100-200ms
|
||||||
- 저장 처리: 200-300ms
|
- 캐시 저장: 50-100ms
|
||||||
총 처리 시간: 약 3-4초
|
총 처리 시간: 약 2.5-3.5초
|
||||||
|
|
||||||
제안 정책:
|
특징:
|
||||||
- 신뢰도 70% 이상만 제안
|
- DB 영구 저장 없음 (임시 데이터)
|
||||||
- 명확한 결정 표현 우선
|
- Redis 캐시만 활용
|
||||||
- 중복 제거 (이미 확정된 것 제외)
|
* 중복 감지용
|
||||||
- 카테고리별로 최대 10개까지
|
* 재접속 복원용
|
||||||
- 실시간으로 계속 감지
|
- 프론트엔드 메모리에서 관리
|
||||||
|
- "적용" 시에만 회의록에 반영
|
||||||
end note
|
end note
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
|
|||||||
@ -6,21 +6,16 @@ title AI Service 내부 시퀀스 - 논의사항제안
|
|||||||
participant "SuggestionController" as Controller
|
participant "SuggestionController" as Controller
|
||||||
participant "DiscussionSuggestionService" as Service
|
participant "DiscussionSuggestionService" as Service
|
||||||
participant "LLMClient" as LLM
|
participant "LLMClient" as LLM
|
||||||
participant "SuggestionRepository" as Repo
|
|
||||||
participant "TranscriptRepository" as TranscriptRepo
|
participant "TranscriptRepository" as TranscriptRepo
|
||||||
database "Azure OpenAI<<E>>" as OpenAI
|
database "Azure OpenAI<<E>>" as OpenAI
|
||||||
|
database "Redis Cache<<E>>" as Cache
|
||||||
database "PostgreSQL<<E>>" as DB
|
database "PostgreSQL<<E>>" as DB
|
||||||
|
|
||||||
== 실시간 논의사항 제안 요청 ==
|
== 실시간 논의사항 제안 요청 ==
|
||||||
|
|
||||||
note over Controller
|
note over Controller
|
||||||
TranscriptService로부터 호출
|
TranscriptService로부터 호출
|
||||||
또는 API 직접 호출:
|
(회의록 자동작성 프로세스 내부)
|
||||||
POST /api/ai/suggestions/discussion
|
|
||||||
Body: {
|
|
||||||
"meetingId": "{meetingId}",
|
|
||||||
"transcriptText": "최근 대화 내용"
|
|
||||||
}
|
|
||||||
end note
|
end note
|
||||||
|
|
||||||
Controller -> Service: suggestDiscussionTopics(meetingId, transcriptText)
|
Controller -> Service: suggestDiscussionTopics(meetingId, transcriptText)
|
||||||
@ -139,42 +134,30 @@ loop 각 제안마다
|
|||||||
- 생성 시각
|
- 생성 시각
|
||||||
- 제안 신뢰도 점수
|
- 제안 신뢰도 점수
|
||||||
- 회의 진행 시점 (분)
|
- 회의 진행 시점 (분)
|
||||||
- 상태: PENDING
|
- 고유 ID (UUID)
|
||||||
end note
|
end note
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
== 제안 저장 ==
|
== 임시 캐시 저장 (선택적) ==
|
||||||
|
|
||||||
loop 각 검증된 제안마다
|
Service -> Cache: SET suggestions:discussion:{meetingId}
|
||||||
|
activate Cache
|
||||||
|
note right
|
||||||
|
Redis에 임시 저장:
|
||||||
|
- Key: suggestions:discussion:{meetingId}
|
||||||
|
- Value: JSON array (제안 목록)
|
||||||
|
- TTL: 2시간 (회의 시간)
|
||||||
|
|
||||||
Service -> Repo: saveSuggestion(meetingId, suggestion)
|
목적:
|
||||||
activate Repo
|
- 재접속 시 복원용
|
||||||
|
- WebSocket 재연결 대응
|
||||||
|
end note
|
||||||
|
|
||||||
Repo -> DB: INSERT INTO ai_suggestions
|
Cache --> Service: 저장 완료
|
||||||
activate DB
|
deactivate Cache
|
||||||
note right
|
|
||||||
저장 데이터:
|
|
||||||
- meeting_id
|
|
||||||
- suggestion_type: 'DISCUSSION'
|
|
||||||
- content: {topic, reason}
|
|
||||||
- priority: HIGH/MEDIUM/LOW
|
|
||||||
- related_agenda: "안건 항목"
|
|
||||||
- estimated_time: 예상 시간
|
|
||||||
- confidence_score: 0.0-1.0
|
|
||||||
- status: PENDING
|
|
||||||
- created_at
|
|
||||||
end note
|
|
||||||
|
|
||||||
DB --> Repo: suggestionId
|
== 응답 반환 ==
|
||||||
deactivate DB
|
|
||||||
|
|
||||||
Repo --> Service: suggestionId
|
|
||||||
deactivate Repo
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
== 응답 구성 ==
|
|
||||||
|
|
||||||
Service -> Service: 응답 데이터 구성
|
Service -> Service: 응답 데이터 구성
|
||||||
note right
|
note right
|
||||||
@ -187,20 +170,22 @@ note right
|
|||||||
"reason": "제안 이유",
|
"reason": "제안 이유",
|
||||||
"priority": "HIGH",
|
"priority": "HIGH",
|
||||||
"relatedAgenda": "관련 안건",
|
"relatedAgenda": "관련 안건",
|
||||||
"estimatedTime": 10,
|
"estimatedTime": 10
|
||||||
"canApply": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"totalCount": 제안 개수,
|
"totalCount": 제안 개수,
|
||||||
"displayHint": "오른쪽 탭에 표시"
|
"timestamp": "생성 시각"
|
||||||
}
|
}
|
||||||
end note
|
end note
|
||||||
|
|
||||||
Service --> Controller: 논의사항 제안 생성 완료
|
Service --> Controller: 논의사항 제안 목록
|
||||||
deactivate Service
|
deactivate Service
|
||||||
|
|
||||||
Controller --> Controller: 200 OK 응답 반환
|
Controller --> Controller: 이벤트 데이터에 포함하여 반환
|
||||||
note right
|
note right
|
||||||
|
TranscriptSummaryCreated 이벤트에
|
||||||
|
discussionSuggestions 필드로 포함
|
||||||
|
|
||||||
프론트엔드 처리:
|
프론트엔드 처리:
|
||||||
- 오른쪽 "추천" 탭에 표시
|
- 오른쪽 "추천" 탭에 표시
|
||||||
- "적용" 버튼 활성화
|
- "적용" 버튼 활성화
|
||||||
@ -214,48 +199,33 @@ end note
|
|||||||
|
|
||||||
note over Controller
|
note over Controller
|
||||||
사용자가 "적용" 버튼 클릭 시:
|
사용자가 "적용" 버튼 클릭 시:
|
||||||
PUT /api/ai/suggestions/{suggestionId}/apply
|
프론트엔드에서 직접 Meeting Service 호출
|
||||||
end note
|
|
||||||
|
|
||||||
Controller -> Service: applySuggestion(suggestionId, meetingId)
|
PUT /api/meetings/{meetingId}/transcript
|
||||||
activate Service
|
Body: {
|
||||||
|
"addDiscussionSection": {
|
||||||
|
"topic": "논의 주제",
|
||||||
|
"content": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Service -> Repo: updateSuggestionStatus(suggestionId, "APPLIED")
|
Meeting Service에서 회의록에
|
||||||
activate Repo
|
|
||||||
|
|
||||||
Repo -> DB: UPDATE ai_suggestions\nSET status = 'APPLIED',\napplied_at = NOW()
|
|
||||||
activate DB
|
|
||||||
|
|
||||||
DB --> Repo: 업데이트 완료
|
|
||||||
deactivate DB
|
|
||||||
|
|
||||||
Repo --> Service: 완료
|
|
||||||
deactivate Repo
|
|
||||||
|
|
||||||
Service -> Service: 논의사항을 회의록에 추가하는 로직 실행
|
|
||||||
note right
|
|
||||||
Meeting Service에 API 호출하여
|
|
||||||
새로운 논의 섹션 추가
|
새로운 논의 섹션 추가
|
||||||
end note
|
end note
|
||||||
|
|
||||||
Service --> Controller: 적용 완료
|
|
||||||
deactivate Service
|
|
||||||
|
|
||||||
Controller --> Controller: 200 OK
|
|
||||||
|
|
||||||
note over Controller, DB
|
note over Controller, DB
|
||||||
처리 시간:
|
처리 시간:
|
||||||
- 맥락 정보 조회: 100-200ms
|
- 맥락 정보 조회: 100-200ms
|
||||||
- LLM 제안 생성: 2-3초
|
- LLM 제안 생성: 2-3초
|
||||||
- 검증 및 필터링: 100-200ms
|
- 검증 및 필터링: 100-200ms
|
||||||
- 저장 처리: 200-300ms
|
- 캐시 저장: 50-100ms
|
||||||
총 처리 시간: 약 3-4초
|
총 처리 시간: 약 2.5-3.5초
|
||||||
|
|
||||||
제안 정책:
|
특징:
|
||||||
- 최대 5개까지만 제안
|
- DB 영구 저장 없음 (임시 데이터)
|
||||||
- 우선순위 HIGH가 1개 이상
|
- Redis 캐시만 활용 (재접속 복원용)
|
||||||
- 이미 논의한 주제는 제외
|
- 프론트엔드 메모리에서 관리
|
||||||
- 제안은 회의 중 언제든 생성 가능
|
- "적용" 시에만 회의록에 반영
|
||||||
end note
|
end note
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
|
|||||||
@ -6,21 +6,16 @@ title AI Service 내부 시퀀스 - 실시간Todo추출
|
|||||||
participant "SuggestionController" as Controller
|
participant "SuggestionController" as Controller
|
||||||
participant "RealtimeTodoService" as Service
|
participant "RealtimeTodoService" as Service
|
||||||
participant "LLMClient" as LLM
|
participant "LLMClient" as LLM
|
||||||
participant "SuggestionRepository" as Repo
|
|
||||||
participant "TranscriptRepository" as TranscriptRepo
|
participant "TranscriptRepository" as TranscriptRepo
|
||||||
database "Azure OpenAI<<E>>" as OpenAI
|
database "Azure OpenAI<<E>>" as OpenAI
|
||||||
|
database "Redis Cache<<E>>" as Cache
|
||||||
database "PostgreSQL<<E>>" as DB
|
database "PostgreSQL<<E>>" as DB
|
||||||
|
|
||||||
== 실시간 액션아이템 추출 요청 ==
|
== 실시간 액션아이템 추출 요청 ==
|
||||||
|
|
||||||
note over Controller
|
note over Controller
|
||||||
TranscriptService로부터 호출
|
TranscriptService로부터 호출
|
||||||
또는 API 직접 호출:
|
(회의록 자동작성 프로세스 내부)
|
||||||
POST /api/ai/suggestions/action-item
|
|
||||||
Body: {
|
|
||||||
"meetingId": "{meetingId}",
|
|
||||||
"transcriptText": "최근 대화 내용"
|
|
||||||
}
|
|
||||||
end note
|
end note
|
||||||
|
|
||||||
Controller -> Service: extractRealtimeActionItems(meetingId, transcriptText)
|
Controller -> Service: extractRealtimeActionItems(meetingId, transcriptText)
|
||||||
@ -40,17 +35,15 @@ deactivate DB
|
|||||||
TranscriptRepo --> Service: meetingContext
|
TranscriptRepo --> Service: meetingContext
|
||||||
deactivate TranscriptRepo
|
deactivate TranscriptRepo
|
||||||
|
|
||||||
Service -> Repo: getPreviousActionItems(meetingId)
|
Service -> Cache: GET action-items:{meetingId}
|
||||||
activate Repo
|
activate Cache
|
||||||
|
note right
|
||||||
|
이전에 추출한 액션아이템 조회
|
||||||
|
(중복 제거용)
|
||||||
|
end note
|
||||||
|
|
||||||
Repo -> DB: SELECT content FROM ai_suggestions\nWHERE meeting_id = {meetingId}\nAND suggestion_type = 'ACTION_ITEM'\nAND status IN ('PENDING', 'APPLIED')
|
Cache --> Service: previousActionItems
|
||||||
activate DB
|
deactivate Cache
|
||||||
|
|
||||||
DB --> Repo: 이미 추출된 액션아이템
|
|
||||||
deactivate DB
|
|
||||||
|
|
||||||
Repo --> Service: previousActionItems
|
|
||||||
deactivate Repo
|
|
||||||
|
|
||||||
== LLM 기반 액션아이템 패턴 감지 ==
|
== LLM 기반 액션아이템 패턴 감지 ==
|
||||||
|
|
||||||
@ -169,47 +162,32 @@ loop 각 제안마다
|
|||||||
추가 정보:
|
추가 정보:
|
||||||
- 생성 시각
|
- 생성 시각
|
||||||
- 회의 진행 시점 (분)
|
- 회의 진행 시점 (분)
|
||||||
- 원문 위치 (라인 번호)
|
- 원문 위치 정보
|
||||||
- 상태: PENDING
|
- 고유 ID (UUID)
|
||||||
- 관련 결정사항 참조
|
|
||||||
- 관련 논의 섹션 참조
|
|
||||||
end note
|
end note
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
== 제안 저장 ==
|
== 임시 캐시 저장 (선택적) ==
|
||||||
|
|
||||||
loop 각 검증된 제안마다
|
Service -> Cache: APPEND action-items:{meetingId}
|
||||||
|
activate Cache
|
||||||
|
note right
|
||||||
|
Redis에 임시 저장:
|
||||||
|
- Key: action-items:{meetingId}
|
||||||
|
- Value: JSON array (제안 목록)
|
||||||
|
- TTL: 2시간 (회의 시간)
|
||||||
|
- APPEND로 기존 목록에 추가
|
||||||
|
|
||||||
Service -> Repo: saveSuggestion(meetingId, actionItem)
|
목적:
|
||||||
activate Repo
|
- 중복 감지용
|
||||||
|
- 재접속 시 복원용
|
||||||
|
end note
|
||||||
|
|
||||||
Repo -> DB: INSERT INTO ai_suggestions
|
Cache --> Service: 저장 완료
|
||||||
activate DB
|
deactivate Cache
|
||||||
note right
|
|
||||||
저장 데이터:
|
|
||||||
- meeting_id
|
|
||||||
- suggestion_type: 'ACTION_ITEM'
|
|
||||||
- content: 할 일 내용
|
|
||||||
- assignee: 담당자
|
|
||||||
- due_date: 마감일
|
|
||||||
- priority: HIGH/MEDIUM/LOW
|
|
||||||
- confidence_score: 0.0-1.0
|
|
||||||
- extracted_from: 원문
|
|
||||||
- related_decision_id: 관련 결정
|
|
||||||
- status: PENDING
|
|
||||||
- created_at
|
|
||||||
end note
|
|
||||||
|
|
||||||
DB --> Repo: suggestionId
|
== 응답 반환 ==
|
||||||
deactivate DB
|
|
||||||
|
|
||||||
Repo --> Service: suggestionId
|
|
||||||
deactivate Repo
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
== 응답 구성 ==
|
|
||||||
|
|
||||||
Service -> Service: 응답 데이터 구성
|
Service -> Service: 응답 데이터 구성
|
||||||
note right
|
note right
|
||||||
@ -224,20 +202,22 @@ note right
|
|||||||
"priority": "HIGH",
|
"priority": "HIGH",
|
||||||
"confidence": 0.85,
|
"confidence": 0.85,
|
||||||
"extractedFrom": "원문 발췌",
|
"extractedFrom": "원문 발췌",
|
||||||
"relatedDecision": "decision-uuid",
|
"relatedDecision": "decision-uuid"
|
||||||
"canApply": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"totalCount": 제안 개수,
|
"totalCount": 제안 개수,
|
||||||
"displayHint": "오른쪽 탭 '액션아이템' 섹션"
|
"timestamp": "생성 시각"
|
||||||
}
|
}
|
||||||
end note
|
end note
|
||||||
|
|
||||||
Service --> Controller: 액션아이템 제안 생성 완료
|
Service --> Controller: 액션아이템 제안 목록
|
||||||
deactivate Service
|
deactivate Service
|
||||||
|
|
||||||
Controller --> Controller: 200 OK 응답 반환
|
Controller --> Controller: 이벤트 데이터에 포함하여 반환
|
||||||
note right
|
note right
|
||||||
|
TranscriptSummaryCreated 이벤트에
|
||||||
|
actionItemSuggestions 필드로 포함
|
||||||
|
|
||||||
프론트엔드 처리:
|
프론트엔드 처리:
|
||||||
- 오른쪽 "추천" 탭의 "액션아이템" 섹션 표시
|
- 오른쪽 "추천" 탭의 "액션아이템" 섹션 표시
|
||||||
- "적용" 버튼 활성화
|
- "적용" 버튼 활성화
|
||||||
@ -251,36 +231,14 @@ end note
|
|||||||
|
|
||||||
note over Controller
|
note over Controller
|
||||||
사용자가 "적용" 버튼 클릭 시:
|
사용자가 "적용" 버튼 클릭 시:
|
||||||
PUT /api/ai/suggestions/{suggestionId}/apply
|
프론트엔드에서 직접 Meeting Service 호출
|
||||||
Body: {
|
|
||||||
"assignee": "김철수" (수정 가능),
|
|
||||||
"dueDate": "2025-02-01" (수정 가능)
|
|
||||||
}
|
|
||||||
end note
|
|
||||||
|
|
||||||
Controller -> Service: applySuggestion(suggestionId, updateData)
|
POST /api/meetings/{meetingId}/todos
|
||||||
activate Service
|
|
||||||
|
|
||||||
Service -> Repo: updateSuggestionStatus(suggestionId, "APPLIED")
|
|
||||||
activate Repo
|
|
||||||
|
|
||||||
Repo -> DB: UPDATE ai_suggestions\nSET status = 'APPLIED',\napplied_at = NOW(),\nassignee = {updateData.assignee},\ndue_date = {updateData.dueDate}
|
|
||||||
activate DB
|
|
||||||
|
|
||||||
DB --> Repo: 업데이트 완료
|
|
||||||
deactivate DB
|
|
||||||
|
|
||||||
Repo --> Service: 완료
|
|
||||||
deactivate Repo
|
|
||||||
|
|
||||||
Service -> Service: Meeting Service에 Todo 생성 요청
|
|
||||||
note right
|
|
||||||
POST /meetings/{meetingId}/todos
|
|
||||||
Body: {
|
Body: {
|
||||||
"content": "할 일 내용",
|
"content": "할 일 내용",
|
||||||
"assignee": "담당자",
|
"assignee": "김철수",
|
||||||
"dueDate": "마감일",
|
"dueDate": "2025-02-01",
|
||||||
"priority": "우선순위",
|
"priority": "HIGH",
|
||||||
"relatedSection": "관련 회의록 섹션"
|
"relatedSection": "관련 회의록 섹션"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,31 +248,26 @@ note right
|
|||||||
- 담당자에게 알림 발송
|
- 담당자에게 알림 발송
|
||||||
end note
|
end note
|
||||||
|
|
||||||
Service --> Controller: 적용 완료
|
|
||||||
deactivate Service
|
|
||||||
|
|
||||||
Controller --> Controller: 200 OK
|
|
||||||
|
|
||||||
note over Controller, DB
|
note over Controller, DB
|
||||||
처리 시간:
|
처리 시간:
|
||||||
- 맥락 조회: 100-200ms
|
- 맥락 조회: 100-200ms
|
||||||
- LLM 패턴 감지: 1-2초
|
- LLM 패턴 감지: 1-2초
|
||||||
- 검증 및 필터링: 100-200ms
|
- 검증 및 필터링: 100-200ms
|
||||||
- 저장 처리: 200-300ms
|
- 캐시 저장: 50-100ms
|
||||||
총 처리 시간: 약 2-3초
|
총 처리 시간: 약 1.5-2.5초
|
||||||
|
|
||||||
제안 정책:
|
특징:
|
||||||
- 신뢰도 70% 이상만 제안
|
- DB 영구 저장 없음 (임시 데이터)
|
||||||
- 명확한 액션 표현 우선
|
- Redis 캐시만 활용
|
||||||
- 중복 제거 (유사도 90% 기준)
|
* 중복 감지용
|
||||||
- 실시간으로 계속 감지
|
* 재접속 복원용
|
||||||
- 최대 20개까지 누적 표시
|
- 프론트엔드 메모리에서 관리
|
||||||
- 적용된 것은 회색 처리
|
- "적용" 시에만 Todo 생성
|
||||||
|
|
||||||
차이점 (회의 종료 후 Todo 추출과):
|
차이점 (회의 종료 후 Todo 추출과):
|
||||||
- 실시간: 5초마다 즉시 추출
|
- 실시간: 5초마다 즉시 추출, 임시 제안
|
||||||
- 종료 후: 전체 회의록 기반 종합 추출
|
- 종료 후: 전체 회의록 기반 종합 추출, 자동 생성
|
||||||
- 실시간은 "후보"로 제시
|
- 실시간은 "후보"로 제시, 사용자 선택
|
||||||
- 종료 후는 "확정" 추출 후 자동 생성
|
- 종료 후는 "확정" 추출 후 자동 생성
|
||||||
end note
|
end note
|
||||||
|
|
||||||
|
|||||||
@ -82,7 +82,7 @@
|
|||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<button class="icon-btn" onclick="history.back()">←</button>
|
<button class="back-btn" onclick="history.back()">←</button>
|
||||||
<h1 class="header-title">검증 완료</h1>
|
<h1 class="header-title">검증 완료</h1>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@ -134,7 +134,7 @@
|
|||||||
<span class="text-caption text-muted">2명 검증 완료</span>
|
<span class="text-caption text-muted">2명 검증 완료</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-ghost btn-sm" onclick="viewSection(0)">보기</button>
|
<button class="btn btn-secondary btn-sm" onclick="viewSection(0)">보기</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 섹션 2 - 검증 완료 + 잠금 -->
|
<!-- 섹션 2 - 검증 완료 + 잠금 -->
|
||||||
@ -154,7 +154,7 @@
|
|||||||
<span class="text-caption text-muted">3명 검증 완료 · 잠금됨</span>
|
<span class="text-caption text-muted">3명 검증 완료 · 잠금됨</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-ghost btn-sm" onclick="unlockSection(1)">잠금해제</button>
|
<button class="btn btn-secondary btn-sm" onclick="unlockSection(1)">잠금해제</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 섹션 3 - 미검증 -->
|
<!-- 섹션 3 - 미검증 -->
|
||||||
@ -169,7 +169,7 @@
|
|||||||
<span class="text-caption text-muted">1명 검증 완료</span>
|
<span class="text-caption text-muted">1명 검증 완료</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-primary btn-sm" onclick="verifySection(2)">검증하기</button>
|
<button class="btn btn-primary btn-sm" onclick="verifySection(2)">검증하기</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 섹션 4 - 미검증 -->
|
<!-- 섹션 4 - 미검증 -->
|
||||||
@ -181,7 +181,7 @@
|
|||||||
<span class="text-caption text-muted">아직 검증되지 않음</span>
|
<span class="text-caption text-muted">아직 검증되지 않음</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-primary btn-sm" onclick="verifySection(3)">검증하기</button>
|
<button class="btn btn-primary btn-sm" onclick="verifySection(3)">검증하기</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -209,8 +209,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn-ghost" onclick="closeModal('sectionModal')">닫기</button>
|
<button class="btn btn-secondary btn-sm" onclick="closeModal('sectionModal')">닫기</button>
|
||||||
<button class="btn-primary" onclick="editSection()">편집</button>
|
<button class="btn btn-primary btn-sm" onclick="editSection()">편집</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -229,8 +229,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn-ghost" onclick="closeModal('verifyModal')">취소</button>
|
<button class="btn btn-secondary btn-sm" onclick="closeModal('verifyModal')">취소</button>
|
||||||
<button class="btn-primary" onclick="confirmVerification()">검증 완료</button>
|
<button class="btn btn-primary btn-sm" onclick="confirmVerification()">검증 완료</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -244,13 +244,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p class="text-small mb-md">이 섹션의 잠금을 해제하시겠습니까?</p>
|
<p class="text-small mb-md">이 섹션의 잠금을 해제하시겠습니까?</p>
|
||||||
<div class="card" style="background: var(--warning); color: var(--white);">
|
<div class="card" style="background: transparent; color: var(--error); border: 1px solid var(--error);">
|
||||||
<p class="text-small font-medium">⚠️ 잠금 해제 시 다른 참석자들이 내용을 수정할 수 있습니다.</p>
|
<p class="text-small font-medium">⚠️ 잠금 해제 시 다른 참석자들이 내용을 수정할 수 있습니다.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn-ghost" onclick="closeModal('unlockModal')">취소</button>
|
<button class="btn btn-sm" style="background: transparent; color: var(--error); border: 1px solid var(--error);" onclick="closeModal('unlockModal')">취소</button>
|
||||||
<button class="btn-error" onclick="confirmUnlock()">잠금 해제</button>
|
<button class="btn btn-error btn-sm" onclick="confirmUnlock()">잠금 해제</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user