mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 13:46:24 +00:00
ai 실행환경 설정
This commit is contained in:
parent
d2a92bcc20
commit
869ce3bbd4
281
ai/logs/ai-service.log
Normal file
281
ai/logs/ai-service.log
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
2025-10-24 09:23:35 [main] INFO com.unicorn.hgzero.ai.AiApplication - Starting AiApplication using Java 21.0.8 with PID 92971 (/Users/daewoong/home/workspace/HGZero/ai/build/classes/java/main started by daewoong in /Users/daewoong/home/workspace/HGZero/ai)
|
||||||
|
2025-10-24 09:23:35 [main] DEBUG com.unicorn.hgzero.ai.AiApplication - Running with Spring Boot v3.3.0, Spring v6.1.8
|
||||||
|
2025-10-24 09:23:35 [main] INFO com.unicorn.hgzero.ai.AiApplication - No active profile set, falling back to 1 default profile: "default"
|
||||||
|
2025-10-24 09:23:36 [main] WARN o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'jpaAuditingHandler' defined in null: Cannot register bean definition [Root bean: class [org.springframework.data.auditing.AuditingHandler]; scope=; abstract=false; lazyInit=null; autowireMode=2; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=from; initMethodNames=null; destroyMethodNames=null] for bean 'jpaAuditingHandler' since there is already [Root bean: class [org.springframework.data.auditing.AuditingHandler]; scope=; abstract=false; lazyInit=null; autowireMode=2; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=from; initMethodNames=null; destroyMethodNames=null] bound.
|
||||||
|
2025-10-24 09:23:36 [main] INFO o.s.b.a.l.ConditionEvaluationReportLogger -
|
||||||
|
|
||||||
|
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
|
||||||
|
2025-10-24 09:23:36 [main] ERROR o.s.b.d.LoggingFailureAnalysisReporter -
|
||||||
|
|
||||||
|
***************************
|
||||||
|
APPLICATION FAILED TO START
|
||||||
|
***************************
|
||||||
|
|
||||||
|
Description:
|
||||||
|
|
||||||
|
The bean 'jpaAuditingHandler' could not be registered. A bean with that name has already been defined and overriding is disabled.
|
||||||
|
|
||||||
|
Action:
|
||||||
|
|
||||||
|
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
|
||||||
|
|
||||||
|
2025-10-24 09:42:56 [main] INFO com.unicorn.hgzero.ai.AiApplication - Starting AiApplication using Java 21.0.8 with PID 93771 (/Users/daewoong/home/workspace/HGZero/ai/build/classes/java/main started by daewoong in /Users/daewoong/home/workspace/HGZero/ai)
|
||||||
|
2025-10-24 09:42:56 [main] DEBUG com.unicorn.hgzero.ai.AiApplication - Running with Spring Boot v3.3.5, Spring v6.1.14
|
||||||
|
2025-10-24 09:42:56 [main] INFO com.unicorn.hgzero.ai.AiApplication - No active profile set, falling back to 1 default profile: "default"
|
||||||
|
2025-10-24 09:42:56 [main] WARN o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'jpaAuditingHandler' defined in null: Cannot register bean definition [Root bean: class [org.springframework.data.auditing.AuditingHandler]; scope=; abstract=false; lazyInit=null; autowireMode=2; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=from; initMethodNames=null; destroyMethodNames=null] for bean 'jpaAuditingHandler' since there is already [Root bean: class [org.springframework.data.auditing.AuditingHandler]; scope=; abstract=false; lazyInit=null; autowireMode=2; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=from; initMethodNames=null; destroyMethodNames=null] bound.
|
||||||
|
2025-10-24 09:42:56 [main] INFO o.s.b.a.l.ConditionEvaluationReportLogger -
|
||||||
|
|
||||||
|
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
|
||||||
|
2025-10-24 09:42:56 [main] ERROR o.s.b.d.LoggingFailureAnalysisReporter -
|
||||||
|
|
||||||
|
***************************
|
||||||
|
APPLICATION FAILED TO START
|
||||||
|
***************************
|
||||||
|
|
||||||
|
Description:
|
||||||
|
|
||||||
|
The bean 'jpaAuditingHandler' could not be registered. A bean with that name has already been defined and overriding is disabled.
|
||||||
|
|
||||||
|
Action:
|
||||||
|
|
||||||
|
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
|
||||||
|
|
||||||
|
2025-10-24 09:43:58 [main] INFO com.unicorn.hgzero.ai.AiApplication - Starting AiApplication using Java 21.0.8 with PID 93809 (/Users/daewoong/home/workspace/HGZero/ai/build/classes/java/main started by daewoong in /Users/daewoong/home/workspace/HGZero/ai)
|
||||||
|
2025-10-24 09:43:58 [main] DEBUG com.unicorn.hgzero.ai.AiApplication - Running with Spring Boot v3.3.5, Spring v6.1.14
|
||||||
|
2025-10-24 09:43:58 [main] INFO com.unicorn.hgzero.ai.AiApplication - No active profile set, falling back to 1 default profile: "default"
|
||||||
|
2025-10-24 09:43:58 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
|
||||||
|
2025-10-24 09:43:58 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Bootstrapping Spring Data JPA repositories in DEFAULT mode.
|
||||||
|
2025-10-24 09:43:58 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 65 ms. Found 1 JPA repository interface.
|
||||||
|
2025-10-24 09:43:58 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
|
||||||
|
2025-10-24 09:43:58 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
|
||||||
|
2025-10-24 09:43:58 [main] INFO o.s.d.r.c.RepositoryConfigurationExtensionSupport - Spring Data Redis - Could not safely identify store assignment for repository candidate interface com.unicorn.hgzero.ai.infra.gateway.repository.ProcessedTranscriptJpaRepository; If you want this repository to be a Redis repository, consider annotating your entities with one of these annotations: org.springframework.data.redis.core.RedisHash (preferred), or consider extending one of the following types with your repository: org.springframework.data.keyvalue.repository.KeyValueRepository
|
||||||
|
2025-10-24 09:43:58 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 3 ms. Found 0 Redis repository interfaces.
|
||||||
|
2025-10-24 09:43:59 [main] INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat initialized with port 8083 (http)
|
||||||
|
2025-10-24 09:43:59 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
|
||||||
|
2025-10-24 09:43:59 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.31]
|
||||||
|
2025-10-24 09:43:59 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
|
||||||
|
2025-10-24 09:43:59 [main] INFO o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 990 ms
|
||||||
|
2025-10-24 09:43:59 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
|
||||||
|
2025-10-24 09:43:59 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 6.5.3.Final
|
||||||
|
2025-10-24 09:43:59 [main] INFO o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration boolean -> org.hibernate.type.BasicTypeReference@24c84e65
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration boolean -> org.hibernate.type.BasicTypeReference@24c84e65
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.lang.Boolean -> org.hibernate.type.BasicTypeReference@24c84e65
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration numeric_boolean -> org.hibernate.type.BasicTypeReference@7337bd2e
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration org.hibernate.type.NumericBooleanConverter -> org.hibernate.type.BasicTypeReference@7337bd2e
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration true_false -> org.hibernate.type.BasicTypeReference@4604e051
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration org.hibernate.type.TrueFalseConverter -> org.hibernate.type.BasicTypeReference@4604e051
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration yes_no -> org.hibernate.type.BasicTypeReference@4535bdc6
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration org.hibernate.type.YesNoConverter -> org.hibernate.type.BasicTypeReference@4535bdc6
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration byte -> org.hibernate.type.BasicTypeReference@23e86863
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration byte -> org.hibernate.type.BasicTypeReference@23e86863
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.lang.Byte -> org.hibernate.type.BasicTypeReference@23e86863
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration binary -> org.hibernate.type.BasicTypeReference@6df87ffd
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration byte[] -> org.hibernate.type.BasicTypeReference@6df87ffd
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration [B -> org.hibernate.type.BasicTypeReference@6df87ffd
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration binary_wrapper -> org.hibernate.type.BasicTypeReference@c1f0c7b
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration wrapper-binary -> org.hibernate.type.BasicTypeReference@c1f0c7b
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration image -> org.hibernate.type.BasicTypeReference@642c5bb3
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration blob -> org.hibernate.type.BasicTypeReference@4e79c25
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.sql.Blob -> org.hibernate.type.BasicTypeReference@4e79c25
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration materialized_blob -> org.hibernate.type.BasicTypeReference@2ace1cd3
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration materialized_blob_wrapper -> org.hibernate.type.BasicTypeReference@5e46a125
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration short -> org.hibernate.type.BasicTypeReference@5831989d
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration short -> org.hibernate.type.BasicTypeReference@5831989d
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.lang.Short -> org.hibernate.type.BasicTypeReference@5831989d
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration integer -> org.hibernate.type.BasicTypeReference@608f310a
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration int -> org.hibernate.type.BasicTypeReference@608f310a
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.lang.Integer -> org.hibernate.type.BasicTypeReference@608f310a
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration long -> org.hibernate.type.BasicTypeReference@3a7d914c
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration long -> org.hibernate.type.BasicTypeReference@3a7d914c
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.lang.Long -> org.hibernate.type.BasicTypeReference@3a7d914c
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration float -> org.hibernate.type.BasicTypeReference@515940af
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration float -> org.hibernate.type.BasicTypeReference@515940af
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.lang.Float -> org.hibernate.type.BasicTypeReference@515940af
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration double -> org.hibernate.type.BasicTypeReference@5f8df69
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration double -> org.hibernate.type.BasicTypeReference@5f8df69
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.lang.Double -> org.hibernate.type.BasicTypeReference@5f8df69
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration big_integer -> org.hibernate.type.BasicTypeReference@1ce6a9bd
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.math.BigInteger -> org.hibernate.type.BasicTypeReference@1ce6a9bd
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration big_decimal -> org.hibernate.type.BasicTypeReference@4a47bc9c
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.math.BigDecimal -> org.hibernate.type.BasicTypeReference@4a47bc9c
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration character -> org.hibernate.type.BasicTypeReference@5100c143
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration char -> org.hibernate.type.BasicTypeReference@5100c143
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.lang.Character -> org.hibernate.type.BasicTypeReference@5100c143
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration character_nchar -> org.hibernate.type.BasicTypeReference@12404f9d
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration string -> org.hibernate.type.BasicTypeReference@3b42b729
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.lang.String -> org.hibernate.type.BasicTypeReference@3b42b729
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration nstring -> org.hibernate.type.BasicTypeReference@4c164f81
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration characters -> org.hibernate.type.BasicTypeReference@1bcb8599
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration char[] -> org.hibernate.type.BasicTypeReference@1bcb8599
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration [C -> org.hibernate.type.BasicTypeReference@1bcb8599
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration wrapper-characters -> org.hibernate.type.BasicTypeReference@b671dda
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration text -> org.hibernate.type.BasicTypeReference@25b20860
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration ntext -> org.hibernate.type.BasicTypeReference@5ba63110
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration clob -> org.hibernate.type.BasicTypeReference@1c0680b0
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.sql.Clob -> org.hibernate.type.BasicTypeReference@1c0680b0
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration nclob -> org.hibernate.type.BasicTypeReference@2f3cd727
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.sql.NClob -> org.hibernate.type.BasicTypeReference@2f3cd727
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration materialized_clob -> org.hibernate.type.BasicTypeReference@1af82ba8
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration materialized_clob_char_array -> org.hibernate.type.BasicTypeReference@703cb756
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration materialized_clob_character_array -> org.hibernate.type.BasicTypeReference@5897aae1
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration materialized_nclob -> org.hibernate.type.BasicTypeReference@11dbcb3b
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration materialized_nclob_character_array -> org.hibernate.type.BasicTypeReference@4aa517c3
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration materialized_nclob_char_array -> org.hibernate.type.BasicTypeReference@5f369fc6
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration Duration -> org.hibernate.type.BasicTypeReference@3a13f663
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.time.Duration -> org.hibernate.type.BasicTypeReference@3a13f663
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration LocalDateTime -> org.hibernate.type.BasicTypeReference@75de7009
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.time.LocalDateTime -> org.hibernate.type.BasicTypeReference@75de7009
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration LocalDate -> org.hibernate.type.BasicTypeReference@17a77a7e
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.time.LocalDate -> org.hibernate.type.BasicTypeReference@17a77a7e
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration LocalTime -> org.hibernate.type.BasicTypeReference@7c840fe3
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.time.LocalTime -> org.hibernate.type.BasicTypeReference@7c840fe3
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration OffsetDateTime -> org.hibernate.type.BasicTypeReference@59014efe
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.time.OffsetDateTime -> org.hibernate.type.BasicTypeReference@59014efe
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration OffsetDateTimeWithTimezone -> org.hibernate.type.BasicTypeReference@5f5923ef
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration OffsetDateTimeWithoutTimezone -> org.hibernate.type.BasicTypeReference@7381d6f0
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration OffsetTime -> org.hibernate.type.BasicTypeReference@2f262474
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.time.OffsetTime -> org.hibernate.type.BasicTypeReference@2f262474
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration OffsetTimeUtc -> org.hibernate.type.BasicTypeReference@7c03f9d0
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration OffsetTimeWithTimezone -> org.hibernate.type.BasicTypeReference@6ad3fbe4
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration OffsetTimeWithoutTimezone -> org.hibernate.type.BasicTypeReference@17189618
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration ZonedDateTime -> org.hibernate.type.BasicTypeReference@983050b
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.time.ZonedDateTime -> org.hibernate.type.BasicTypeReference@983050b
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration ZonedDateTimeWithTimezone -> org.hibernate.type.BasicTypeReference@6aadb092
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration ZonedDateTimeWithoutTimezone -> org.hibernate.type.BasicTypeReference@1f547af8
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration date -> org.hibernate.type.BasicTypeReference@4caf875c
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.sql.Date -> org.hibernate.type.BasicTypeReference@4caf875c
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration time -> org.hibernate.type.BasicTypeReference@5d15789f
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.sql.Time -> org.hibernate.type.BasicTypeReference@5d15789f
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration timestamp -> org.hibernate.type.BasicTypeReference@5abb7a8f
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.sql.Timestamp -> org.hibernate.type.BasicTypeReference@5abb7a8f
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.util.Date -> org.hibernate.type.BasicTypeReference@5abb7a8f
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration calendar -> org.hibernate.type.BasicTypeReference@6684589a
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.util.Calendar -> org.hibernate.type.BasicTypeReference@6684589a
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.util.GregorianCalendar -> org.hibernate.type.BasicTypeReference@6684589a
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration calendar_date -> org.hibernate.type.BasicTypeReference@5621a671
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration calendar_time -> org.hibernate.type.BasicTypeReference@2006fdaa
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration instant -> org.hibernate.type.BasicTypeReference@21688427
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.time.Instant -> org.hibernate.type.BasicTypeReference@21688427
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration uuid -> org.hibernate.type.BasicTypeReference@656c5818
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.util.UUID -> org.hibernate.type.BasicTypeReference@656c5818
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration pg-uuid -> org.hibernate.type.BasicTypeReference@656c5818
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration uuid-binary -> org.hibernate.type.BasicTypeReference@3e2578ea
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration uuid-char -> org.hibernate.type.BasicTypeReference@29592929
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration class -> org.hibernate.type.BasicTypeReference@4cf5d999
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.lang.Class -> org.hibernate.type.BasicTypeReference@4cf5d999
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration currency -> org.hibernate.type.BasicTypeReference@4bdef487
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration Currency -> org.hibernate.type.BasicTypeReference@4bdef487
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.util.Currency -> org.hibernate.type.BasicTypeReference@4bdef487
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration locale -> org.hibernate.type.BasicTypeReference@5ea9373e
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.util.Locale -> org.hibernate.type.BasicTypeReference@5ea9373e
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration serializable -> org.hibernate.type.BasicTypeReference@3e595da3
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.io.Serializable -> org.hibernate.type.BasicTypeReference@3e595da3
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration timezone -> org.hibernate.type.BasicTypeReference@5c0272e0
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.util.TimeZone -> org.hibernate.type.BasicTypeReference@5c0272e0
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration ZoneOffset -> org.hibernate.type.BasicTypeReference@60c4cf2b
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.time.ZoneOffset -> org.hibernate.type.BasicTypeReference@60c4cf2b
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration url -> org.hibernate.type.BasicTypeReference@774304ca
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.net.URL -> org.hibernate.type.BasicTypeReference@774304ca
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration vector -> org.hibernate.type.BasicTypeReference@303fbc4
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration row_version -> org.hibernate.type.BasicTypeReference@4cd90c36
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration object -> org.hibernate.type.JavaObjectType@3dbbed3e
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.lang.Object -> org.hibernate.type.JavaObjectType@3dbbed3e
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration null -> org.hibernate.type.NullType@64540344
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration imm_date -> org.hibernate.type.BasicTypeReference@b2d8dcd
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration imm_time -> org.hibernate.type.BasicTypeReference@1397b141
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration imm_timestamp -> org.hibernate.type.BasicTypeReference@579dde54
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration imm_calendar -> org.hibernate.type.BasicTypeReference@30b9728f
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration imm_calendar_date -> org.hibernate.type.BasicTypeReference@6b899971
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration imm_calendar_time -> org.hibernate.type.BasicTypeReference@453a30f8
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration imm_binary -> org.hibernate.type.BasicTypeReference@7cec3975
|
||||||
|
2025-10-24 09:43:59 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration imm_serializable -> org.hibernate.type.BasicTypeReference@73a116d
|
||||||
|
2025-10-24 09:43:59 [main] INFO o.s.o.j.p.SpringPersistenceUnitInfo - No LoadTimeWeaver setup: ignoring JPA class transformer
|
||||||
|
2025-10-24 09:43:59 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
|
||||||
|
2025-10-24 09:43:59 [main] INFO com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@720c0996
|
||||||
|
2025-10-24 09:43:59 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.h.t.d.sql.spi.DdlTypeRegistry - addDescriptor(2003, org.hibernate.type.descriptor.sql.internal.ArrayDdlTypeImpl@15549dd7) replaced previous registration(org.hibernate.type.descriptor.sql.internal.ArrayDdlTypeImpl@1e75af65)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.h.t.d.sql.spi.DdlTypeRegistry - addDescriptor(6, org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType@25a2c4dc) replaced previous registration(org.hibernate.type.descriptor.sql.internal.DdlTypeImpl@29d81c22)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.h.t.d.jdbc.spi.JdbcTypeRegistry - addDescriptor(2004, BlobTypeDescriptor(BLOB_BINDING)) replaced previous registration(BlobTypeDescriptor(DEFAULT))
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.h.t.d.jdbc.spi.JdbcTypeRegistry - addDescriptor(2005, ClobTypeDescriptor(CLOB_BINDING)) replaced previous registration(ClobTypeDescriptor(DEFAULT))
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration JAVA_OBJECT -> org.hibernate.type.JavaObjectType@35e357b
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.lang.Object -> org.hibernate.type.JavaObjectType@35e357b
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Type registration key [java.lang.Object] overrode previous entry : `org.hibernate.type.JavaObjectType@3dbbed3e`
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration org.hibernate.type.DurationType -> basicType@1(java.time.Duration,3015)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration Duration -> basicType@1(java.time.Duration,3015)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.time.Duration -> basicType@1(java.time.Duration,3015)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration org.hibernate.type.OffsetDateTimeType -> basicType@2(java.time.OffsetDateTime,3003)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration OffsetDateTime -> basicType@2(java.time.OffsetDateTime,3003)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.time.OffsetDateTime -> basicType@2(java.time.OffsetDateTime,3003)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration org.hibernate.type.ZonedDateTimeType -> basicType@3(java.time.ZonedDateTime,3003)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration ZonedDateTime -> basicType@3(java.time.ZonedDateTime,3003)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.time.ZonedDateTime -> basicType@3(java.time.ZonedDateTime,3003)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration org.hibernate.type.OffsetTimeType -> basicType@4(java.time.OffsetTime,3007)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration OffsetTime -> basicType@4(java.time.OffsetTime,3007)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.hibernate.type.BasicTypeRegistry - Adding type registration java.time.OffsetTime -> basicType@4(java.time.OffsetTime,3007)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.h.type.spi.TypeConfiguration$Scope - Scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration@755009f2] to MetadataBuildingContext [org.hibernate.boot.internal.MetadataBuildingContextRootImpl@1756a471]
|
||||||
|
2025-10-24 09:44:00 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG o.h.type.spi.TypeConfiguration$Scope - Scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration@755009f2] to SessionFactoryImplementor [org.hibernate.internal.SessionFactoryImpl@139da216]
|
||||||
|
2025-10-24 09:44:00 [main] DEBUG org.hibernate.SQL -
|
||||||
|
create table processed_transcripts (
|
||||||
|
transcript_id varchar(50) not null,
|
||||||
|
created_at timestamp(6) not null,
|
||||||
|
updated_at timestamp(6) not null,
|
||||||
|
decisions TEXT,
|
||||||
|
discussions TEXT,
|
||||||
|
meeting_id varchar(50) not null,
|
||||||
|
pending_items TEXT,
|
||||||
|
status varchar(20) not null,
|
||||||
|
summary TEXT,
|
||||||
|
primary key (transcript_id)
|
||||||
|
)
|
||||||
|
2025-10-24 09:44:00 [main] TRACE o.h.type.spi.TypeConfiguration$Scope - Handling #sessionFactoryCreated from [org.hibernate.internal.SessionFactoryImpl@139da216] for TypeConfiguration
|
||||||
|
2025-10-24 09:44:00 [main] INFO o.s.o.j.LocalContainerEntityManagerFactoryBean - Initialized JPA EntityManagerFactory for persistence unit 'default'
|
||||||
|
2025-10-24 09:44:01 [main] WARN o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
|
||||||
|
2025-10-24 09:44:01 [main] WARN o.s.b.a.s.s.UserDetailsServiceAutoConfiguration -
|
||||||
|
|
||||||
|
Using generated security password: 95eb4232-3294-428d-b23f-4e7e714862aa
|
||||||
|
|
||||||
|
This generated password is for development use only. Your security configuration must be updated before running your application in production.
|
||||||
|
|
||||||
|
2025-10-24 09:44:01 [main] INFO o.s.s.c.a.a.c.InitializeUserDetailsBeanManagerConfigurer$InitializeUserDetailsManagerConfigurer - Global AuthenticationManager configured with UserDetailsService bean with name inMemoryUserDetailsManager
|
||||||
|
2025-10-24 09:44:01 [main] ERROR i.n.r.d.DnsServerAddressStreamProviders - Unable to load io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider, fallback to system defaults. This may result in incorrect DNS resolutions on MacOS. Check whether you have a dependency on 'io.netty:netty-resolver-dns-native-macos'. Use DEBUG level to see the full stack: java.lang.UnsatisfiedLinkError: failed to load the required native library
|
||||||
|
2025-10-24 09:44:01 [main] INFO o.s.b.a.e.web.EndpointLinksResolver - Exposing 3 endpoints beneath base path '/actuator'
|
||||||
|
2025-10-24 09:44:01 [main] DEBUG o.s.s.web.DefaultSecurityFilterChain - Will secure any request with filters: DisableEncodeUrlFilter, WebAsyncManagerIntegrationFilter, SecurityContextHolderFilter, HeaderWriterFilter, CorsFilter, LogoutFilter, JwtAuthenticationFilter, RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, SessionManagementFilter, ExceptionTranslationFilter, AuthorizationFilter
|
||||||
|
2025-10-24 09:44:01 [main] INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port 8083 (http) with context path '/'
|
||||||
|
2025-10-24 09:44:01 [main] INFO com.unicorn.hgzero.ai.AiApplication - Started AiApplication in 3.911 seconds (process running for 4.067)
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-1] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-1] INFO o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-1] INFO o.s.web.servlet.DispatcherServlet - Completed initialization in 3 ms
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-1] DEBUG o.s.security.web.FilterChainProxy - Securing GET /swagger-ui/index.html
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-1] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-1] DEBUG o.s.security.web.FilterChainProxy - Secured GET /swagger-ui/index.html
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-2] DEBUG o.s.security.web.FilterChainProxy - Securing GET /swagger-ui/swagger-ui.css
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-2] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-2] DEBUG o.s.security.web.FilterChainProxy - Secured GET /swagger-ui/swagger-ui.css
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-3] DEBUG o.s.security.web.FilterChainProxy - Securing GET /swagger-ui/index.css
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-4] DEBUG o.s.security.web.FilterChainProxy - Securing GET /swagger-ui/swagger-ui-bundle.js
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-4] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-5] DEBUG o.s.security.web.FilterChainProxy - Securing GET /swagger-ui/swagger-ui-standalone-preset.js
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-4] DEBUG o.s.security.web.FilterChainProxy - Secured GET /swagger-ui/swagger-ui-bundle.js
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-5] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-6] DEBUG o.s.security.web.FilterChainProxy - Securing GET /swagger-ui/swagger-initializer.js
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-5] DEBUG o.s.security.web.FilterChainProxy - Secured GET /swagger-ui/swagger-ui-standalone-preset.js
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-6] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-6] DEBUG o.s.security.web.FilterChainProxy - Secured GET /swagger-ui/swagger-initializer.js
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-3] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-3] DEBUG o.s.security.web.FilterChainProxy - Secured GET /swagger-ui/index.css
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-8] DEBUG o.s.security.web.FilterChainProxy - Securing GET /swagger-ui/favicon-32x32.png
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-8] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-7] DEBUG o.s.security.web.FilterChainProxy - Securing GET /v3/api-docs/swagger-config
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-8] DEBUG o.s.security.web.FilterChainProxy - Secured GET /swagger-ui/favicon-32x32.png
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-7] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-7] DEBUG o.s.security.web.FilterChainProxy - Secured GET /v3/api-docs/swagger-config
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-7] INFO c.u.hgzero.common.aop.LoggingAspect - [Controller] org.springdoc.webmvc.ui.SwaggerConfigResource.openapiJson 호출 - 파라미터: [SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@4e36d653]]
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-7] INFO c.u.hgzero.common.aop.LoggingAspect - [Controller] org.springdoc.webmvc.ui.SwaggerConfigResource.openapiJson 완료 - 실행시간: 0ms
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-9] DEBUG o.s.security.web.FilterChainProxy - Securing GET /v3/api-docs
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-9] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-9] DEBUG o.s.security.web.FilterChainProxy - Secured GET /v3/api-docs
|
||||||
|
2025-10-24 09:45:34 [http-nio-8083-exec-9] INFO c.u.hgzero.common.aop.LoggingAspect - [Controller] org.springdoc.webmvc.api.OpenApiWebMvcResource.openapiJson 호출 - 파라미터: [SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@668a5138], /v3/api-docs, ko_KR]
|
||||||
|
2025-10-24 09:45:35 [http-nio-8083-exec-9] INFO o.s.api.AbstractOpenApiResource - Init duration for springdoc-openapi is: 229 ms
|
||||||
|
2025-10-24 09:45:35 [http-nio-8083-exec-9] INFO c.u.hgzero.common.aop.LoggingAspect - [Controller] org.springdoc.webmvc.api.OpenApiWebMvcResource.openapiJson 완료 - 실행시간: 239ms
|
||||||
@ -3,6 +3,7 @@ package com.unicorn.hgzero.ai;
|
|||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI Service Application
|
* AI Service Application
|
||||||
|
|||||||
@ -0,0 +1,44 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.domain;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 추출된 Todo 도메인 모델
|
||||||
|
* AI가 회의록에서 추출한 Todo 정보
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ExtractedTodo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Todo 내용
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 담당자
|
||||||
|
*/
|
||||||
|
private String assignee;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 마감일
|
||||||
|
*/
|
||||||
|
private LocalDate dueDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 우선순위 (HIGH, MEDIUM, LOW)
|
||||||
|
*/
|
||||||
|
private String priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 회의록 섹션
|
||||||
|
*/
|
||||||
|
private String sectionReference;
|
||||||
|
}
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.domain;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 처리된 회의록 도메인 모델
|
||||||
|
* AI가 처리한 회의록 정보를 담는 도메인 객체
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ProcessedTranscript {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 ID
|
||||||
|
*/
|
||||||
|
private String transcriptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID
|
||||||
|
*/
|
||||||
|
private String meetingId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전체 요약
|
||||||
|
*/
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의사항 목록
|
||||||
|
*/
|
||||||
|
private List<DiscussionItem> discussions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정사항 목록
|
||||||
|
*/
|
||||||
|
private List<DecisionItem> decisions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 보류사항 목록
|
||||||
|
*/
|
||||||
|
private List<String> pendingItems;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 생성 시간
|
||||||
|
*/
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 상태 (DRAFT, COMPLETED)
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의사항 아이템
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class DiscussionItem {
|
||||||
|
private String topic;
|
||||||
|
private String speaker;
|
||||||
|
private String content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정사항 아이템
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class DecisionItem {
|
||||||
|
private String content;
|
||||||
|
private String decisionMaker;
|
||||||
|
private String category;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.domain;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 회의록 도메인 모델
|
||||||
|
* RAG 검색으로 찾은 관련 회의록 정보
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RelatedMinutes {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 ID
|
||||||
|
*/
|
||||||
|
private String transcriptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 제목
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 날짜
|
||||||
|
*/
|
||||||
|
private LocalDate date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참석자 목록
|
||||||
|
*/
|
||||||
|
private List<String> participants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련도 점수 (0-100)
|
||||||
|
*/
|
||||||
|
private Double relevanceScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 공통 키워드 목록
|
||||||
|
*/
|
||||||
|
private List<String> commonKeywords;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 링크
|
||||||
|
*/
|
||||||
|
private String link;
|
||||||
|
}
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.domain;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 제안사항 도메인 모델
|
||||||
|
* AI가 제안하는 논의사항 또는 결정사항
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class Suggestion {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 제안 ID
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 제안 유형 (DISCUSSION, DECISION)
|
||||||
|
*/
|
||||||
|
private SuggestionType type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 제안 내용
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 우선순위 (HIGH, MEDIUM, LOW)
|
||||||
|
*/
|
||||||
|
private String priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 제안 이유
|
||||||
|
*/
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 신뢰도 점수 (0-1)
|
||||||
|
*/
|
||||||
|
private Double confidence;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 안건
|
||||||
|
*/
|
||||||
|
private String relatedAgenda;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 소요 시간 (분)
|
||||||
|
*/
|
||||||
|
private Integer estimatedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여자 목록 (결정사항인 경우)
|
||||||
|
*/
|
||||||
|
private List<String> participants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카테고리 (결정사항인 경우: 기술, 일정, 리소스, 정책, 기타)
|
||||||
|
*/
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 원문 발췌 (결정사항인 경우)
|
||||||
|
*/
|
||||||
|
private String extractedFrom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배경 설명 (결정사항인 경우)
|
||||||
|
*/
|
||||||
|
private String context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 제안 유형
|
||||||
|
*/
|
||||||
|
public enum SuggestionType {
|
||||||
|
DISCUSSION, // 논의사항
|
||||||
|
DECISION // 결정사항
|
||||||
|
}
|
||||||
|
}
|
||||||
54
ai/src/main/java/com/unicorn/hgzero/ai/biz/domain/Term.java
Normal file
54
ai/src/main/java/com/unicorn/hgzero/ai/biz/domain/Term.java
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.domain;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전문용어 도메인 모델
|
||||||
|
* 회의록에서 감지된 전문용어 정보
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class Term {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 용어명
|
||||||
|
*/
|
||||||
|
private String term;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 텍스트 위치 정보
|
||||||
|
*/
|
||||||
|
private TextPosition position;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 신뢰도 점수 (0-1)
|
||||||
|
*/
|
||||||
|
private Double confidence;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 용어 카테고리 (기술, 업무, 도메인)
|
||||||
|
*/
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 하이라이트 여부
|
||||||
|
*/
|
||||||
|
private Boolean highlight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 텍스트 위치
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class TextPosition {
|
||||||
|
private Integer line;
|
||||||
|
private Integer offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.gateway;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LLM Gateway 인터페이스
|
||||||
|
* OpenAI API 연동을 추상화
|
||||||
|
*/
|
||||||
|
public interface LlmGateway {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 자동 작성 (LLM 기반)
|
||||||
|
*
|
||||||
|
* @param transcriptText STT 변환 텍스트
|
||||||
|
* @param title 회의 제목
|
||||||
|
* @param participants 참석자 목록
|
||||||
|
* @param agenda 회의 안건
|
||||||
|
* @return LLM 생성 회의록 (JSON 형식)
|
||||||
|
*/
|
||||||
|
String generateTranscript(String transcriptText, String title, List<String> participants, List<String> agenda);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Todo 추출 (LLM 기반)
|
||||||
|
*
|
||||||
|
* @param minutesContent 회의록 내용
|
||||||
|
* @return 추출된 Todo JSON
|
||||||
|
*/
|
||||||
|
String extractTodos(String minutesContent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 섹션 요약 생성 (LLM 기반)
|
||||||
|
*
|
||||||
|
* @param sectionContent 섹션 내용
|
||||||
|
* @param meetingContext 회의 맥락
|
||||||
|
* @return 생성된 요약 (2-3문장)
|
||||||
|
*/
|
||||||
|
String generateSummary(String sectionContent, String meetingContext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전문용어 감지 (LLM 기반)
|
||||||
|
*
|
||||||
|
* @param text 분석할 텍스트
|
||||||
|
* @param organizationId 조직 ID
|
||||||
|
* @return 감지된 용어 JSON
|
||||||
|
*/
|
||||||
|
String detectTerms(String text, String organizationId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의사항 제안 (LLM 기반)
|
||||||
|
*
|
||||||
|
* @param transcriptText 현재 회의록 텍스트
|
||||||
|
* @param agenda 회의 안건
|
||||||
|
* @return 논의사항 제안 JSON
|
||||||
|
*/
|
||||||
|
String suggestDiscussions(String transcriptText, List<String> agenda);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정사항 제안 (LLM 기반)
|
||||||
|
*
|
||||||
|
* @param transcriptText 현재 회의록 텍스트
|
||||||
|
* @return 결정사항 제안 JSON
|
||||||
|
*/
|
||||||
|
String suggestDecisions(String transcriptText);
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.gateway;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RAG 검색 Gateway 인터페이스
|
||||||
|
* Azure AI Search 연동을 추상화
|
||||||
|
*/
|
||||||
|
public interface SearchGateway {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 회의록 검색 (벡터 유사도 기반)
|
||||||
|
*
|
||||||
|
* @param meetingId 회의 ID
|
||||||
|
* @param transcriptId 회의록 ID
|
||||||
|
* @param limit 최대 개수
|
||||||
|
* @return 관련 회의록 JSON
|
||||||
|
*/
|
||||||
|
String searchRelatedTranscripts(String meetingId, String transcriptId, int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 용어 설명을 위한 문서 검색
|
||||||
|
*
|
||||||
|
* @param term 용어명
|
||||||
|
* @param meetingId 회의 ID
|
||||||
|
* @param context 맥락
|
||||||
|
* @return 관련 문서 JSON
|
||||||
|
*/
|
||||||
|
String searchTermExplanation(String term, String meetingId, String context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 인덱싱 (벡터 임베딩 저장)
|
||||||
|
*
|
||||||
|
* @param transcriptId 회의록 ID
|
||||||
|
* @param content 회의록 내용
|
||||||
|
* @param metadata 메타데이터
|
||||||
|
*/
|
||||||
|
void indexTranscript(String transcriptId, String content, String metadata);
|
||||||
|
}
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.gateway;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.ProcessedTranscript;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 데이터 Gateway 인터페이스
|
||||||
|
* 회의록 영속성 관리를 추상화
|
||||||
|
*/
|
||||||
|
public interface TranscriptGateway {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 저장
|
||||||
|
*
|
||||||
|
* @param transcript 처리된 회의록
|
||||||
|
* @return 저장된 회의록
|
||||||
|
*/
|
||||||
|
ProcessedTranscript save(ProcessedTranscript transcript);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 ID로 조회
|
||||||
|
*
|
||||||
|
* @param transcriptId 회의록 ID
|
||||||
|
* @return 회의록 (Optional)
|
||||||
|
*/
|
||||||
|
Optional<ProcessedTranscript> findById(String transcriptId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID로 조회
|
||||||
|
*
|
||||||
|
* @param meetingId 회의 ID
|
||||||
|
* @return 회의록 (Optional)
|
||||||
|
*/
|
||||||
|
Optional<ProcessedTranscript> findByMeetingId(String meetingId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID 목록으로 조회
|
||||||
|
*
|
||||||
|
* @param meetingIds 회의 ID 목록
|
||||||
|
* @return 회의록 목록
|
||||||
|
*/
|
||||||
|
List<ProcessedTranscript> findByMeetingIds(List<String> meetingIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 상태로 조회
|
||||||
|
*
|
||||||
|
* @param status 상태
|
||||||
|
* @return 회의록 목록
|
||||||
|
*/
|
||||||
|
List<ProcessedTranscript> findByStatus(String status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 존재 여부 확인
|
||||||
|
*
|
||||||
|
* @param meetingId 회의 ID
|
||||||
|
* @return 존재 여부
|
||||||
|
*/
|
||||||
|
boolean existsByMeetingId(String meetingId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 삭제
|
||||||
|
*
|
||||||
|
* @param transcriptId 회의록 ID
|
||||||
|
*/
|
||||||
|
void delete(String transcriptId);
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.RelatedMinutes;
|
||||||
|
import com.unicorn.hgzero.ai.biz.gateway.SearchGateway;
|
||||||
|
import com.unicorn.hgzero.ai.biz.usecase.RelatedTranscriptSearchUseCase;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 회의록 검색 Service
|
||||||
|
* RAG 기반 벡터 유사도 검색
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RelatedTranscriptSearchService implements RelatedTranscriptSearchUseCase {
|
||||||
|
|
||||||
|
private final SearchGateway searchGateway;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RelatedMinutes> findRelatedTranscripts(String meetingId, String transcriptId, int limit) {
|
||||||
|
log.info("Searching related transcripts: meetingId={}, transcriptId={}, limit={}",
|
||||||
|
meetingId, transcriptId, limit);
|
||||||
|
|
||||||
|
// RAG 검색
|
||||||
|
String searchResult = searchGateway.searchRelatedTranscripts(meetingId, transcriptId, limit);
|
||||||
|
|
||||||
|
// TODO: JSON 파싱 및 RelatedMinutes 리스트 생성
|
||||||
|
// 현재는 mock 데이터 반환
|
||||||
|
return List.of(
|
||||||
|
RelatedMinutes.builder()
|
||||||
|
.transcriptId("aa0e8400-e29b-41d4-a716-446655440005")
|
||||||
|
.title("프로젝트 X 주간 회의")
|
||||||
|
.date(LocalDate.of(2025, 1, 15))
|
||||||
|
.participants(List.of("김철수", "이영희"))
|
||||||
|
.relevanceScore(85.5)
|
||||||
|
.commonKeywords(List.of("MSA", "API Gateway", "Spring Boot"))
|
||||||
|
.link("/transcripts/aa0e8400-e29b-41d4-a716-446655440005")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.service;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.gateway.LlmGateway;
|
||||||
|
import com.unicorn.hgzero.ai.biz.usecase.SectionSummaryUseCase;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 섹션 AI 요약 재생성 Service
|
||||||
|
* LLM 기반 섹션 요약 생성
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SectionSummaryService implements SectionSummaryUseCase {
|
||||||
|
|
||||||
|
private final LlmGateway llmGateway;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String regenerateSummary(String sectionId, String sectionContent, String meetingId) {
|
||||||
|
log.info("Regenerating section summary: sectionId={}, meetingId={}", sectionId, meetingId);
|
||||||
|
|
||||||
|
// LLM을 통한 요약 생성
|
||||||
|
String meetingContext = meetingId != null ? "회의 ID: " + meetingId : "";
|
||||||
|
return llmGateway.generateSummary(sectionContent, meetingContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.service;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.Suggestion;
|
||||||
|
import com.unicorn.hgzero.ai.biz.gateway.LlmGateway;
|
||||||
|
import com.unicorn.hgzero.ai.biz.usecase.SuggestionUseCase;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의사항/결정사항 제안 Service
|
||||||
|
* LLM 기반 실시간 회의 제안
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SuggestionService implements SuggestionUseCase {
|
||||||
|
|
||||||
|
private final LlmGateway llmGateway;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Suggestion> suggestDiscussions(String meetingId, String transcriptText) {
|
||||||
|
log.info("Suggesting discussions: meetingId={}", meetingId);
|
||||||
|
|
||||||
|
// TODO: 회의 안건 조회
|
||||||
|
List<String> agenda = List.of();
|
||||||
|
|
||||||
|
// LLM을 통한 논의사항 제안
|
||||||
|
String llmResponse = llmGateway.suggestDiscussions(transcriptText, agenda);
|
||||||
|
|
||||||
|
// TODO: JSON 파싱 및 Suggestion 리스트 생성
|
||||||
|
return List.of(
|
||||||
|
Suggestion.builder()
|
||||||
|
.id("sugg-001")
|
||||||
|
.type(Suggestion.SuggestionType.DISCUSSION)
|
||||||
|
.content("보안 요구사항 검토")
|
||||||
|
.priority("HIGH")
|
||||||
|
.reason("안건에 포함되어 있으나 아직 논의되지 않음")
|
||||||
|
.confidence(0.9)
|
||||||
|
.relatedAgenda("프로젝트 개요")
|
||||||
|
.estimatedTime(15)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Suggestion> suggestDecisions(String meetingId, String transcriptText) {
|
||||||
|
log.info("Suggesting decisions: meetingId={}", meetingId);
|
||||||
|
|
||||||
|
// LLM을 통한 결정사항 제안
|
||||||
|
String llmResponse = llmGateway.suggestDecisions(transcriptText);
|
||||||
|
|
||||||
|
// TODO: JSON 파싱 및 Suggestion 리스트 생성
|
||||||
|
return List.of(
|
||||||
|
Suggestion.builder()
|
||||||
|
.id("dec-001")
|
||||||
|
.type(Suggestion.SuggestionType.DECISION)
|
||||||
|
.content("React로 프론트엔드 개발")
|
||||||
|
.category("기술")
|
||||||
|
.participants(List.of("김철수", "이영희"))
|
||||||
|
.confidence(0.85)
|
||||||
|
.extractedFrom("프론트엔드는 React로 개발하기로 했습니다")
|
||||||
|
.context("팀원 대부분이 React 경험이 있어 개발 속도가 빠를 것으로 예상")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.service;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.Term;
|
||||||
|
import com.unicorn.hgzero.ai.biz.gateway.LlmGateway;
|
||||||
|
import com.unicorn.hgzero.ai.biz.usecase.TermDetectionUseCase;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전문용어 감지 Service
|
||||||
|
* LLM 기반 전문용어 자동 감지
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TermDetectionService implements TermDetectionUseCase {
|
||||||
|
|
||||||
|
private final LlmGateway llmGateway;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Term> detectTerms(String meetingId, String text, String organizationId) {
|
||||||
|
log.info("Detecting terms: meetingId={}, organizationId={}", meetingId, organizationId);
|
||||||
|
|
||||||
|
// LLM을 통한 전문용어 감지
|
||||||
|
String llmResponse = llmGateway.detectTerms(text, organizationId);
|
||||||
|
|
||||||
|
// TODO: JSON 파싱 및 Term 리스트 생성
|
||||||
|
// 현재는 mock 데이터 반환
|
||||||
|
return List.of(
|
||||||
|
Term.builder()
|
||||||
|
.term("MSA")
|
||||||
|
.position(Term.TextPosition.builder().line(5).offset(42).build())
|
||||||
|
.confidence(0.92)
|
||||||
|
.category("기술")
|
||||||
|
.highlight(true)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.service;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.gateway.SearchGateway;
|
||||||
|
import com.unicorn.hgzero.ai.biz.usecase.TermExplanationUseCase;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전문용어 설명 Service
|
||||||
|
* RAG 기반 맥락적 용어 설명 생성
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TermExplanationService implements TermExplanationUseCase {
|
||||||
|
|
||||||
|
private final SearchGateway searchGateway;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TermExplanationResult explainTerm(String term, String meetingId, String context) {
|
||||||
|
log.info("Explaining term: term={}, meetingId={}", term, meetingId);
|
||||||
|
|
||||||
|
// RAG 검색
|
||||||
|
String searchResult = searchGateway.searchTermExplanation(term, meetingId, context);
|
||||||
|
|
||||||
|
// TODO: JSON 파싱 및 TermExplanationResult 생성
|
||||||
|
// 현재는 mock 데이터 반환
|
||||||
|
return new TermExplanationResult(
|
||||||
|
"MSA",
|
||||||
|
"Microservices Architecture의 약자",
|
||||||
|
"이번 프로젝트에서는 확장성과 독립 배포를 위해 MSA를 적용하기로 결정",
|
||||||
|
List.of(
|
||||||
|
"2024년 프로젝트 X에서 주문/결제/배송 서비스를 독립적으로 구성",
|
||||||
|
"서비스별 독립 배포로 배포 시간 70% 단축"
|
||||||
|
),
|
||||||
|
List.of(new RelatedProject("프로젝트 X", "동일한 MSA 아키텍처 적용")),
|
||||||
|
List.of(new PastDiscussion(
|
||||||
|
LocalDate.of(2024, 12, 15),
|
||||||
|
List.of("김철수", "이영희"),
|
||||||
|
"MSA 아키텍처의 장단점을 비교하고 적용 방안을 논의",
|
||||||
|
"/transcripts/bb0e8400-e29b-41d4-a716-446655440006"
|
||||||
|
)),
|
||||||
|
List.of(new Reference(
|
||||||
|
"MSA 아키텍처 가이드",
|
||||||
|
"위키",
|
||||||
|
"https://wiki.example.com/msa-guide"
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.ExtractedTodo;
|
||||||
|
import com.unicorn.hgzero.ai.biz.gateway.LlmGateway;
|
||||||
|
import com.unicorn.hgzero.ai.biz.usecase.TodoExtractionUseCase;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Todo 자동 추출 Service
|
||||||
|
* LLM 기반 액션 아이템 추출
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TodoExtractionService implements TodoExtractionUseCase {
|
||||||
|
|
||||||
|
private final LlmGateway llmGateway;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ExtractedTodo> extractTodos(String meetingId, String minutesContent, String userId) {
|
||||||
|
log.info("Extracting todos from minutes: meetingId={}, userId={}", meetingId, userId);
|
||||||
|
|
||||||
|
// LLM을 통한 Todo 추출
|
||||||
|
String llmResponse = llmGateway.extractTodos(minutesContent);
|
||||||
|
|
||||||
|
// TODO: JSON 파싱 및 ExtractedTodo 리스트 생성
|
||||||
|
// 현재는 mock 데이터 반환
|
||||||
|
return List.of(
|
||||||
|
ExtractedTodo.builder()
|
||||||
|
.content("API 설계서 작성")
|
||||||
|
.assignee("박민수")
|
||||||
|
.dueDate(LocalDate.of(2025, 1, 30))
|
||||||
|
.priority("HIGH")
|
||||||
|
.sectionReference("결정사항 #3")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,180 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.ProcessedTranscript;
|
||||||
|
import com.unicorn.hgzero.ai.biz.gateway.LlmGateway;
|
||||||
|
import com.unicorn.hgzero.ai.biz.gateway.SearchGateway;
|
||||||
|
import com.unicorn.hgzero.ai.biz.gateway.TranscriptGateway;
|
||||||
|
import com.unicorn.hgzero.ai.biz.usecase.TranscriptProcessUseCase;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 자동 작성 Service
|
||||||
|
* LLM 기반 회의록 생성 및 저장
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TranscriptProcessService implements TranscriptProcessUseCase {
|
||||||
|
|
||||||
|
private final LlmGateway llmGateway;
|
||||||
|
private final SearchGateway searchGateway;
|
||||||
|
private final TranscriptGateway transcriptGateway;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public ProcessedTranscript processTranscript(
|
||||||
|
String meetingId,
|
||||||
|
String transcriptText,
|
||||||
|
String userId,
|
||||||
|
String userName,
|
||||||
|
String title,
|
||||||
|
List<String> participants,
|
||||||
|
List<String> agenda
|
||||||
|
) {
|
||||||
|
log.info("Processing transcript for meeting: meetingId={}, userId={}", meetingId, userId);
|
||||||
|
|
||||||
|
// 1. LLM을 통한 회의록 자동 생성
|
||||||
|
String llmResponse = llmGateway.generateTranscript(transcriptText, title, participants, agenda);
|
||||||
|
log.debug("LLM response received: length={}", llmResponse.length());
|
||||||
|
|
||||||
|
// 2. LLM 응답 파싱
|
||||||
|
ProcessedTranscript processedTranscript = parseTranscriptFromLlm(llmResponse, meetingId);
|
||||||
|
|
||||||
|
// 3. 회의록 저장
|
||||||
|
ProcessedTranscript saved = transcriptGateway.save(processedTranscript);
|
||||||
|
log.info("Transcript saved: transcriptId={}, meetingId={}", saved.getTranscriptId(), meetingId);
|
||||||
|
|
||||||
|
// 4. RAG 인덱싱 (비동기 처리 고려)
|
||||||
|
indexTranscriptForSearch(saved);
|
||||||
|
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public ProcessedTranscript getTranscript(String transcriptId) {
|
||||||
|
log.debug("Retrieving transcript: transcriptId={}", transcriptId);
|
||||||
|
return transcriptGateway.findById(transcriptId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Transcript not found: " + transcriptId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public ProcessedTranscript getTranscriptByMeetingId(String meetingId) {
|
||||||
|
log.debug("Retrieving transcript by meetingId: {}", meetingId);
|
||||||
|
return transcriptGateway.findByMeetingId(meetingId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Transcript not found for meeting: " + meetingId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LLM 응답을 ProcessedTranscript 도메인으로 파싱
|
||||||
|
*/
|
||||||
|
private ProcessedTranscript parseTranscriptFromLlm(String llmResponse, String meetingId) {
|
||||||
|
try {
|
||||||
|
JsonNode root = objectMapper.readTree(llmResponse);
|
||||||
|
|
||||||
|
// Discussions 파싱
|
||||||
|
List<ProcessedTranscript.DiscussionItem> discussions = new ArrayList<>();
|
||||||
|
if (root.has("discussions")) {
|
||||||
|
root.get("discussions").forEach(node -> {
|
||||||
|
discussions.add(ProcessedTranscript.DiscussionItem.builder()
|
||||||
|
.topic(node.get("topic").asText())
|
||||||
|
.speaker(node.get("speaker").asText())
|
||||||
|
.content(node.get("content").asText())
|
||||||
|
.build());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decisions 파싱
|
||||||
|
List<ProcessedTranscript.DecisionItem> decisions = new ArrayList<>();
|
||||||
|
if (root.has("decisions")) {
|
||||||
|
root.get("decisions").forEach(node -> {
|
||||||
|
decisions.add(ProcessedTranscript.DecisionItem.builder()
|
||||||
|
.content(node.get("content").asText())
|
||||||
|
.decisionMaker(node.get("decisionMaker").asText())
|
||||||
|
.category(node.get("category").asText())
|
||||||
|
.build());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pending items 파싱
|
||||||
|
List<String> pendingItems = new ArrayList<>();
|
||||||
|
if (root.has("pendingItems")) {
|
||||||
|
root.get("pendingItems").forEach(node -> pendingItems.add(node.asText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProcessedTranscript.builder()
|
||||||
|
.transcriptId(UUID.randomUUID().toString())
|
||||||
|
.meetingId(meetingId)
|
||||||
|
.summary(root.has("summary") ? root.get("summary").asText() : "")
|
||||||
|
.discussions(discussions)
|
||||||
|
.decisions(decisions)
|
||||||
|
.pendingItems(pendingItems)
|
||||||
|
.createdAt(LocalDateTime.now())
|
||||||
|
.status("DRAFT")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Failed to parse LLM response: {}", llmResponse, e);
|
||||||
|
throw new RuntimeException("Failed to parse transcript from LLM", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RAG 검색을 위한 회의록 인덱싱
|
||||||
|
*/
|
||||||
|
private void indexTranscriptForSearch(ProcessedTranscript transcript) {
|
||||||
|
try {
|
||||||
|
String content = buildSearchableContent(transcript);
|
||||||
|
String metadata = buildMetadata(transcript);
|
||||||
|
|
||||||
|
searchGateway.indexTranscript(transcript.getTranscriptId(), content, metadata);
|
||||||
|
log.debug("Transcript indexed for search: transcriptId={}", transcript.getTranscriptId());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to index transcript for search: transcriptId={}",
|
||||||
|
transcript.getTranscriptId(), e);
|
||||||
|
// 인덱싱 실패는 치명적이지 않으므로 예외를 전파하지 않음
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildSearchableContent(ProcessedTranscript transcript) {
|
||||||
|
StringBuilder content = new StringBuilder();
|
||||||
|
content.append(transcript.getSummary()).append("\n\n");
|
||||||
|
|
||||||
|
if (transcript.getDiscussions() != null) {
|
||||||
|
transcript.getDiscussions().forEach(d ->
|
||||||
|
content.append(d.getTopic()).append(": ").append(d.getContent()).append("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transcript.getDecisions() != null) {
|
||||||
|
transcript.getDecisions().forEach(d ->
|
||||||
|
content.append("결정: ").append(d.getContent()).append("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return content.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildMetadata(ProcessedTranscript transcript) {
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsString(transcript);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.warn("Failed to serialize transcript metadata", e);
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.usecase;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.RelatedMinutes;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 회의록 검색 UseCase
|
||||||
|
* RAG 기반 벡터 유사도 검색으로 관련 회의록 조회
|
||||||
|
*/
|
||||||
|
public interface RelatedTranscriptSearchUseCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 회의록 검색
|
||||||
|
*
|
||||||
|
* @param meetingId 회의 ID
|
||||||
|
* @param transcriptId 회의록 ID
|
||||||
|
* @param limit 반환할 최대 개수
|
||||||
|
* @return 관련 회의록 목록
|
||||||
|
*/
|
||||||
|
List<RelatedMinutes> findRelatedTranscripts(String meetingId, String transcriptId, int limit);
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.usecase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 섹션 AI 요약 재생성 UseCase
|
||||||
|
* 사용자가 작성한 섹션 내용을 기반으로 AI 요약 재생성
|
||||||
|
*/
|
||||||
|
public interface SectionSummaryUseCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 섹션 요약 재생성
|
||||||
|
*
|
||||||
|
* @param sectionId 섹션 ID
|
||||||
|
* @param sectionContent 섹션 내용 (Markdown 형식)
|
||||||
|
* @param meetingId 회의 ID (선택적, 맥락 이해용)
|
||||||
|
* @return 생성된 AI 요약 (2-3문장)
|
||||||
|
*/
|
||||||
|
String regenerateSummary(String sectionId, String sectionContent, String meetingId);
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.usecase;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.Suggestion;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의사항/결정사항 제안 UseCase
|
||||||
|
* AI 기반 실시간 회의 제안 기능
|
||||||
|
*/
|
||||||
|
public interface SuggestionUseCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의사항 제안
|
||||||
|
*
|
||||||
|
* @param meetingId 회의 ID
|
||||||
|
* @param transcriptText 현재까지의 회의록 텍스트
|
||||||
|
* @return 논의사항 제안 목록
|
||||||
|
*/
|
||||||
|
List<Suggestion> suggestDiscussions(String meetingId, String transcriptText);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정사항 제안
|
||||||
|
*
|
||||||
|
* @param meetingId 회의 ID
|
||||||
|
* @param transcriptText 현재까지의 회의록 텍스트
|
||||||
|
* @return 결정사항 제안 목록
|
||||||
|
*/
|
||||||
|
List<Suggestion> suggestDecisions(String meetingId, String transcriptText);
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.usecase;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.Term;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전문용어 감지 UseCase
|
||||||
|
* 회의록 텍스트에서 전문용어를 자동으로 감지
|
||||||
|
*/
|
||||||
|
public interface TermDetectionUseCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전문용어 감지
|
||||||
|
*
|
||||||
|
* @param meetingId 회의 ID
|
||||||
|
* @param text 분석할 회의록 텍스트
|
||||||
|
* @param organizationId 조직 ID
|
||||||
|
* @return 감지된 전문용어 목록
|
||||||
|
*/
|
||||||
|
List<Term> detectTerms(String meetingId, String text, String organizationId);
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.usecase;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전문용어 설명 UseCase
|
||||||
|
* RAG 기반 맥락적 용어 설명 생성
|
||||||
|
*/
|
||||||
|
public interface TermExplanationUseCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 용어 설명 생성
|
||||||
|
*
|
||||||
|
* @param term 용어명
|
||||||
|
* @param meetingId 회의 ID
|
||||||
|
* @param context 현재 회의 맥락 (선택)
|
||||||
|
* @return 용어 설명 결과
|
||||||
|
*/
|
||||||
|
TermExplanationResult explainTerm(String term, String meetingId, String context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 용어 설명 결과
|
||||||
|
*/
|
||||||
|
record TermExplanationResult(
|
||||||
|
String term,
|
||||||
|
String basicDefinition,
|
||||||
|
String contextualMeaning,
|
||||||
|
List<String> useCases,
|
||||||
|
List<RelatedProject> relatedProjects,
|
||||||
|
List<PastDiscussion> pastDiscussions,
|
||||||
|
List<Reference> references
|
||||||
|
) {}
|
||||||
|
|
||||||
|
record RelatedProject(String name, String relevance) {}
|
||||||
|
record PastDiscussion(java.time.LocalDate date, List<String> participants, String summary, String link) {}
|
||||||
|
record Reference(String title, String type, String link) {}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.usecase;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.ExtractedTodo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Todo 자동 추출 UseCase
|
||||||
|
* 회의록에서 액션 아이템을 자동으로 추출하고 담당자 식별
|
||||||
|
*/
|
||||||
|
public interface TodoExtractionUseCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록에서 Todo 추출
|
||||||
|
*
|
||||||
|
* @param meetingId 회의 ID
|
||||||
|
* @param minutesContent 회의록 전체 내용 (Markdown 형식)
|
||||||
|
* @param userId 요청자 ID
|
||||||
|
* @return 추출된 Todo 목록
|
||||||
|
*/
|
||||||
|
List<ExtractedTodo> extractTodos(String meetingId, String minutesContent, String userId);
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package com.unicorn.hgzero.ai.biz.usecase;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.ProcessedTranscript;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 자동 작성 UseCase
|
||||||
|
* STT에서 변환된 텍스트를 받아 LLM 기반으로 회의록 자동 작성
|
||||||
|
*/
|
||||||
|
public interface TranscriptProcessUseCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 자동 작성
|
||||||
|
*
|
||||||
|
* @param meetingId 회의 ID
|
||||||
|
* @param transcriptText STT에서 변환된 텍스트
|
||||||
|
* @param userId 사용자 ID
|
||||||
|
* @param userName 사용자 이름
|
||||||
|
* @param title 회의 제목
|
||||||
|
* @param participants 참석자 목록
|
||||||
|
* @param agenda 회의 안건
|
||||||
|
* @return 처리된 회의록
|
||||||
|
*/
|
||||||
|
ProcessedTranscript processTranscript(
|
||||||
|
String meetingId,
|
||||||
|
String transcriptText,
|
||||||
|
String userId,
|
||||||
|
String userName,
|
||||||
|
String title,
|
||||||
|
java.util.List<String> participants,
|
||||||
|
java.util.List<String> agenda
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 조회
|
||||||
|
*
|
||||||
|
* @param transcriptId 회의록 ID
|
||||||
|
* @return 처리된 회의록
|
||||||
|
*/
|
||||||
|
ProcessedTranscript getTranscript(String transcriptId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID로 회의록 조회
|
||||||
|
*
|
||||||
|
* @param meetingId 회의 ID
|
||||||
|
* @return 처리된 회의록
|
||||||
|
*/
|
||||||
|
ProcessedTranscript getTranscriptByMeetingId(String meetingId);
|
||||||
|
}
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.config;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.common.security.JwtTokenProvider;
|
||||||
|
import com.unicorn.hgzero.common.security.filter.JwtAuthenticationFilter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Security 설정
|
||||||
|
* JWT 기반 인증 및 API 보안 설정
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
|
|
||||||
|
@Value("${cors.allowed-origins:http://localhost:3000,http://localhost:8080,http://localhost:8081,http://localhost:8082,http://localhost:8083,http://localhost:8084}")
|
||||||
|
private String allowedOrigins;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
return http
|
||||||
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||||
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
// Actuator endpoints
|
||||||
|
.requestMatchers("/actuator/**").permitAll()
|
||||||
|
// Swagger UI endpoints - context path와 상관없이 접근 가능하도록 설정
|
||||||
|
.requestMatchers("/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**").permitAll()
|
||||||
|
// Health check
|
||||||
|
.requestMatchers("/health").permitAll()
|
||||||
|
// All other requests require authentication
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
|
||||||
|
UsernamePasswordAuthenticationFilter.class)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
CorsConfiguration configuration = new CorsConfiguration();
|
||||||
|
|
||||||
|
// 환경변수에서 허용할 Origin 패턴 설정
|
||||||
|
String[] origins = allowedOrigins.split(",");
|
||||||
|
configuration.setAllowedOriginPatterns(Arrays.asList(origins));
|
||||||
|
|
||||||
|
// 허용할 HTTP 메소드
|
||||||
|
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
|
||||||
|
|
||||||
|
// 허용할 헤더
|
||||||
|
configuration.setAllowedHeaders(Arrays.asList(
|
||||||
|
"Authorization", "Content-Type", "X-Requested-With", "Accept",
|
||||||
|
"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers",
|
||||||
|
"X-User-Id", "X-User-Name"
|
||||||
|
));
|
||||||
|
|
||||||
|
// 자격 증명 허용
|
||||||
|
configuration.setAllowCredentials(true);
|
||||||
|
|
||||||
|
// Pre-flight 요청 캐시 시간
|
||||||
|
configuration.setMaxAge(3600L);
|
||||||
|
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", configuration);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.config;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.Components;
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.info.Contact;
|
||||||
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
|
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||||
|
import io.swagger.v3.oas.models.servers.Server;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swagger/OpenAPI 설정
|
||||||
|
* AI Service API 문서화를 위한 설정
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class SwaggerConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OpenAPI openAPI() {
|
||||||
|
return new OpenAPI()
|
||||||
|
.info(apiInfo())
|
||||||
|
.addServersItem(new Server()
|
||||||
|
.url("http://localhost:8083")
|
||||||
|
.description("Local Development"))
|
||||||
|
.addServersItem(new Server()
|
||||||
|
.url("{protocol}://{host}:{port}")
|
||||||
|
.description("Custom Server")
|
||||||
|
.variables(new io.swagger.v3.oas.models.servers.ServerVariables()
|
||||||
|
.addServerVariable("protocol", new io.swagger.v3.oas.models.servers.ServerVariable()
|
||||||
|
._default("http")
|
||||||
|
.description("Protocol (http or https)")
|
||||||
|
.addEnumItem("http")
|
||||||
|
.addEnumItem("https"))
|
||||||
|
.addServerVariable("host", new io.swagger.v3.oas.models.servers.ServerVariable()
|
||||||
|
._default("localhost")
|
||||||
|
.description("Server host"))
|
||||||
|
.addServerVariable("port", new io.swagger.v3.oas.models.servers.ServerVariable()
|
||||||
|
._default("8083")
|
||||||
|
.description("Server port"))))
|
||||||
|
.addSecurityItem(new SecurityRequirement().addList("Bearer Authentication"))
|
||||||
|
.components(new Components()
|
||||||
|
.addSecuritySchemes("Bearer Authentication", createAPIKeyScheme()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Info apiInfo() {
|
||||||
|
return new Info()
|
||||||
|
.title("AI Service API")
|
||||||
|
.description("AI 기반 회의록 자동 작성 및 분석 서비스 API")
|
||||||
|
.version("1.0.0")
|
||||||
|
.contact(new Contact()
|
||||||
|
.name("HGZero Development Team")
|
||||||
|
.email("dev@hgzero.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecurityScheme createAPIKeyScheme() {
|
||||||
|
return new SecurityScheme()
|
||||||
|
.type(SecurityScheme.Type.HTTP)
|
||||||
|
.bearerFormat("JWT")
|
||||||
|
.scheme("bearer");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.controller;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.usecase.TermExplanationUseCase;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.response.TermExplanationResponse;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.*;
|
||||||
|
import com.unicorn.hgzero.common.dto.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전문용어 설명 Controller
|
||||||
|
* GET /api/terms/{term}/explain
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/terms")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "Term", description = "전문용어 감지 및 설명 API")
|
||||||
|
public class ExplanationController {
|
||||||
|
|
||||||
|
private final TermExplanationUseCase termExplanationUseCase;
|
||||||
|
|
||||||
|
@GetMapping("/{term}/explain")
|
||||||
|
@Operation(summary = "맥락 기반 용어 설명", description = "전문용어에 대한 맥락 기반 설명을 생성합니다")
|
||||||
|
public ResponseEntity<ApiResponse<TermExplanationResponse>> explainTerm(
|
||||||
|
@PathVariable String term,
|
||||||
|
@RequestParam String meetingId,
|
||||||
|
@RequestParam(required = false) String context) {
|
||||||
|
|
||||||
|
log.info("용어 설명 요청 - term: {}, meetingId: {}", term, meetingId);
|
||||||
|
|
||||||
|
TermExplanationUseCase.TermExplanationResult result = termExplanationUseCase.explainTerm(
|
||||||
|
term,
|
||||||
|
meetingId,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
|
||||||
|
TermExplanationResponse response = TermExplanationResponse.builder()
|
||||||
|
.term(result.term())
|
||||||
|
.basicDefinition(result.basicDefinition())
|
||||||
|
.contextualMeaning(result.contextualMeaning())
|
||||||
|
.useCases(result.useCases())
|
||||||
|
.relatedProjects(result.relatedProjects().stream()
|
||||||
|
.map(p -> RelatedProjectDto.builder()
|
||||||
|
.name(p.name())
|
||||||
|
.relevance(p.relevance())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList()))
|
||||||
|
.pastDiscussions(result.pastDiscussions().stream()
|
||||||
|
.map(d -> PastDiscussionDto.builder()
|
||||||
|
.date(d.date())
|
||||||
|
.participants(d.participants())
|
||||||
|
.summary(d.summary())
|
||||||
|
.link(d.link())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList()))
|
||||||
|
.references(result.references().stream()
|
||||||
|
.map(r -> ReferenceDto.builder()
|
||||||
|
.title(r.title())
|
||||||
|
.type(r.type())
|
||||||
|
.link(r.link())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.controller;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.RelatedMinutes;
|
||||||
|
import com.unicorn.hgzero.ai.biz.usecase.RelatedTranscriptSearchUseCase;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.response.RelatedTranscriptsResponse;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.RelatedTranscriptDto;
|
||||||
|
import com.unicorn.hgzero.common.dto.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 회의록 조회 Controller
|
||||||
|
* GET /api/transcripts/{meetingId}/related
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/transcripts")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "Relation", description = "관련 회의록 조회 API")
|
||||||
|
public class RelationController {
|
||||||
|
|
||||||
|
private final RelatedTranscriptSearchUseCase relatedTranscriptSearchUseCase;
|
||||||
|
|
||||||
|
@GetMapping("/{meetingId}/related")
|
||||||
|
@Operation(summary = "관련 회의록 조회", description = "벡터 유사도 검색을 통해 관련된 회의록을 찾아 반환합니다")
|
||||||
|
public ResponseEntity<ApiResponse<RelatedTranscriptsResponse>> findRelatedTranscripts(
|
||||||
|
@PathVariable String meetingId,
|
||||||
|
@RequestParam String transcriptId,
|
||||||
|
@RequestParam(defaultValue = "5") int limit) {
|
||||||
|
|
||||||
|
log.info("관련 회의록 조회 요청 - meetingId: {}, transcriptId: {}, limit: {}", meetingId, transcriptId, limit);
|
||||||
|
|
||||||
|
List<RelatedMinutes> relatedMinutes = relatedTranscriptSearchUseCase.findRelatedTranscripts(
|
||||||
|
meetingId,
|
||||||
|
transcriptId,
|
||||||
|
limit
|
||||||
|
);
|
||||||
|
|
||||||
|
RelatedTranscriptsResponse response = RelatedTranscriptsResponse.builder()
|
||||||
|
.relatedTranscripts(relatedMinutes.stream()
|
||||||
|
.map(r -> RelatedTranscriptDto.builder()
|
||||||
|
.transcriptId(r.getTranscriptId())
|
||||||
|
.title(r.getTitle())
|
||||||
|
.date(r.getDate())
|
||||||
|
.participants(r.getParticipants())
|
||||||
|
.relevanceScore(r.getRelevanceScore())
|
||||||
|
.commonKeywords(r.getCommonKeywords())
|
||||||
|
.link(r.getLink())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList()))
|
||||||
|
.totalCount(relatedMinutes.size())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.controller;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.usecase.SectionSummaryUseCase;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.request.SectionSummaryRequest;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.response.SectionSummaryResponse;
|
||||||
|
import com.unicorn.hgzero.common.dto.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 섹션 AI 요약 재생성 Controller
|
||||||
|
* POST /api/sections/{sectionId}/regenerate-summary
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/sections")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "Section", description = "섹션 AI 요약 재생성 API")
|
||||||
|
public class SectionController {
|
||||||
|
|
||||||
|
private final SectionSummaryUseCase sectionSummaryUseCase;
|
||||||
|
|
||||||
|
@PostMapping("/{sectionId}/regenerate-summary")
|
||||||
|
@Operation(summary = "섹션 AI 요약 재생성", description = "사용자가 작성한 섹션 내용을 기반으로 AI 요약을 재생성합니다")
|
||||||
|
public ResponseEntity<ApiResponse<SectionSummaryResponse>> regenerateSummary(
|
||||||
|
@PathVariable String sectionId,
|
||||||
|
@Valid @RequestBody SectionSummaryRequest request) {
|
||||||
|
|
||||||
|
log.info("섹션 요약 재생성 요청 - sectionId: {}, meetingId: {}", sectionId, request.getMeetingId());
|
||||||
|
|
||||||
|
String summary = sectionSummaryUseCase.regenerateSummary(
|
||||||
|
sectionId,
|
||||||
|
request.getSectionContent(),
|
||||||
|
request.getMeetingId()
|
||||||
|
);
|
||||||
|
|
||||||
|
SectionSummaryResponse response = SectionSummaryResponse.builder()
|
||||||
|
.summary(summary)
|
||||||
|
.generatedAt(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.controller;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.Suggestion;
|
||||||
|
import com.unicorn.hgzero.ai.biz.usecase.SuggestionUseCase;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.request.DiscussionSuggestionRequest;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.request.DecisionSuggestionRequest;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.response.DiscussionSuggestionResponse;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.response.DecisionSuggestionResponse;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.DiscussionSuggestionDto;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.DecisionSuggestionDto;
|
||||||
|
import com.unicorn.hgzero.common.dto.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의사항/결정사항 제안 Controller
|
||||||
|
* POST /api/suggestions/discussion
|
||||||
|
* POST /api/suggestions/decision
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/suggestions")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "Suggestion", description = "논의사항/결정사항 제안 API")
|
||||||
|
public class SuggestionController {
|
||||||
|
|
||||||
|
private final SuggestionUseCase suggestionUseCase;
|
||||||
|
|
||||||
|
@PostMapping("/discussion")
|
||||||
|
@Operation(summary = "논의사항 제안", description = "현재 회의 진행 상황을 분석하여 추가로 논의하면 좋을 주제를 제안합니다")
|
||||||
|
public ResponseEntity<ApiResponse<DiscussionSuggestionResponse>> suggestDiscussion(
|
||||||
|
@Valid @RequestBody DiscussionSuggestionRequest request) {
|
||||||
|
|
||||||
|
log.info("논의사항 제안 요청 - meetingId: {}", request.getMeetingId());
|
||||||
|
|
||||||
|
List<Suggestion> suggestions = suggestionUseCase.suggestDiscussions(
|
||||||
|
request.getMeetingId(),
|
||||||
|
request.getTranscriptText()
|
||||||
|
);
|
||||||
|
|
||||||
|
DiscussionSuggestionResponse response = DiscussionSuggestionResponse.builder()
|
||||||
|
.suggestions(suggestions.stream()
|
||||||
|
.map(s -> DiscussionSuggestionDto.builder()
|
||||||
|
.id(s.getId())
|
||||||
|
.topic(s.getContent())
|
||||||
|
.reason(s.getReason())
|
||||||
|
.priority(s.getPriority())
|
||||||
|
.relatedAgenda(s.getRelatedAgenda())
|
||||||
|
.estimatedTime(s.getEstimatedTime())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList()))
|
||||||
|
.totalCount(suggestions.size())
|
||||||
|
.timestamp(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/decision")
|
||||||
|
@Operation(summary = "결정사항 제안", description = "회의록 텍스트에서 결정사항 패턴을 감지하여 제안합니다")
|
||||||
|
public ResponseEntity<ApiResponse<DecisionSuggestionResponse>> suggestDecision(
|
||||||
|
@Valid @RequestBody DecisionSuggestionRequest request) {
|
||||||
|
|
||||||
|
log.info("결정사항 제안 요청 - meetingId: {}", request.getMeetingId());
|
||||||
|
|
||||||
|
List<Suggestion> suggestions = suggestionUseCase.suggestDecisions(
|
||||||
|
request.getMeetingId(),
|
||||||
|
request.getTranscriptText()
|
||||||
|
);
|
||||||
|
|
||||||
|
DecisionSuggestionResponse response = DecisionSuggestionResponse.builder()
|
||||||
|
.suggestions(suggestions.stream()
|
||||||
|
.map(s -> DecisionSuggestionDto.builder()
|
||||||
|
.id(s.getId())
|
||||||
|
.content(s.getContent())
|
||||||
|
.category(s.getCategory())
|
||||||
|
.decisionMaker("") // TODO: Extract from suggestion
|
||||||
|
.participants(s.getParticipants())
|
||||||
|
.confidence(s.getConfidence())
|
||||||
|
.extractedFrom(s.getExtractedFrom())
|
||||||
|
.context(s.getContext())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList()))
|
||||||
|
.totalCount(suggestions.size())
|
||||||
|
.timestamp(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.controller;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.Term;
|
||||||
|
import com.unicorn.hgzero.ai.biz.usecase.TermDetectionUseCase;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.request.TermDetectionRequest;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.response.TermDetectionResponse;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.DetectedTermDto;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.HighlightInfoDto;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.TextPositionDto;
|
||||||
|
import com.unicorn.hgzero.common.dto.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전문용어 감지 Controller
|
||||||
|
* POST /api/terms/detect
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/terms")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "Term", description = "전문용어 감지 및 설명 API")
|
||||||
|
public class TermController {
|
||||||
|
|
||||||
|
private final TermDetectionUseCase termDetectionUseCase;
|
||||||
|
|
||||||
|
@PostMapping("/detect")
|
||||||
|
@Operation(summary = "전문용어 감지", description = "회의록 텍스트에서 전문용어를 자동으로 감지합니다")
|
||||||
|
public ResponseEntity<ApiResponse<TermDetectionResponse>> detectTerms(
|
||||||
|
@Valid @RequestBody TermDetectionRequest request) {
|
||||||
|
|
||||||
|
log.info("전문용어 감지 요청 - meetingId: {}, organizationId: {}",
|
||||||
|
request.getMeetingId(), request.getOrganizationId());
|
||||||
|
|
||||||
|
List<Term> terms = termDetectionUseCase.detectTerms(
|
||||||
|
request.getMeetingId(),
|
||||||
|
request.getText(),
|
||||||
|
request.getOrganizationId()
|
||||||
|
);
|
||||||
|
|
||||||
|
List<DetectedTermDto> detectedTerms = terms.stream()
|
||||||
|
.map(t -> DetectedTermDto.builder()
|
||||||
|
.term(t.getTerm())
|
||||||
|
.position(t.getPosition() != null ? TextPositionDto.builder()
|
||||||
|
.line(t.getPosition().getLine())
|
||||||
|
.offset(t.getPosition().getOffset())
|
||||||
|
.build() : null)
|
||||||
|
.confidence(t.getConfidence())
|
||||||
|
.category(t.getCategory())
|
||||||
|
.highlight(t.getHighlight())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<HighlightInfoDto> highlightInfo = detectedTerms.stream()
|
||||||
|
.filter(t -> Boolean.TRUE.equals(t.getHighlight()))
|
||||||
|
.map(t -> HighlightInfoDto.builder()
|
||||||
|
.term(t.getTerm())
|
||||||
|
.position(t.getPosition())
|
||||||
|
.style("background-color: yellow")
|
||||||
|
.tooltip("용어 설명 로딩 중...")
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
TermDetectionResponse response = TermDetectionResponse.builder()
|
||||||
|
.detectedTerms(detectedTerms)
|
||||||
|
.totalCount(detectedTerms.size())
|
||||||
|
.highlightInfo(highlightInfo)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.controller;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.ExtractedTodo;
|
||||||
|
import com.unicorn.hgzero.ai.biz.usecase.TodoExtractionUseCase;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.request.TodoExtractionRequest;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.response.TodoExtractionResponse;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.ExtractedTodoDto;
|
||||||
|
import com.unicorn.hgzero.common.dto.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Todo 자동 추출 Controller
|
||||||
|
* POST /api/todos/extract
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/todos")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "Todo", description = "Todo 자동 추출 API")
|
||||||
|
public class TodoController {
|
||||||
|
|
||||||
|
private final TodoExtractionUseCase todoExtractionUseCase;
|
||||||
|
|
||||||
|
@PostMapping("/extract")
|
||||||
|
@Operation(summary = "Todo 자동 추출", description = "회의록에서 액션 아이템을 자동으로 추출하고 담당자를 식별합니다")
|
||||||
|
public ResponseEntity<ApiResponse<TodoExtractionResponse>> extractTodos(
|
||||||
|
@RequestHeader("X-User-Id") String userId,
|
||||||
|
@Valid @RequestBody TodoExtractionRequest request) {
|
||||||
|
|
||||||
|
log.info("Todo 추출 요청 - meetingId: {}, userId: {}", request.getMeetingId(), userId);
|
||||||
|
|
||||||
|
List<ExtractedTodo> todos = todoExtractionUseCase.extractTodos(
|
||||||
|
request.getMeetingId(),
|
||||||
|
request.getMinutesContent(),
|
||||||
|
request.getUserId() != null ? request.getUserId() : userId
|
||||||
|
);
|
||||||
|
|
||||||
|
TodoExtractionResponse response = TodoExtractionResponse.builder()
|
||||||
|
.meetingId(request.getMeetingId())
|
||||||
|
.todos(todos.stream()
|
||||||
|
.map(t -> ExtractedTodoDto.builder()
|
||||||
|
.content(t.getContent())
|
||||||
|
.assignee(t.getAssignee())
|
||||||
|
.dueDate(t.getDueDate())
|
||||||
|
.priority(t.getPriority())
|
||||||
|
.sectionReference(t.getSectionReference())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList()))
|
||||||
|
.totalCount(todos.size())
|
||||||
|
.extractedAt(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.controller;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.ProcessedTranscript;
|
||||||
|
import com.unicorn.hgzero.ai.biz.usecase.TranscriptProcessUseCase;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.request.TranscriptProcessRequest;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.response.TranscriptProcessResponse;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.*;
|
||||||
|
import com.unicorn.hgzero.common.dto.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 자동 작성 Controller
|
||||||
|
* POST /api/transcripts/process
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/transcripts")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "Transcript", description = "회의록 자동 작성 API")
|
||||||
|
public class TranscriptController {
|
||||||
|
|
||||||
|
private final TranscriptProcessUseCase transcriptProcessUseCase;
|
||||||
|
|
||||||
|
@PostMapping("/process")
|
||||||
|
@Operation(summary = "회의록 자동 작성", description = "STT에서 변환된 텍스트를 받아 LLM 기반으로 회의록을 자동 작성합니다")
|
||||||
|
public ResponseEntity<ApiResponse<TranscriptProcessResponse>> processTranscript(
|
||||||
|
@RequestHeader("X-User-Id") String userId,
|
||||||
|
@RequestHeader("X-User-Name") String userName,
|
||||||
|
@Valid @RequestBody TranscriptProcessRequest request) {
|
||||||
|
|
||||||
|
log.info("회의록 자동 작성 요청 - meetingId: {}, userId: {}", request.getMeetingId(), userId);
|
||||||
|
|
||||||
|
ProcessedTranscript result = transcriptProcessUseCase.processTranscript(
|
||||||
|
request.getMeetingId(),
|
||||||
|
request.getTranscriptText(),
|
||||||
|
request.getUserId() != null ? request.getUserId() : userId,
|
||||||
|
request.getUserName() != null ? request.getUserName() : userName,
|
||||||
|
request.getContext() != null ? request.getContext().getTitle() : "",
|
||||||
|
request.getContext() != null ? request.getContext().getParticipants() : null,
|
||||||
|
request.getContext() != null ? request.getContext().getAgenda() : null
|
||||||
|
);
|
||||||
|
|
||||||
|
TranscriptProcessResponse response = mapToResponse(result);
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
private TranscriptProcessResponse mapToResponse(ProcessedTranscript domain) {
|
||||||
|
return TranscriptProcessResponse.builder()
|
||||||
|
.transcriptId(domain.getTranscriptId())
|
||||||
|
.meetingId(domain.getMeetingId())
|
||||||
|
.content(mapContent(domain))
|
||||||
|
.suggestions(null) // TODO: 실시간 제안 기능 구현 시 추가
|
||||||
|
.createdAt(domain.getCreatedAt())
|
||||||
|
.status(domain.getStatus())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TranscriptContentDto mapContent(ProcessedTranscript domain) {
|
||||||
|
return TranscriptContentDto.builder()
|
||||||
|
.summary(domain.getSummary())
|
||||||
|
.discussions(domain.getDiscussions().stream()
|
||||||
|
.map(d -> DiscussionItemDto.builder()
|
||||||
|
.topic(d.getTopic())
|
||||||
|
.speaker(d.getSpeaker())
|
||||||
|
.content(d.getContent())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList()))
|
||||||
|
.decisions(domain.getDecisions().stream()
|
||||||
|
.map(d -> DecisionItemDto.builder()
|
||||||
|
.content(d.getContent())
|
||||||
|
.decisionMaker(d.getDecisionMaker())
|
||||||
|
.category(d.getCategory())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList()))
|
||||||
|
.pendingItems(domain.getPendingItems())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정사항 아이템 DTO
|
||||||
|
* 결정 내용, 결정자, 카테고리 포함
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class DecisionItemDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정 내용
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정자
|
||||||
|
*/
|
||||||
|
private String decisionMaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정 카테고리 (기술, 일정, 리소스, 정책, 기타)
|
||||||
|
*/
|
||||||
|
private String category;
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정사항 제안 DTO
|
||||||
|
* AI가 감지한 결정사항 패턴 정보
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class DecisionSuggestionDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 제안 ID
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정 내용
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정 카테고리 (기술, 일정, 리소스, 정책, 기타)
|
||||||
|
*/
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정자
|
||||||
|
*/
|
||||||
|
private String decisionMaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참여자 목록
|
||||||
|
*/
|
||||||
|
private List<String> participants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 신뢰도 점수 (0-1)
|
||||||
|
*/
|
||||||
|
private Double confidence;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 원문 발췌
|
||||||
|
*/
|
||||||
|
private String extractedFrom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정 배경
|
||||||
|
*/
|
||||||
|
private String context;
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 감지된 전문용어 DTO
|
||||||
|
* 회의록에서 감지된 전문용어 정보
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class DetectedTermDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 용어명
|
||||||
|
*/
|
||||||
|
private String term;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 텍스트 위치 정보
|
||||||
|
*/
|
||||||
|
private TextPositionDto position;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 신뢰도 점수 (0-1)
|
||||||
|
*/
|
||||||
|
private Double confidence;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 용어 카테고리 (기술, 업무, 도메인)
|
||||||
|
*/
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 하이라이트 여부
|
||||||
|
*/
|
||||||
|
private Boolean highlight;
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의사항 아이템 DTO
|
||||||
|
* 논의 주제, 발언자, 논의 내용 포함
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class DiscussionItemDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의 주제
|
||||||
|
*/
|
||||||
|
private String topic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 발언자
|
||||||
|
*/
|
||||||
|
private String speaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의 내용
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의사항 제안 DTO
|
||||||
|
* AI가 추천하는 논의 주제 정보
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class DiscussionSuggestionDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 제안 ID
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의 주제
|
||||||
|
*/
|
||||||
|
private String topic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 제안 이유
|
||||||
|
*/
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 우선순위 (HIGH, MEDIUM, LOW)
|
||||||
|
*/
|
||||||
|
private String priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 안건
|
||||||
|
*/
|
||||||
|
private String relatedAgenda;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 소요 시간 (분)
|
||||||
|
*/
|
||||||
|
private Integer estimatedTime;
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 에러 응답 DTO
|
||||||
|
* API 에러 발생 시 통일된 형식으로 에러 정보 반환
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ErrorResponseDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 에러 코드
|
||||||
|
*/
|
||||||
|
private String error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 에러 메시지
|
||||||
|
*/
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 에러 발생 시각
|
||||||
|
*/
|
||||||
|
private LocalDateTime timestamp;
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 추출된 Todo DTO
|
||||||
|
* AI가 회의록에서 추출한 Todo 정보
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ExtractedTodoDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Todo 내용
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 담당자
|
||||||
|
*/
|
||||||
|
private String assignee;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 마감일
|
||||||
|
*/
|
||||||
|
private LocalDate dueDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 우선순위 (HIGH, MEDIUM, LOW)
|
||||||
|
*/
|
||||||
|
private String priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 회의록 섹션
|
||||||
|
*/
|
||||||
|
private String sectionReference;
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 하이라이트 정보 DTO
|
||||||
|
* 용어 하이라이트 스타일과 툴팁 정보
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class HighlightInfoDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 용어명
|
||||||
|
*/
|
||||||
|
private String term;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 텍스트 위치 정보
|
||||||
|
*/
|
||||||
|
private TextPositionDto position;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 하이라이트 스타일
|
||||||
|
*/
|
||||||
|
private String style;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 툴팁 텍스트
|
||||||
|
*/
|
||||||
|
private String tooltip;
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 맥락 정보 DTO
|
||||||
|
* 회의 제목, 참석자, 안건, 이전 회의록 등의 맥락 정보를 전달
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class MeetingContextDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 제목
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참석자 목록
|
||||||
|
*/
|
||||||
|
private List<String> participants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 안건 목록
|
||||||
|
*/
|
||||||
|
private List<String> agenda;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이전 회의록 내용
|
||||||
|
*/
|
||||||
|
private String previousContent;
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 과거 논의 DTO
|
||||||
|
* 전문용어 관련 과거 논의 정보
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PastDiscussionDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의 날짜
|
||||||
|
*/
|
||||||
|
private LocalDate date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참석자 목록
|
||||||
|
*/
|
||||||
|
private List<String> participants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의 요약
|
||||||
|
*/
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 링크
|
||||||
|
*/
|
||||||
|
private String link;
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 실시간 추천사항 DTO
|
||||||
|
* 논의 주제와 결정사항 제안을 포함
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RealtimeSuggestionsDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의 주제 제안 목록
|
||||||
|
*/
|
||||||
|
private List<DiscussionSuggestionDto> discussionTopics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정사항 제안 목록
|
||||||
|
*/
|
||||||
|
private List<DecisionSuggestionDto> decisions;
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참조 문서 DTO
|
||||||
|
* 전문용어 관련 참조 문서 정보
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ReferenceDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 문서 제목
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 문서 유형 (위키, 매뉴얼, 회의록, 보고서)
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 문서 URL
|
||||||
|
*/
|
||||||
|
private String link;
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 프로젝트 DTO
|
||||||
|
* 전문용어와 관련된 프로젝트 정보
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RelatedProjectDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 프로젝트명
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 연관성 설명
|
||||||
|
*/
|
||||||
|
private String relevance;
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 회의록 DTO
|
||||||
|
* RAG 검색으로 찾은 관련 회의록 정보
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RelatedTranscriptDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 ID
|
||||||
|
*/
|
||||||
|
private String transcriptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 제목
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 날짜
|
||||||
|
*/
|
||||||
|
private LocalDate date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참석자 목록
|
||||||
|
*/
|
||||||
|
private List<String> participants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련도 점수 (0-100%)
|
||||||
|
*/
|
||||||
|
private Double relevanceScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 공통 키워드 목록
|
||||||
|
*/
|
||||||
|
private List<String> commonKeywords;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 링크
|
||||||
|
*/
|
||||||
|
private String link;
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 텍스트 위치 정보 DTO
|
||||||
|
* 줄 번호와 오프셋 정보를 포함
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TextPositionDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 줄 번호
|
||||||
|
*/
|
||||||
|
private Integer line;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 시작 오프셋
|
||||||
|
*/
|
||||||
|
private Integer offset;
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 내용 DTO
|
||||||
|
* 전체 요약, 논의사항, 결정사항, 보류사항 포함
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TranscriptContentDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전체 요약
|
||||||
|
*/
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의사항 목록
|
||||||
|
*/
|
||||||
|
private List<DiscussionItemDto> discussions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정사항 목록
|
||||||
|
*/
|
||||||
|
private List<DecisionItemDto> decisions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 보류사항 목록
|
||||||
|
*/
|
||||||
|
private List<String> pendingItems;
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.request;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정사항 제안 요청 DTO
|
||||||
|
* 회의록 텍스트에서 결정사항 패턴을 감지하여 제안 요청
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class DecisionSuggestionRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID (필수)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "회의 ID는 필수입니다")
|
||||||
|
private String meetingId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재까지의 회의록 텍스트 (필수)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "회의록 텍스트는 필수입니다")
|
||||||
|
private String transcriptText;
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.request;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의사항 제안 요청 DTO
|
||||||
|
* 현재 회의 진행 상황을 분석하여 추가 논의 주제 제안 요청
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class DiscussionSuggestionRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID (필수)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "회의 ID는 필수입니다")
|
||||||
|
private String meetingId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재까지의 회의록 텍스트 (필수)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "회의록 텍스트는 필수입니다")
|
||||||
|
private String transcriptText;
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.request;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 섹션 AI 요약 재생성 요청 DTO
|
||||||
|
* 사용자가 작성한 섹션 내용을 기반으로 AI 요약 재생성 요청
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SectionSummaryRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자가 작성/수정한 섹션 내용 (필수, Markdown 형식)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "섹션 내용은 필수입니다")
|
||||||
|
private String sectionContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID (맥락 이해용, 선택적)
|
||||||
|
*/
|
||||||
|
private String meetingId;
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.request;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전문용어 감지 요청 DTO
|
||||||
|
* 회의록 텍스트에서 전문용어를 자동으로 감지 요청
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TermDetectionRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID (필수)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "회의 ID는 필수입니다")
|
||||||
|
private String meetingId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 분석할 회의록 텍스트 (필수)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "분석할 텍스트는 필수입니다")
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조직 ID
|
||||||
|
*/
|
||||||
|
private String organizationId;
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.request;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Todo 자동 추출 요청 DTO
|
||||||
|
* 회의록에서 액션 아이템을 자동으로 추출하고 담당자 식별 요청
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TodoExtractionRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID (필수)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "회의 ID는 필수입니다")
|
||||||
|
private String meetingId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 요청자 ID
|
||||||
|
*/
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 전체 내용 (필수, Markdown 형식)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "회의록 내용은 필수입니다")
|
||||||
|
private String minutesContent;
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.request;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.MeetingContextDto;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 자동 작성 요청 DTO
|
||||||
|
* STT에서 변환된 텍스트를 받아 LLM 기반 회의록 자동 작성 요청
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TranscriptProcessRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID (필수)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "회의 ID는 필수입니다")
|
||||||
|
private String meetingId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STT에서 변환된 텍스트 (필수)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "회의록 텍스트는 필수입니다")
|
||||||
|
private String transcriptText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자 ID
|
||||||
|
*/
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자 이름
|
||||||
|
*/
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 맥락 정보
|
||||||
|
*/
|
||||||
|
private MeetingContextDto context;
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.response;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.DecisionSuggestionDto;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정사항 제안 응답 DTO
|
||||||
|
* AI가 감지한 결정사항 패턴 목록 반환
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class DecisionSuggestionResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정사항 제안 목록
|
||||||
|
*/
|
||||||
|
private List<DecisionSuggestionDto> suggestions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 제안 개수
|
||||||
|
*/
|
||||||
|
private Integer totalCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 생성 시각
|
||||||
|
*/
|
||||||
|
private LocalDateTime timestamp;
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.response;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.DiscussionSuggestionDto;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의사항 제안 응답 DTO
|
||||||
|
* AI가 제안하는 추가 논의 주제 목록 반환
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class DiscussionSuggestionResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의사항 제안 목록
|
||||||
|
*/
|
||||||
|
private List<DiscussionSuggestionDto> suggestions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 제안 개수
|
||||||
|
*/
|
||||||
|
private Integer totalCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 생성 시각
|
||||||
|
*/
|
||||||
|
private LocalDateTime timestamp;
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.response;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.RelatedTranscriptDto;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 회의록 조회 응답 DTO
|
||||||
|
* RAG 검색으로 찾은 관련 회의록 목록 반환
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RelatedTranscriptsResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 회의록 목록
|
||||||
|
*/
|
||||||
|
private List<RelatedTranscriptDto> relatedTranscripts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 회의록 개수
|
||||||
|
*/
|
||||||
|
private Integer totalCount;
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 섹션 요약 응답 DTO
|
||||||
|
* AI가 생성한 섹션 요약 반환
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SectionSummaryResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 생성된 AI 요약 (2-3문장)
|
||||||
|
*/
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 생성 시간
|
||||||
|
*/
|
||||||
|
private LocalDateTime generatedAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.response;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.DetectedTermDto;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.HighlightInfoDto;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전문용어 감지 응답 DTO
|
||||||
|
* 감지된 전문용어 목록과 하이라이트 정보 반환
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TermDetectionResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 감지된 용어 목록
|
||||||
|
*/
|
||||||
|
private List<DetectedTermDto> detectedTerms;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 감지된 용어 개수
|
||||||
|
*/
|
||||||
|
private Integer totalCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 하이라이트 정보 목록
|
||||||
|
*/
|
||||||
|
private List<HighlightInfoDto> highlightInfo;
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.response;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.PastDiscussionDto;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.ReferenceDto;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.RelatedProjectDto;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전문용어 설명 응답 DTO
|
||||||
|
* RAG 기반 맥락적 용어 설명 반환
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TermExplanationResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 용어명
|
||||||
|
*/
|
||||||
|
private String term;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 간단한 정의
|
||||||
|
*/
|
||||||
|
private String basicDefinition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 회의 맥락에서의 의미
|
||||||
|
*/
|
||||||
|
private String contextualMeaning;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 실제 사용 사례 목록
|
||||||
|
*/
|
||||||
|
private List<String> useCases;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관련 프로젝트 목록
|
||||||
|
*/
|
||||||
|
private List<RelatedProjectDto> relatedProjects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 과거 논의 목록
|
||||||
|
*/
|
||||||
|
private List<PastDiscussionDto> pastDiscussions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 참조 문서 목록
|
||||||
|
*/
|
||||||
|
private List<ReferenceDto> references;
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.response;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.ExtractedTodoDto;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Todo 추출 응답 DTO
|
||||||
|
* AI가 추출한 Todo 목록 반환
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TodoExtractionResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID
|
||||||
|
*/
|
||||||
|
private String meetingId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 추출된 Todo 목록
|
||||||
|
*/
|
||||||
|
private List<ExtractedTodoDto> todos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 추출된 Todo 개수
|
||||||
|
*/
|
||||||
|
private Integer totalCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 추출 시간
|
||||||
|
*/
|
||||||
|
private LocalDateTime extractedAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.dto.response;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.RealtimeSuggestionsDto;
|
||||||
|
import com.unicorn.hgzero.ai.infra.dto.common.TranscriptContentDto;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 자동 작성 응답 DTO
|
||||||
|
* LLM 기반으로 생성된 회의록 정보 반환
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TranscriptProcessResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 생성된 회의록 ID
|
||||||
|
*/
|
||||||
|
private String transcriptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID
|
||||||
|
*/
|
||||||
|
private String meetingId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 내용
|
||||||
|
*/
|
||||||
|
private TranscriptContentDto content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 실시간 추천사항
|
||||||
|
*/
|
||||||
|
private RealtimeSuggestionsDto suggestions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 생성 시간
|
||||||
|
*/
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 상태 (DRAFT, COMPLETED)
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.gateway;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.ProcessedTranscript;
|
||||||
|
import com.unicorn.hgzero.ai.biz.gateway.TranscriptGateway;
|
||||||
|
import com.unicorn.hgzero.ai.infra.gateway.entity.ProcessedTranscriptEntity;
|
||||||
|
import com.unicorn.hgzero.ai.infra.gateway.repository.ProcessedTranscriptJpaRepository;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의록 Gateway 구현체
|
||||||
|
* JPA Repository를 사용한 회의록 영속성 관리
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TranscriptGatewayImpl implements TranscriptGateway {
|
||||||
|
|
||||||
|
private final ProcessedTranscriptJpaRepository repository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProcessedTranscript save(ProcessedTranscript transcript) {
|
||||||
|
log.debug("Saving transcript: transcriptId={}, meetingId={}",
|
||||||
|
transcript.getTranscriptId(), transcript.getMeetingId());
|
||||||
|
|
||||||
|
ProcessedTranscriptEntity entity = ProcessedTranscriptEntity.fromDomain(transcript);
|
||||||
|
ProcessedTranscriptEntity saved = repository.save(entity);
|
||||||
|
|
||||||
|
log.info("Transcript saved successfully: transcriptId={}", saved.getTranscriptId());
|
||||||
|
return saved.toDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ProcessedTranscript> findById(String transcriptId) {
|
||||||
|
log.debug("Finding transcript by id: {}", transcriptId);
|
||||||
|
return repository.findById(transcriptId)
|
||||||
|
.map(ProcessedTranscriptEntity::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ProcessedTranscript> findByMeetingId(String meetingId) {
|
||||||
|
log.debug("Finding transcript by meetingId: {}", meetingId);
|
||||||
|
return repository.findByMeetingId(meetingId)
|
||||||
|
.map(ProcessedTranscriptEntity::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProcessedTranscript> findByMeetingIds(List<String> meetingIds) {
|
||||||
|
log.debug("Finding transcripts by meetingIds: count={}", meetingIds.size());
|
||||||
|
return repository.findByMeetingIdIn(meetingIds).stream()
|
||||||
|
.map(ProcessedTranscriptEntity::toDomain)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProcessedTranscript> findByStatus(String status) {
|
||||||
|
log.debug("Finding transcripts by status: {}", status);
|
||||||
|
return repository.findByStatus(status).stream()
|
||||||
|
.map(ProcessedTranscriptEntity::toDomain)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean existsByMeetingId(String meetingId) {
|
||||||
|
log.debug("Checking transcript existence by meetingId: {}", meetingId);
|
||||||
|
return repository.existsByMeetingId(meetingId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(String transcriptId) {
|
||||||
|
log.debug("Deleting transcript: {}", transcriptId);
|
||||||
|
repository.deleteById(transcriptId);
|
||||||
|
log.info("Transcript deleted successfully: {}", transcriptId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,179 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.gateway.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.unicorn.hgzero.ai.biz.domain.ProcessedTranscript;
|
||||||
|
import com.unicorn.hgzero.common.entity.BaseTimeEntity;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 처리된 회의록 Entity
|
||||||
|
* AI가 처리한 회의록 정보를 데이터베이스에 영속화
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Entity
|
||||||
|
@Table(name = "processed_transcripts")
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ProcessedTranscriptEntity extends BaseTimeEntity {
|
||||||
|
|
||||||
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(name = "transcript_id", length = 50)
|
||||||
|
private String transcriptId;
|
||||||
|
|
||||||
|
@Column(name = "meeting_id", length = 50, nullable = false)
|
||||||
|
private String meetingId;
|
||||||
|
|
||||||
|
@Column(name = "summary", columnDefinition = "TEXT")
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 논의사항 목록 (JSON 형식)
|
||||||
|
*/
|
||||||
|
@Column(name = "discussions", columnDefinition = "TEXT")
|
||||||
|
private String discussions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 결정사항 목록 (JSON 형식)
|
||||||
|
*/
|
||||||
|
@Column(name = "decisions", columnDefinition = "TEXT")
|
||||||
|
private String decisions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 보류사항 목록 (콤마 구분)
|
||||||
|
*/
|
||||||
|
@Column(name = "pending_items", columnDefinition = "TEXT")
|
||||||
|
private String pendingItems;
|
||||||
|
|
||||||
|
@Column(name = "status", length = 20, nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
private String status = "DRAFT";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity를 Domain 모델로 변환
|
||||||
|
*/
|
||||||
|
public ProcessedTranscript toDomain() {
|
||||||
|
return ProcessedTranscript.builder()
|
||||||
|
.transcriptId(this.transcriptId)
|
||||||
|
.meetingId(this.meetingId)
|
||||||
|
.summary(this.summary)
|
||||||
|
.discussions(parseDiscussions(this.discussions))
|
||||||
|
.decisions(parseDecisions(this.decisions))
|
||||||
|
.pendingItems(parsePendingItems(this.pendingItems))
|
||||||
|
.createdAt(this.getCreatedAt())
|
||||||
|
.status(this.status)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain 모델에서 Entity로 변환
|
||||||
|
*/
|
||||||
|
public static ProcessedTranscriptEntity fromDomain(ProcessedTranscript domain) {
|
||||||
|
return ProcessedTranscriptEntity.builder()
|
||||||
|
.transcriptId(domain.getTranscriptId())
|
||||||
|
.meetingId(domain.getMeetingId())
|
||||||
|
.summary(domain.getSummary())
|
||||||
|
.discussions(formatDiscussions(domain.getDiscussions()))
|
||||||
|
.decisions(formatDecisions(domain.getDecisions()))
|
||||||
|
.pendingItems(formatPendingItems(domain.getPendingItems()))
|
||||||
|
.status(domain.getStatus())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 상태 업데이트
|
||||||
|
*/
|
||||||
|
public void updateStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 요약 업데이트
|
||||||
|
*/
|
||||||
|
public void updateSummary(String summary) {
|
||||||
|
this.summary = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Private Helper Methods - JSON 변환
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
private static List<ProcessedTranscript.DiscussionItem> parseDiscussions(String json) {
|
||||||
|
if (json == null || json.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(json,
|
||||||
|
new TypeReference<List<ProcessedTranscript.DiscussionItem>>() {});
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Failed to parse discussions JSON: {}", json, e);
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatDiscussions(List<ProcessedTranscript.DiscussionItem> discussions) {
|
||||||
|
if (discussions == null || discussions.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsString(discussions);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Failed to format discussions to JSON", e);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ProcessedTranscript.DecisionItem> parseDecisions(String json) {
|
||||||
|
if (json == null || json.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(json,
|
||||||
|
new TypeReference<List<ProcessedTranscript.DecisionItem>>() {});
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Failed to parse decisions JSON: {}", json, e);
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatDecisions(List<ProcessedTranscript.DecisionItem> decisions) {
|
||||||
|
if (decisions == null || decisions.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsString(decisions);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Failed to format decisions to JSON", e);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> parsePendingItems(String items) {
|
||||||
|
if (items == null || items.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return Arrays.asList(items.split(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatPendingItems(List<String> items) {
|
||||||
|
if (items == null || items.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return String.join(",", items);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.gateway.repository;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.infra.gateway.entity.ProcessedTranscriptEntity;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 처리된 회의록 JPA Repository
|
||||||
|
* 회의록 데이터 영속성 관리
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface ProcessedTranscriptJpaRepository extends JpaRepository<ProcessedTranscriptEntity, String> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID로 회의록 조회
|
||||||
|
*/
|
||||||
|
Optional<ProcessedTranscriptEntity> findByMeetingId(String meetingId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID 목록으로 회의록 목록 조회
|
||||||
|
*/
|
||||||
|
List<ProcessedTranscriptEntity> findByMeetingIdIn(List<String> meetingIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 상태로 회의록 목록 조회
|
||||||
|
*/
|
||||||
|
List<ProcessedTranscriptEntity> findByStatus(String status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID와 상태로 회의록 조회
|
||||||
|
*/
|
||||||
|
Optional<ProcessedTranscriptEntity> findByMeetingIdAndStatus(String meetingId, String status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 ID로 회의록 존재 여부 확인
|
||||||
|
*/
|
||||||
|
boolean existsByMeetingId(String meetingId);
|
||||||
|
}
|
||||||
@ -0,0 +1,146 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.llm;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.gateway.LlmGateway;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenAI LLM Gateway 구현체
|
||||||
|
* OpenAI API를 사용한 LLM 연동
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class OpenAiLlmGateway implements LlmGateway {
|
||||||
|
|
||||||
|
// TODO: OpenAI API 클라이언트 주입
|
||||||
|
// private final OpenAiClient openAiClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateTranscript(String transcriptText, String title, List<String> participants, List<String> agenda) {
|
||||||
|
log.info("Generating transcript using OpenAI: title={}", title);
|
||||||
|
|
||||||
|
// TODO: OpenAI API 호출
|
||||||
|
// 1. 프롬프트 구성 (회의록 자동 작성 프롬프트)
|
||||||
|
// 2. GPT-4 호출
|
||||||
|
// 3. 응답 JSON 파싱
|
||||||
|
// 4. 반환
|
||||||
|
|
||||||
|
// 임시 mock 응답
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
"summary": "회의록 자동 생성 요약",
|
||||||
|
"discussions": [
|
||||||
|
{
|
||||||
|
"topic": "프로젝트 진행 상황",
|
||||||
|
"speaker": "김철수",
|
||||||
|
"content": "현재 80% 진행 중"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decisions": [
|
||||||
|
{
|
||||||
|
"content": "React로 프론트엔드 개발",
|
||||||
|
"decisionMaker": "이영희",
|
||||||
|
"category": "기술"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pendingItems": ["추가 예산 검토", "외주 업체 선정"]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String extractTodos(String minutesContent) {
|
||||||
|
log.info("Extracting todos using OpenAI");
|
||||||
|
|
||||||
|
// TODO: OpenAI API 호출 (Todo 추출 프롬프트)
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
"todos": [
|
||||||
|
{
|
||||||
|
"content": "API 설계서 작성",
|
||||||
|
"assignee": "박민수",
|
||||||
|
"dueDate": "2025-01-30",
|
||||||
|
"priority": "HIGH",
|
||||||
|
"sectionReference": "결정사항 #3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateSummary(String sectionContent, String meetingContext) {
|
||||||
|
log.info("Generating section summary using OpenAI");
|
||||||
|
|
||||||
|
// TODO: OpenAI API 호출 (섹션 요약 프롬프트)
|
||||||
|
return "AI 기반 회의록 자동화 서비스로 결정. 타겟은 중소기업 및 스타트업이며, 주요 기능은 음성인식, AI 요약, Todo 추출입니다.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String detectTerms(String text, String organizationId) {
|
||||||
|
log.info("Detecting terms using OpenAI: organizationId={}", organizationId);
|
||||||
|
|
||||||
|
// TODO: OpenAI API 호출 (전문용어 감지 프롬프트)
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
"terms": [
|
||||||
|
{
|
||||||
|
"term": "MSA",
|
||||||
|
"position": {"line": 5, "offset": 42},
|
||||||
|
"confidence": 0.92,
|
||||||
|
"category": "기술",
|
||||||
|
"highlight": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String suggestDiscussions(String transcriptText, List<String> agenda) {
|
||||||
|
log.info("Suggesting discussions using OpenAI");
|
||||||
|
|
||||||
|
// TODO: OpenAI API 호출 (논의사항 제안 프롬프트)
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
"suggestions": [
|
||||||
|
{
|
||||||
|
"id": "sugg-001",
|
||||||
|
"topic": "보안 요구사항 검토",
|
||||||
|
"reason": "안건에 포함되어 있으나 아직 논의되지 않음",
|
||||||
|
"priority": "HIGH",
|
||||||
|
"relatedAgenda": "프로젝트 개요",
|
||||||
|
"estimatedTime": 15
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String suggestDecisions(String transcriptText) {
|
||||||
|
log.info("Suggesting decisions using OpenAI");
|
||||||
|
|
||||||
|
// TODO: OpenAI API 호출 (결정사항 제안 프롬프트)
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
"suggestions": [
|
||||||
|
{
|
||||||
|
"id": "dec-001",
|
||||||
|
"content": "React로 프론트엔드 개발",
|
||||||
|
"category": "기술",
|
||||||
|
"decisionMaker": "김철수",
|
||||||
|
"participants": ["김철수", "이영희"],
|
||||||
|
"confidence": 0.85,
|
||||||
|
"extractedFrom": "프론트엔드는 React로 개발하기로 했습니다",
|
||||||
|
"context": "팀원 대부분이 React 경험이 있어 개발 속도가 빠를 것으로 예상"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
package com.unicorn.hgzero.ai.infra.search;
|
||||||
|
|
||||||
|
import com.unicorn.hgzero.ai.biz.gateway.SearchGateway;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Azure AI Search Gateway 구현체
|
||||||
|
* RAG 기반 벡터 검색 기능 제공
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AzureAiSearchGateway implements SearchGateway {
|
||||||
|
|
||||||
|
// TODO: Azure AI Search 클라이언트 주입
|
||||||
|
// private final SearchClient searchClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String searchRelatedTranscripts(String meetingId, String transcriptId, int limit) {
|
||||||
|
log.info("Searching related transcripts: meetingId={}, transcriptId={}, limit={}",
|
||||||
|
meetingId, transcriptId, limit);
|
||||||
|
|
||||||
|
// TODO: Azure AI Search 벡터 검색
|
||||||
|
// 1. 회의록 내용으로 임베딩 생성
|
||||||
|
// 2. 벡터 유사도 검색
|
||||||
|
// 3. 상위 N개 결과 반환
|
||||||
|
|
||||||
|
// 임시 mock 응답
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
"relatedTranscripts": [
|
||||||
|
{
|
||||||
|
"transcriptId": "aa0e8400-e29b-41d4-a716-446655440005",
|
||||||
|
"title": "프로젝트 X 주간 회의",
|
||||||
|
"date": "2025-01-15",
|
||||||
|
"participants": ["김철수", "이영희"],
|
||||||
|
"relevanceScore": 85.5,
|
||||||
|
"commonKeywords": ["MSA", "API Gateway", "Spring Boot"],
|
||||||
|
"link": "/transcripts/aa0e8400-e29b-41d4-a716-446655440005"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String searchTermExplanation(String term, String meetingId, String context) {
|
||||||
|
log.info("Searching term explanation: term={}, meetingId={}", term, meetingId);
|
||||||
|
|
||||||
|
// TODO: Azure AI Search 문서 검색
|
||||||
|
// 1. 용어와 맥락으로 검색 쿼리 구성
|
||||||
|
// 2. 과거 회의록, 위키, 매뉴얼 검색
|
||||||
|
// 3. 관련 문서 반환
|
||||||
|
|
||||||
|
// 임시 mock 응답
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
"term": "MSA",
|
||||||
|
"basicDefinition": "Microservices Architecture의 약자",
|
||||||
|
"contextualMeaning": "이번 프로젝트에서는 확장성과 독립 배포를 위해 MSA를 적용하기로 결정",
|
||||||
|
"useCases": [
|
||||||
|
"2024년 프로젝트 X에서 주문/결제/배송 서비스를 독립적으로 구성",
|
||||||
|
"서비스별 독립 배포로 배포 시간 70% 단축"
|
||||||
|
],
|
||||||
|
"relatedProjects": [
|
||||||
|
{"name": "프로젝트 X", "relevance": "동일한 MSA 아키텍처 적용"}
|
||||||
|
],
|
||||||
|
"pastDiscussions": [
|
||||||
|
{
|
||||||
|
"date": "2024-12-15",
|
||||||
|
"participants": ["김철수", "이영희"],
|
||||||
|
"summary": "MSA 아키텍처의 장단점을 비교하고 적용 방안을 논의",
|
||||||
|
"link": "/transcripts/bb0e8400-e29b-41d4-a716-446655440006"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"title": "MSA 아키텍처 가이드",
|
||||||
|
"type": "위키",
|
||||||
|
"link": "https://wiki.example.com/msa-guide"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void indexTranscript(String transcriptId, String content, String metadata) {
|
||||||
|
log.info("Indexing transcript: transcriptId={}", transcriptId);
|
||||||
|
|
||||||
|
// TODO: Azure AI Search 인덱싱
|
||||||
|
// 1. 회의록 내용 임베딩 생성
|
||||||
|
// 2. 벡터와 메타데이터를 인덱스에 저장
|
||||||
|
// 3. 검색 가능 상태로 만들기
|
||||||
|
|
||||||
|
log.debug("Transcript indexed successfully: {}", transcriptId);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user