Skip to content

Commit 1991271

Browse files
committed
feat: 데이터 전송 방식 수정(문장 별로 전송)
1 parent 6387105 commit 1991271

File tree

12 files changed

+627
-86
lines changed

12 files changed

+627
-86
lines changed

ProjectVG.Api/Controllers/HomeController.cs

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,18 @@
33
namespace ProjectVG.Api.Controllers
44
{
55
[ApiController]
6+
[Route("api/v1")]
67
public class HomeController : ControllerBase
78
{
8-
[HttpGet("/")]
9-
public IActionResult Index()
9+
[HttpGet("config")]
10+
public IActionResult GetConfig()
1011
{
1112
return Ok(new
1213
{
13-
message = "ProjectVG API Server",
14-
version = "1.0.0",
15-
endpoints = new
16-
{
17-
swagger = "/swagger",
18-
characters = "/api/v1/character",
19-
chat = "/api/v1/chat",
20-
health = "/health"
21-
}
22-
});
23-
}
24-
25-
[HttpGet("/health")]
26-
[HttpGet("/api/health")]
27-
public IActionResult Health()
28-
{
29-
return Ok(new
30-
{
31-
status = "ok",
32-
serverTime = DateTime.UtcNow,
33-
message = "ProjectVG API가 정상적으로 실행 중입니다."
14+
messageType = "json",
15+
supportedFormats = new[] { "json", "binary" },
16+
audioFormat = "wav",
17+
version = "1.0.0"
3418
});
3519
}
3620
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System.Text;
2+
3+
namespace ProjectVG.Application.Models.Chat
4+
{
5+
public static class BinaryMessageProtocol
6+
{
7+
public const byte MESSAGE_TYPE_TEXT = 0x01;
8+
public const byte MESSAGE_TYPE_AUDIO = 0x02;
9+
public const byte MESSAGE_TYPE_INTEGRATED = 0x03;
10+
11+
public static byte[] CreateIntegratedMessage(string sessionId, string? text, byte[]? audioData, float? audioLength)
12+
{
13+
using var ms = new MemoryStream();
14+
using var writer = new BinaryWriter(ms);
15+
16+
// 헤더: 메시지 타입 (1바이트)
17+
writer.Write(MESSAGE_TYPE_INTEGRATED);
18+
19+
// 세션 ID 길이 (4바이트) + 세션 ID
20+
var sessionIdBytes = Encoding.UTF8.GetBytes(sessionId);
21+
writer.Write(sessionIdBytes.Length);
22+
writer.Write(sessionIdBytes);
23+
24+
// 텍스트 길이 (4바이트) + 텍스트 (있을 경우)
25+
if (!string.IsNullOrEmpty(text))
26+
{
27+
var textBytes = Encoding.UTF8.GetBytes(text);
28+
writer.Write(textBytes.Length);
29+
writer.Write(textBytes);
30+
}
31+
else
32+
{
33+
writer.Write(0);
34+
}
35+
36+
// 오디오 길이 (4바이트) + 오디오 데이터 (있을 경우)
37+
if (audioData != null && audioData.Length > 0)
38+
{
39+
writer.Write(audioData.Length);
40+
writer.Write(audioData);
41+
}
42+
else
43+
{
44+
writer.Write(0);
45+
}
46+
47+
// 오디오 길이 (4바이트 float)
48+
writer.Write(audioLength ?? 0f);
49+
50+
return ms.ToArray();
51+
}
52+
53+
public static (string sessionId, string? text, byte[]? audioData, float? audioLength) ParseIntegratedMessage(byte[] data)
54+
{
55+
using var ms = new MemoryStream(data);
56+
using var reader = new BinaryReader(ms);
57+
58+
var messageType = reader.ReadByte();
59+
if (messageType != MESSAGE_TYPE_INTEGRATED)
60+
throw new ArgumentException("Invalid message type");
61+
62+
// 세션 ID 읽기
63+
var sessionIdLength = reader.ReadInt32();
64+
var sessionIdBytes = reader.ReadBytes(sessionIdLength);
65+
var sessionId = Encoding.UTF8.GetString(sessionIdBytes);
66+
67+
// 텍스트 읽기
68+
var textLength = reader.ReadInt32();
69+
string? text = null;
70+
if (textLength > 0)
71+
{
72+
var textBytes = reader.ReadBytes(textLength);
73+
text = Encoding.UTF8.GetString(textBytes);
74+
}
75+
76+
// 오디오 데이터 읽기
77+
var audioLength = reader.ReadInt32();
78+
byte[]? audioData = null;
79+
if (audioLength > 0)
80+
{
81+
audioData = reader.ReadBytes(audioLength);
82+
}
83+
84+
// 오디오 길이 읽기
85+
var audioDuration = reader.ReadSingle();
86+
87+
return (sessionId, text, audioData, audioDuration);
88+
}
89+
}
90+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
namespace ProjectVG.Application.Models.Chat
2+
{
3+
/// <summary>
4+
/// 채팅 메시지의 하나의 세그먼트 (텍스트 + 오디오 쌍)
5+
/// </summary>
6+
public class ChatMessageSegment
7+
{
8+
/// <summary>
9+
/// 텍스트 메시지 (선택사항)
10+
/// </summary>
11+
public string? Text { get; set; }
12+
13+
/// <summary>
14+
/// 오디오 데이터 (선택사항)
15+
/// </summary>
16+
public byte[]? AudioData { get; set; }
17+
18+
/// <summary>
19+
/// 오디오 콘텐츠 타입 (예: "audio/wav")
20+
/// </summary>
21+
public string? AudioContentType { get; set; }
22+
23+
/// <summary>
24+
/// 오디오 길이 (초)
25+
/// </summary>
26+
public float? AudioLength { get; set; }
27+
28+
/// <summary>
29+
/// 감정 (예: "happy", "sad", "neutral")
30+
/// </summary>
31+
public string? Emotion { get; set; }
32+
33+
/// <summary>
34+
/// 세그먼트 순서 (0부터 시작)
35+
/// </summary>
36+
public int Order { get; set; }
37+
38+
/// <summary>
39+
/// 텍스트가 있는지 확인
40+
/// </summary>
41+
public bool HasText => !string.IsNullOrEmpty(Text);
42+
43+
/// <summary>
44+
/// 오디오가 있는지 확인
45+
/// </summary>
46+
public bool HasAudio => AudioData != null && AudioData.Length > 0;
47+
48+
/// <summary>
49+
/// 빈 세그먼트인지 확인
50+
/// </summary>
51+
public bool IsEmpty => !HasText && !HasAudio;
52+
53+
/// <summary>
54+
/// 세그먼트 내용을 문자열로 출력
55+
/// </summary>
56+
public override string ToString()
57+
{
58+
var parts = new List<string>();
59+
60+
parts.Add($"Order: {Order}");
61+
62+
if (HasText)
63+
{
64+
parts.Add($"Text: \"{Text}\"");
65+
}
66+
67+
if (HasAudio)
68+
{
69+
parts.Add($"Audio: {AudioData!.Length} bytes, {AudioContentType}, {AudioLength:F2}s");
70+
}
71+
72+
if (!string.IsNullOrEmpty(Emotion))
73+
{
74+
parts.Add($"Emotion: {Emotion}");
75+
}
76+
77+
return $"Segment({string.Join(", ", parts)})";
78+
}
79+
80+
/// <summary>
81+
/// 세그먼트 내용을 간단한 문자열로 출력
82+
/// </summary>
83+
public string ToShortString()
84+
{
85+
var parts = new List<string>();
86+
87+
if (HasText)
88+
{
89+
parts.Add($"\"{Text}\"");
90+
}
91+
92+
if (HasAudio)
93+
{
94+
parts.Add($"[Audio: {AudioLength:F1}s]");
95+
}
96+
97+
return string.Join(" ", parts);
98+
}
99+
100+
/// <summary>
101+
/// 디버그용 상세 정보 출력
102+
/// </summary>
103+
public string ToDebugString()
104+
{
105+
return $"Segment[Order={Order}, Text={HasText}, Audio={HasAudio}, Emotion={Emotion ?? "none"}, AudioSize={AudioData?.Length ?? 0} bytes, AudioLength={AudioLength:F2}s]";
106+
}
107+
108+
/// <summary>
109+
/// 텍스트만 있는 세그먼트 생성
110+
/// </summary>
111+
public static ChatMessageSegment CreateTextOnly(string text, int order = 0)
112+
{
113+
return new ChatMessageSegment
114+
{
115+
Text = text,
116+
Order = order
117+
};
118+
}
119+
120+
/// <summary>
121+
/// 오디오만 있는 세그먼트 생성
122+
/// </summary>
123+
public static ChatMessageSegment CreateAudioOnly(byte[] audioData, string contentType, float? audioLength, int order = 0)
124+
{
125+
return new ChatMessageSegment
126+
{
127+
AudioData = audioData,
128+
AudioContentType = contentType,
129+
AudioLength = audioLength,
130+
Order = order
131+
};
132+
}
133+
134+
/// <summary>
135+
/// 텍스트와 오디오가 모두 있는 세그먼트 생성
136+
/// </summary>
137+
public static ChatMessageSegment CreateIntegrated(string text, byte[] audioData, string contentType, float? audioLength, string? emotion = null, int order = 0)
138+
{
139+
return new ChatMessageSegment
140+
{
141+
Text = text,
142+
AudioData = audioData,
143+
AudioContentType = contentType,
144+
AudioLength = audioLength,
145+
Emotion = emotion,
146+
Order = order
147+
};
148+
}
149+
}
150+
}

ProjectVG.Application/Models/Chat/ChatProcessResult.cs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,27 @@ namespace ProjectVG.Application.Models.Chat
33
public class ChatProcessResult
44
{
55
public string Response { get; set; } = string.Empty;
6-
public List<string> Emotion { get; set; } = new List<string>();
7-
public List<string> Text { get; set; } = new List<string>();
86
public int TokensUsed { get; set; }
97
public double Cost { get; set; }
10-
public List<byte[]> AudioDataList { get; set; } = new List<byte[]>();
11-
public List<string?> AudioContentTypeList { get; set; } = new List<string?>();
12-
public List<float?> AudioLengthList { get; set; } = new List<float?>();
13-
// 기존 단일 필드는 하위 호환을 위해 남겨둡니다.
14-
public byte[]? AudioData { get; set; }
15-
public string? AudioContentType { get; set; }
16-
public float? AudioLength { get; set; }
8+
9+
/// <summary>
10+
/// 메시지 세그먼트 리스트 (텍스트와 오디오가 함께 묶여있음)
11+
/// </summary>
12+
public List<ChatMessageSegment> Segments { get; set; } = new List<ChatMessageSegment>();
13+
14+
/// <summary>
15+
/// 전체 응답 텍스트 (모든 세그먼트의 텍스트를 합친 것)
16+
/// </summary>
17+
public string FullText => string.Join(" ", Segments.Where(s => s.HasText).Select(s => s.Text));
18+
19+
/// <summary>
20+
/// 오디오가 있는 세그먼트가 있는지 확인
21+
/// </summary>
22+
public bool HasAudio => Segments.Any(s => s.HasAudio);
23+
24+
/// <summary>
25+
/// 텍스트가 있는 세그먼트가 있는지 확인
26+
/// </summary>
27+
public bool HasText => Segments.Any(s => s.HasText);
1728
}
1829
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace ProjectVG.Application.Models.Chat
4+
{
5+
public class IntegratedChatMessage
6+
{
7+
[JsonPropertyName("type")]
8+
public string Type { get; set; } = "chat";
9+
10+
[JsonPropertyName("messageType")]
11+
public string MessageType { get; set; } = "json";
12+
13+
[JsonPropertyName("sessionId")]
14+
public string SessionId { get; set; } = string.Empty;
15+
16+
[JsonPropertyName("text")]
17+
public string? Text { get; set; }
18+
19+
[JsonPropertyName("audioData")]
20+
public string? AudioData { get; set; } // Base64 인코딩된 오디오 데이터
21+
22+
[JsonPropertyName("audioFormat")]
23+
public string? AudioFormat { get; set; } = "wav";
24+
25+
[JsonPropertyName("audioLength")]
26+
public float? AudioLength { get; set; }
27+
28+
[JsonPropertyName("timestamp")]
29+
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
30+
31+
[JsonPropertyName("metadata")]
32+
public Dictionary<string, object>? Metadata { get; set; }
33+
34+
// 오디오 데이터를 Base64로 변환하는 메서드
35+
public void SetAudioData(byte[]? audioBytes)
36+
{
37+
if (audioBytes != null && audioBytes.Length > 0)
38+
{
39+
AudioData = Convert.ToBase64String(audioBytes);
40+
}
41+
else
42+
{
43+
AudioData = null;
44+
}
45+
}
46+
}
47+
}

ProjectVG.Application/Services/Chat/Core/ChatOrchestrator.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,13 @@ public async Task ProcessChatRequestAsync(ProcessChatCommand command)
7070
metrics.Add(new StepMetrics { StepName = "[TTS]", TimeMs = sw.Elapsed.TotalMilliseconds, Cost = processResult.Cost });
7171
metricsDto.TTSTimeMs = sw.Elapsed.TotalMilliseconds;
7272
metricsDto.TTSEnabled = !string.IsNullOrWhiteSpace(preContext.VoiceName);
73-
metricsDto.TTSAudioLength = processResult.AudioLength;
73+
74+
// 세그먼트에서 오디오 길이 계산
75+
var totalAudioLength = processResult.Segments
76+
.Where(s => s.HasAudio && s.AudioLength.HasValue)
77+
.Sum(s => s.AudioLength.Value);
78+
metricsDto.TTSAudioLength = totalAudioLength > 0 ? totalAudioLength : null;
79+
7480
metricsDto.TTSCost = processResult.Cost - metricsDto.LLMCost;
7581

7682
// Persist

0 commit comments

Comments
 (0)