버튼 하나를 잘못 읽은 일
쇼 카드는 아빠가 상대한테 돌려 보여주는 문장이야. 상대 언어로 쓴 피파의 문장이 크게 있고, 아빠용 한국어 한 줄이 작게 붙고, 피파 목소리를 트는 버튼이 하나 있어. 그 버튼 이름이 원래 "Speak"였어. 어떤 웨이터가 그걸 보고 여기 대고 말하면 번역되는 줄 알고 폰에 대고 말을 했지. 버튼 이름은 Listen으로 바뀌었고, 아빠는 그 오해를 기능으로 만들었어. 사람들이 어차피 카드한테 말을 거니까, 카드가 들으면 되잖아.
실제로 그런 기능을 넣는 건 어때? 이건 아빠한테도 유용할 거야. 카드에서 바로 말을 하면 (어떤 언어로 입력되든 번역어를 판단해서 A -> B 언어로) 실제 번역해주는 기능. 아빠가 쓰면 바로 주문 카드가 되는 식이고. 상대 원어민이 쓰면 아빠가 볼 수 있는 우리말이 나오는 식이고. 실시간 번역인 거지. 대신 피파가 문맥을 알아서 판단하는 거고.
조각 다섯, 카드 하나
이 기능은 레포 하나에 다 안 담겨. 그러니까 누가 뭘 가졌는지부터 보자.
| 조각 | 주인 |
|---|---|
| 화면 위 카드: Talk, 실시간 자막, 방향 칩, 다듬은 문장 | Sibling Kit의 phrase card와 통역 모듈(웹은 KitPhraseCard랑 kitInterpreter.ts, iOS는 CwkKitInterpreter). 그리는 건 cwkPippa 웹이랑 피파 아이폰 앱 |
| 들어도 된다는 허가 | cwkPippa의 /api/stt/realtime-session, language_code="auto". Scribe v2 Realtime 토큰은 cwkBellows가 찍어 주고, 기기가 Scribe에 직접 연결해 |
| 문 | cwkPippa의 /api/interpret. 루프백으로 Spark에 넘기기만 하는 얇은 문 |
| 시팅과 그 교환들 | Spark 엔진. 대화 순간 하나, 발화마다 exchange 턴 하나, 녹음, 이벤트 |
| 카드에 입힐 피파 목소리 | cwkBellows. Spark가 자기 음성 바인딩으로 불러 |
문은 독스트링에 자기가 넘지 않는 선을 적어 뒀어. 여기선 아무것도 번역하지 않고, 아무것도 저장하지 않는다. 하는 일은 id가 ULID 모양인지 보고, 녹음의 역할을 두 가지 중 하나로 제한하고, Spark가 준 답이나 거절을 그대로 돌려주는 거야. Spark가 죽어 있으면 카드가 두 언어로 그렇다고 말하고, Listen은 그래도 돌아가.
듣는 쪽은 Voice Mode Quest 영역이야. Scribe 실시간 소켓, 말하는 중간에 뜨는 단어, 말이 끝났다고 보는 시간 창은 거기서 배워. 통역사는 그 귀를 그대로 쓰고 딱 하나만 바꿨어. 언어를 미리 정하지 않는다는 거. cwkPippa 쪽 집 구조는 Pippa Quest가 다뤄.
왜 순간이야
통역사가 생기기 전에도 Spark엔 대화 순간이 있었어. 거기엔 아빠가 미리 써 두는 카드, 답 마이크, 상대 말을 피파가 옮겨 주는 릴레이가 들어 있었지. 테이블에 앉아 있는 시간도 그런 순간 하나로 들어가. 아빠가 카드에서 Talk를 누르면 시팅이 열리고, 엔진은 시작한 카드를 순간의 origin으로 들고 있어. 거기엔 카드 문장, 아빠가 뜻한 것, 그 언어, 카드가 나온 cwkPippa 대화까지 담겨. 시팅의 장면 언어는 카드 언어로 시작해.
새 종류의 기록을 만들지 않고 순간으로 둔 덕에 세 가지를 한꺼번에 얻어. 시팅이 사진이나 간판처럼 코퍼스에 들어가니까 검색되고 기억돼. 아빠의 베일 씌운 기록은 원래 낯선 사람 목소리를 둘 사적인 공간이라, 녹음이 안전하게 머물 데가 이미 있어. 그리고 뒤에 오는 턴이 순간을 맥락으로 읽으니까, 테이블의 다음 문장이 앞 문장을 알아.
나중에 아빠가 빈 카드를 달라고 했어. 문장 없이 대화를 시작하는 방법이 필요했던 거야. 빈 시팅엔 origin이 없어. 그럴 땐 cwkPippa 문이 아빠의 가장 최근 위치 기록을 봐. 여섯 시간 안쪽 것만 쓰고, 역지오코딩으로 나라를 알아낸 다음 그 나라를 언어로 바꿔. 일본이면 일본어, 포르투갈이면 포르투갈어. 쓸 만한 위치가 없으면 아빠의 첫 문장이 정해. 피파가 아빠 첫 카드를 쓴 언어가 시팅의 언어가 돼.
Spark 자체 앱이 아직 안 하는 것
여기 경계를 똑바로 잡아 둬. 시팅과 교환은 Spark 엔진 거야. 그런데 지금 /exchanges를 부르는 건 Spark 웹도 SparkMobile도 아니야. 부르는 쪽은 전부 cwkPippa 화면이고, 위의 문을 지나와. Spark 웹에도 킷의 phrase card가 통역 transport까지 통째로 들어와 있긴 해. 근데 transport를 넘겨주는 Spark 화면이 하나도 없어서, Spark 카드는 예전 그대로야.
이 순서는 아빠가 정했어. 아빠가 원래 카드를 보여주던 cwkPippa 웹이랑 피파 아이폰 앱이 먼저고, Spark 웹이 두 번째, SparkMobile이 맨 끝이야. SparkMobile이 끝인 데엔 구조적인 이유가 있어. Spark 폰은 Spark 엔진하고만 말하고 제공자 자격 증명을 하나도 안 들고 있어. 그래서 cwkPippa 화면처럼 ElevenLabs에 직접 연결할 수 없고, 먼저 엔진을 거치는 중계가 있어야 해.
그러니까 이 트랙에서 엔진 발췌는 다 Spark 거로 읽고, 카드 동작은 cwkPippa가 그리는 Sibling Kit 카드로 읽어.