소스 검색

feat(session): room-based session resume via dispatcher

- POST /connect accepts optional 'room' field for session resume
- Flutter saves _currentRoom on connect, passes on resume
- Same room → same Worker process → self.hist preserved
- Persistent memory via voiceprint still works as fallback
wenhongquan 3 주 전
부모
커밋
9fc1b405c2
2개의 변경된 파일24개의 추가작업 그리고 12개의 파일을 삭제
  1. 16 9
      asr_agent/dispatcher.py
  2. 8 3
      flutter_asr_client/lib/pages/recording/notifiers/recording_notifier.dart

+ 16 - 9
asr_agent/dispatcher.py

@@ -57,20 +57,27 @@ async def _spawn_worker(room_name: str):
 
 
 async def handle_connect(request: web.Request) -> web.Response:
-    """POST /connect — return tokens for a new unique room and spawn worker."""
+    """POST /connect — return tokens for a room and spawn worker.
+
+    Body: {"identity": "user-xxx", "room": "room-xxx" (optional)}
+    If room is provided and a worker already exists, reuse it.
+    """
     try:
         body = await request.json()
         app_identity = body.get("identity", f"user-{int(time.time()*1000)%100000}")
+        resume_room = body.get("room")
     except Exception:
         app_identity = f"user-{int(time.time()*1000)%100000}"
-
-    room_name = f"room-{int(time.time_ns())}"
-
-    # Spawn worker (runs in background, connects to room)
-    task = asyncio.create_task(_spawn_worker(room_name))
-    _workers[room_name] = task
-
-    logger.info("Room %s created for %s (total workers: %d)", room_name, app_identity, len(_workers))
+        resume_room = None
+
+    if resume_room and resume_room in _workers and not _workers[resume_room].done():
+        room_name = resume_room
+        logger.info("Session resume: %s (%s)", room_name, app_identity)
+    else:
+        room_name = f"room-{int(time.time_ns())}"
+        task = asyncio.create_task(_spawn_worker(room_name))
+        _workers[room_name] = task
+        logger.info("Room %s created for %s (total workers: %d)", room_name, app_identity, len(_workers))
 
     return web.json_response({
         "room": room_name,

+ 8 - 3
flutter_asr_client/lib/pages/recording/notifiers/recording_notifier.dart

@@ -111,16 +111,20 @@ final class RecordingNotifier extends AutoDisposeAsyncNotifier<RecordingState> {
 
   LiveKitService get _liveKit => ref.read(liveKitServiceProvider);
 
-  Future<({String room, String url, String token})> _getRoomInfo(String identity) async {
+  String? _currentRoom;
+
+  Future<({String room, String url, String token})> _getRoomInfo(String identity, {String? resumeRoom}) async {
     final lkUrl = await ref.read(settingsServiceProvider).getLiveKitUrl();
     final dispatcherUrl = await ref.read(settingsServiceProvider).getDispatcherUrl();
 
     if (dispatcherUrl.isNotEmpty) {
       try {
+        final body = <String, String>{'identity': identity};
+        if (resumeRoom != null) body['room'] = resumeRoom;
         final resp = await http.post(
           Uri.parse('$dispatcherUrl/connect'),
           headers: {'Content-Type': 'application/json'},
-          body: jsonEncode({'identity': identity}),
+          body: jsonEncode(body),
         ).timeout(const Duration(seconds: 5));
         if (resp.statusCode == 200) {
           final data = jsonDecode(resp.body) as Map<String, dynamic>;
@@ -147,6 +151,7 @@ final class RecordingNotifier extends AutoDisposeAsyncNotifier<RecordingState> {
     );
 
     final info = await _getRoomInfo(identity);
+    _currentRoom = info.room;
     try {
       await _liveKit.connect(url: info.url, room: info.room, identity: identity, token: info.token);
     } on Exception catch (e) {
@@ -237,7 +242,7 @@ final class RecordingNotifier extends AutoDisposeAsyncNotifier<RecordingState> {
     if (state.value?.status != RecordingStatus.paused) return;
     try {
       final identity = 'user-${_uuid.v4().substring(0, 6)}';
-      final info = await _getRoomInfo(identity);
+      final info = await _getRoomInfo(identity, resumeRoom: _currentRoom);
       await _liveKit.connect(url: info.url, room: info.room, identity: identity, token: info.token);
       _updateState((s) => _startedState(s));
     } on Exception catch (e) {