npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@medc-com-br/ngx-jaimes-scribe

v0.1.23

Published

Angular library for real-time medical transcription

Readme

ngx-jaimes-scribe

Biblioteca Angular para transcrição de áudio em tempo real com suporte a diarização de speakers e geração automática de documentos clínicos via IA.

Instalação

npm install @medc-com-br/ngx-jaimes-scribe
# ou
pnpm add @medc-com-br/ngx-jaimes-scribe

Uso Básico

import { Component } from '@angular/core';
import { RecorderComponent } from '@medc-com-br/ngx-jaimes-scribe';

@Component({
  selector: 'app-consultation',
  standalone: true,
  imports: [RecorderComponent],
  template: `
    <ngx-jaimes-scribe-recorder
      [wsUrl]="wsUrl"
      [token]="token"
      (sessionStarted)="onSessionStarted($event)"
      (sessionFinished)="onSessionFinished($event)"
      (error)="onError($event)"
    />
  `,
})
export class ConsultationComponent {
  wsUrl = 'wss://stream.example.com/stream';
  token = 'your-jwt-token';

  onSessionStarted(sessionId: string): void {
    console.log('Sessão iniciada:', sessionId);
  }

  onSessionFinished(result: { transcript: string; entries: unknown[] }): void {
    console.log('Transcrição completa:', result.transcript);
  }

  onError(error: Error): void {
    console.error('Erro:', error.message);
  }
}

API Reference

Selector

<ngx-jaimes-scribe-recorder />

Inputs

| Input | Tipo | Obrigatório | Default | Descrição | |-------|------|-------------|---------|-----------| | wsUrl | string | ✅ Sim* | - | URL do WebSocket do serviço de streaming | | token | string | Não | '' | Token JWT para autenticação | | speakerLabels | Record<number, string> | Não | {} | Labels customizados para speakers | | doctorName | string | Não | '' | Nome do médico (exibido quando speaker é identificado como doctor) | | patientName | string | Não | '' | Nome do paciente (exibido quando speaker é identificado como patient) | | companionName | string | Não | '' | Nome do acompanhante (exibido quando speaker é identificado como companion) | | templates | TemplateOption[] | Não | [] | Lista de templates para geração de documentos | | apiUrl | string | Não | '' | URL base da API (geração de documentos, identificação de speakers, playback) | | sessionId | string | Não | - | ID de sessão para modo playback (player de áudio + transcrição) | | resumeSessionId | string | Não | - | ID de sessão para restaurar estado após refresh (sem player de áudio) |

* wsUrl é obrigatório apenas no modo gravação (quando sessionId e resumeSessionId não são fornecidos)

Detalhes dos Inputs

wsUrl (obrigatório)

URL completa do WebSocket de streaming. O componente adiciona automaticamente o query parameter token.

<ngx-jaimes-scribe-recorder
  [wsUrl]="'wss://stream.jaimes.example.com/stream'"
/>

token

Token JWT para autenticação. Usado tanto na conexão WebSocket quanto nas chamadas ao Lambda.

<ngx-jaimes-scribe-recorder
  [wsUrl]="wsUrl"
  [token]="authService.getToken()"
/>

speakerLabels

Mapeamento de índices de speaker para labels customizados. Útil para identificar médico e paciente.

speakerLabels: Record<number, string> = {
  0: 'Dr. Silva',
  1: 'Paciente',
};
<ngx-jaimes-scribe-recorder
  [wsUrl]="wsUrl"
  [token]="token"
  [speakerLabels]="speakerLabels"
/>

Labels padrão (quando não especificado):

  • 0: "Pessoa 1"
  • 1: "Pessoa 2"
  • 2: "Pessoa 3"
  • 3: "Pessoa 4"

doctorName, patientName, companionName

Nomes para exibir na UI quando os speakers são identificados automaticamente. A identificação usa Claude Sonnet para analisar o conteúdo da conversa e determinar quem é médico, paciente ou acompanhante.

<ngx-jaimes-scribe-recorder
  [wsUrl]="wsUrl"
  [token]="token"
  [doctorName]="'Dr. João Silva'"
  [patientName]="'Maria Santos'"
  [companionName]="'José Santos'"
/>

Na UI: Exibe "Dr. João Silva", "Maria Santos", etc. Na API/Resumo: Usa labels genéricos "Médico", "Paciente", "Acompanhante".


Identificação Automática de Speakers

O componente identifica automaticamente quem é cada speaker (Médico, Paciente, Acompanhante) usando IA.

Fluxo

  1. Gravação em andamento: Diarização ativa (Deepgram retorna speaker IDs 0, 1, 2...)
  2. Acumulação: Sistema aguarda 2+ speakers com pelo menos 100 caracteres cada
  3. Identificação automática: Envia amostra para /identify-speakers → Claude Sonnet analisa contexto
  4. Atualização da UI: Labels mudam de "Pessoa 1" → "Dr. João Silva" (ou "Médico" se nome não fornecido)
  5. Re-identificação: Se novo speaker aparecer, identifica novamente incluindo o novo

Correção Manual

O médico pode clicar no label de qualquer speaker para corrigir a identificação:

[Clica no label "Médico"]
        ↓
[Dropdown aparece]
├── 🩺 Médico
├── 🙋 Paciente
├── 👥 Acompanhante
└── ✏️ Outro...

Indicador de Baixa Confiança

Quando a IA tem confiança < 70%, aparece um indicador ⚠️ para o médico revisar.

Persistência

O mapeamento de speakers é salvo automaticamente no Jaimes (S3) e retornado via GET /session/{sessionId}. Não é necessário persistir no EHR.

Labels na Geração de Anamnese

Quando o documento é gerado, o Lambda /generate usa os roles identificados:

[Médico]: Bom dia, como posso ajudá-lo?
[Paciente]: Doutor, estou com dor nas costas há duas semanas...
[Acompanhante]: Ele também tem dormido mal por causa disso.

templates

Lista de templates disponíveis para geração de documentos. O botão "Gerar Resumo" só aparece se houver templates configurados e uma sessão finalizada.

import { TemplateOption } from '@medc-com-br/ngx-jaimes-scribe';

templates: TemplateOption[] = [
  {
    id: 'anamnese',
    name: 'Anamnese Completa',
    description: 'Documento SOAP padrão',
    content: {
      type: 'object',
      properties: {
        queixa_principal: {
          type: 'string',
          description: 'Queixa principal do paciente'
        },
        historia_doenca_atual: {
          type: 'string',
          description: 'História da doença atual'
        },
        hipotese_diagnostica: {
          type: 'string',
          description: 'Hipótese diagnóstica'
        },
        conduta: {
          type: 'string',
          description: 'Conduta médica proposta'
        },
      },
      required: ['queixa_principal', 'hipotese_diagnostica', 'conduta'],
    },
  },
  {
    id: 'resumo',
    name: 'Resumo Rápido',
    description: 'Resumo simplificado da consulta',
    content: {
      type: 'object',
      properties: {
        resumo: { type: 'string', description: 'Resumo da consulta' },
        proximos_passos: { type: 'string', description: 'Próximos passos' },
      },
      required: ['resumo'],
    },
  },
];

apiUrl

URL base da API para todas as operações do componente:

  • POST {apiUrl}/generate - Geração de documentos
  • POST {apiUrl}/identify-speakers - Identificação de speakers (médico/paciente)
  • GET {apiUrl}/session/{sessionId} - Carregar sessões anteriores (playback)
<ngx-jaimes-scribe-recorder
  [apiUrl]="'https://api.example.com'"
  [sessionId]="previousSessionId"
  [token]="token"
/>

sessionId (Modo Playback)

Quando fornecido, o componente entra em modo playback:

  • Carrega áudio e transcrição da sessão anterior
  • Exibe player de áudio com controles (play/pause, seek, velocidade)
  • Sincroniza transcrição com posição do áudio (highlight de palavra atual)
  • Permite clicar em segmentos/palavras para pular para aquele ponto
<!-- Modo Playback - Revisar sessão anterior com áudio -->
<ngx-jaimes-scribe-recorder
  [sessionId]="'session-abc123'"
  [apiUrl]="apiUrl"
  [token]="token"
  [speakerLabels]="{ 0: 'Médico', 1: 'Paciente' }"
/>

resumeSessionId (Restaurar Estado)

Quando fornecido, o componente restaura o estado de uma sessão finalizada:

  • Carrega apenas a transcrição (sem player de áudio)
  • Exibe a transcrição como se a sessão tivesse acabado de ser finalizada
  • Habilita o botão "Gerar Resumo" para gerar novos documentos
  • Útil para refresh de página ou navegação de volta à consulta
<!-- Restaurar estado após refresh -->
<ngx-jaimes-scribe-recorder
  [resumeSessionId]="savedSessionId"
  [apiUrl]="apiUrl"
  [token]="token"
  [templates]="templates"
/>

Exemplo de uso com persistência:

@Component({
  template: `
    <ngx-jaimes-scribe-recorder
      [wsUrl]="wsUrl"
      [token]="token"
      [apiUrl]="apiUrl"
      [resumeSessionId]="savedSessionId()"
      [templates]="templates"
      (sessionFinished)="onSessionFinished($event)"
    />
  `
})
export class ConsultationComponent implements OnInit {
  savedSessionId = signal<string | undefined>(undefined);

  ngOnInit() {
    // Restaurar sessionId salvo (localStorage, URL param, etc)
    const saved = localStorage.getItem('currentSessionId');
    if (saved) {
      this.savedSessionId.set(saved);
    }
  }

  onSessionFinished(result: { transcript: string }) {
    const sessionId = this.recorderComponent.lastSessionId();
    // Salvar para poder restaurar após refresh
    localStorage.setItem('currentSessionId', sessionId);
  }
}

Diferença entre sessionId e resumeSessionId:

| Aspecto | sessionId (Playback) | resumeSessionId (Restaurar) | |---------|------------------------|-------------------------------| | Player de áudio | ✅ Sim | ❌ Não | | Transcrição | ✅ Com highlight sincronizado | ✅ Estática | | Botão "Gerar Resumo" | ❌ Não | ✅ Sim | | Controles de gravação | ❌ Não | ❌ Não | | Caso de uso | Revisar consulta passada | Continuar após refresh |


Outputs

| Output | Tipo | Descrição | |--------|------|-----------| | sessionStarted | string | Emitido quando a gravação inicia, com o sessionId | | sessionFinished | { transcript: string; entries: TranscriptEntry[] } | Emitido ao finalizar, com transcrição completa | | documentGenerated | GeneratedDocument | Emitido quando um documento é gerado com sucesso | | generationError | Error | Emitido quando há erro na geração do documento | | error | Error | Emitido em erros de gravação, conexão ou transcrição |

Detalhes dos Outputs

sessionStarted

Emitido após a conexão WebSocket ser estabelecida e a captura de áudio iniciar.

onSessionStarted(sessionId: string): void {
  this.currentSessionId = sessionId;
  console.log('Gravação iniciada:', sessionId);
}

sessionFinished

Emitido ao clicar em "Finalizar". Contém a transcrição completa e todas as entradas individuais.

interface TranscriptEntry {
  id: number;
  text: string;
  speaker?: number;
  isFinal: boolean;
  startTime?: number;
  endTime?: number;
}

onSessionFinished(result: { transcript: string; entries: TranscriptEntry[] }): void {
  console.log('Palavras:', result.transcript.split(' ').length);
  console.log('Segmentos:', result.entries.length);

  // Salvar no prontuário
  this.ehr.saveTranscription(result.transcript);
}

documentGenerated

Emitido após a geração bem-sucedida de um documento via Lambda.

import { GeneratedDocument } from '@medc-com-br/ngx-jaimes-scribe';

onDocumentGenerated(doc: GeneratedDocument): void {
  console.log('Template usado:', doc.templateId);
  console.log('Conteúdo:', doc.content);

  // Exemplo de conteúdo para template "anamnese"
  // {
  //   queixa_principal: "Dor de cabeça há 3 dias",
  //   historia_doenca_atual: "Paciente relata...",
  //   hipotese_diagnostica: "Cefaleia tensional",
  //   conduta: "Dipirona 500mg 6/6h..."
  // }
}

generationError

Emitido quando há falha na geração do documento.

onGenerationError(error: Error): void {
  console.error('Falha na geração:', error.message);
  // Possíveis erros:
  // - "Lambda URL not provided"
  // - "No session available"
  // - "HTTP 500" (erro do servidor)
  // - "Transcription not found" (sessão expirou)
}

error

Emitido em erros gerais de gravação ou conexão.

onError(error: Error): void {
  console.error('Erro:', error.message);
  // Possíveis erros:
  // - "Permission denied" (microfone negado)
  // - "WebSocket connection failed"
  // - "Transcription service unavailable"
}

Interfaces

TemplateOption

interface TemplateOption {
  id: string;                       // Identificador único do template
  name: string;                     // Nome exibido no menu
  description?: string;             // Descrição opcional
  content: Record<string, unknown>; // JSON Schema para geração
}

GeneratedDocument

interface GeneratedDocument {
  sessionId: string;                  // ID da sessão de gravação
  templateId: string;                 // ID do template usado
  content: Record<string, unknown>;   // Documento gerado
  generatedAt: string;                // ISO timestamp
}

TranscriptionEvent

interface TranscriptionEvent {
  type: 'connected' | 'partial' | 'final' | 'error';
  sessionId: string;
  transcript?: string;
  timestamp?: number;
  message?: string;          // Mensagem de erro (quando type='error')
  words?: TranscriptionWord[];
  speaker?: number;          // Índice do speaker (diarização)
  start?: number;            // Tempo inicial (segundos)
  end?: number;              // Tempo final (segundos)
  confidence?: number;       // Confiança da transcrição (0-1)
}

interface TranscriptionWord {
  word: string;
  start: number;
  end: number;
  confidence?: number;
  speaker?: number;
}

Customização Visual (CSS Variables)

O componente usa CSS Custom Properties para permitir customização completa sem sobrescrever estilos.

Cores Principais

ngx-jaimes-scribe-recorder {
  --scribe-primary: #4caf50;           /* Cor principal (botões, destaques) */
  --scribe-primary-dark: #388e3c;      /* Hover da cor principal */
  --scribe-primary-light: #81c784;     /* Estado connecting */
  --scribe-danger: #f44336;            /* Botão gravando (vermelho) */
  --scribe-danger-dark: #d32f2f;       /* Hover do danger */
}

Cores de Texto

ngx-jaimes-scribe-recorder {
  --scribe-text-color: #212121;        /* Texto principal */
  --scribe-text-partial: #9e9e9e;      /* Texto parcial/placeholder */
}

Tipografia

ngx-jaimes-scribe-recorder {
  --scribe-font-family: inherit;       /* Herda da aplicação */
  --scribe-font-size: 1rem;            /* Tamanho base */
}

Backgrounds e Bordas

ngx-jaimes-scribe-recorder {
  --scribe-bg: #ffffff;                /* Background do componente */
  --scribe-bg-transcript: #f5f5f5;     /* Background da área de transcrição */
  --scribe-border-radius: 8px;         /* Arredondamento */
  --scribe-border-color: #e0e0e0;      /* Cor das bordas */
}

Indicador de Nível de Áudio

ngx-jaimes-scribe-recorder {
  --scribe-level-bg: #e0e0e0;          /* Background da barra */
  --scribe-level-fill: #4caf50;        /* Preenchimento (nível) */
}

Cores dos Speakers (Diarização)

ngx-jaimes-scribe-recorder {
  --scribe-speaker-0: #2196f3;         /* Speaker 0 (azul) */
  --scribe-speaker-1: #9c27b0;         /* Speaker 1 (roxo) */
  --scribe-speaker-2: #ff9800;         /* Speaker 2 (laranja) */
  --scribe-speaker-3: #009688;         /* Speaker 3 (teal) */
}

Exemplo: Tema Escuro

ngx-jaimes-scribe-recorder {
  --scribe-bg: #1e1e1e;
  --scribe-bg-transcript: #2d2d2d;
  --scribe-text-color: #ffffff;
  --scribe-text-partial: #888888;
  --scribe-border-color: #444444;
  --scribe-level-bg: #444444;
  --scribe-primary: #66bb6a;
  --scribe-primary-dark: #4caf50;
}

Exemplo: Tema Médico (Azul)

ngx-jaimes-scribe-recorder {
  --scribe-primary: #1976d2;
  --scribe-primary-dark: #1565c0;
  --scribe-primary-light: #64b5f6;
  --scribe-level-fill: #1976d2;
  --scribe-speaker-0: #1976d2;
  --scribe-speaker-1: #e91e63;
}

Exemplo Completo

import { Component, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
  RecorderComponent,
  TemplateOption,
  GeneratedDocument
} from '@medc-com-br/ngx-jaimes-scribe';

@Component({
  selector: 'app-medical-consultation',
  standalone: true,
  imports: [CommonModule, RecorderComponent],
  template: `
    <div class="consultation-container">
      <header>
        <h1>Consulta - {{ patientName }}</h1>
      </header>

      <ngx-jaimes-scribe-recorder
        [wsUrl]="wsUrl"
        [token]="token"
        [speakerLabels]="speakerLabels"
        [templates]="templates"
        [apiUrl]="apiUrl"
        (sessionStarted)="onSessionStarted($event)"
        (sessionFinished)="onSessionFinished($event)"
        (documentGenerated)="onDocumentGenerated($event)"
        (generationError)="onGenerationError($event)"
        (error)="onError($event)"
      />

      @if (generatedDoc()) {
        <section class="generated-document">
          <h2>Documento Gerado</h2>
          @for (field of getDocFields(); track field.key) {
            <div class="field">
              <label>{{ field.key }}</label>
              <p>{{ field.value }}</p>
            </div>
          }
          <button (click)="saveToEHR()">Salvar no Prontuário</button>
        </section>
      }

      @if (errorMessage()) {
        <div class="error-toast">{{ errorMessage() }}</div>
      }
    </div>
  `,
  styles: [`
    .consultation-container {
      max-width: 800px;
      margin: 0 auto;
      padding: 2rem;
    }

    ngx-jaimes-scribe-recorder {
      --scribe-primary: #1976d2;
      --scribe-border-radius: 12px;
    }

    .generated-document {
      margin-top: 2rem;
      padding: 1.5rem;
      background: #f5f5f5;
      border-radius: 8px;
    }

    .field {
      margin-bottom: 1rem;
    }

    .field label {
      font-weight: 600;
      color: #1976d2;
      text-transform: capitalize;
    }

    .error-toast {
      position: fixed;
      bottom: 1rem;
      right: 1rem;
      padding: 1rem;
      background: #f44336;
      color: white;
      border-radius: 4px;
    }
  `],
})
export class MedicalConsultationComponent {
  patientName = 'João da Silva';

  wsUrl = 'wss://stream.jaimes.example.com/stream';
  token = 'eyJhbGciOiJIUzI1NiIs...';
  apiUrl = 'https://api.jaimes.example.com';

  speakerLabels: Record<number, string> = {
    0: 'Dr. Oliveira',
    1: 'Paciente',
  };

  templates: TemplateOption[] = [
    {
      id: 'soap',
      name: 'SOAP',
      description: 'Subjetivo, Objetivo, Avaliação, Plano',
      content: {
        type: 'object',
        properties: {
          subjetivo: { type: 'string', description: 'Queixas e história relatada' },
          objetivo: { type: 'string', description: 'Achados do exame físico' },
          avaliacao: { type: 'string', description: 'Diagnóstico/hipóteses' },
          plano: { type: 'string', description: 'Conduta e tratamento' },
        },
        required: ['subjetivo', 'avaliacao', 'plano'],
      },
    },
    {
      id: 'receita',
      name: 'Receituário',
      description: 'Prescrição médica',
      content: {
        type: 'object',
        properties: {
          medicamentos: {
            type: 'array',
            items: {
              type: 'object',
              properties: {
                nome: { type: 'string' },
                dose: { type: 'string' },
                posologia: { type: 'string' },
              },
            },
          },
          orientacoes: { type: 'string' },
        },
      },
    },
  ];

  currentSessionId = signal<string | null>(null);
  generatedDoc = signal<GeneratedDocument | null>(null);
  errorMessage = signal<string | null>(null);

  onSessionStarted(sessionId: string): void {
    this.currentSessionId.set(sessionId);
    console.log('Gravação iniciada:', sessionId);
  }

  onSessionFinished(result: { transcript: string; entries: unknown[] }): void {
    console.log('Transcrição:', result.transcript);
    console.log('Segmentos:', result.entries.length);
  }

  onDocumentGenerated(doc: GeneratedDocument): void {
    this.generatedDoc.set(doc);
    console.log('Documento gerado:', doc);
  }

  onGenerationError(error: Error): void {
    this.showError(`Erro na geração: ${error.message}`);
  }

  onError(error: Error): void {
    this.showError(error.message);
  }

  getDocFields(): { key: string; value: string }[] {
    const doc = this.generatedDoc();
    if (!doc) return [];
    return Object.entries(doc.content).map(([key, value]) => ({
      key,
      value: typeof value === 'string' ? value : JSON.stringify(value, null, 2),
    }));
  }

  saveToEHR(): void {
    const doc = this.generatedDoc();
    if (!doc) return;
    // Integrar com seu sistema de prontuário
    console.log('Salvando no prontuário...', doc);
  }

  private showError(message: string): void {
    this.errorMessage.set(message);
    setTimeout(() => this.errorMessage.set(null), 5000);
  }
}

Arquitetura de Comunicação

┌─────────────────────────────────────────────────────────────────┐
│                        Angular App                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │              RecorderComponent                           │    │
│  │  ┌─────────────────┐  ┌─────────────────────────────┐   │    │
│  │  │ AudioCapture    │  │ ScribeSocket                │   │    │
│  │  │ Service         │  │ Service                     │   │    │
│  │  │                 │  │                             │   │    │
│  │  │ - Microfone     │  │ - WebSocket                 │   │    │
│  │  │ - VAD           │──┼─► Streaming                 │   │    │
│  │  │ - PCM 16kHz     │  │ - Eventos                   │   │    │
│  │  └─────────────────┘  └──────────────┬──────────────┘   │    │
│  └──────────────────────────────────────┼──────────────────┘    │
└─────────────────────────────────────────┼───────────────────────┘
                                          │ WSS
                                          ▼
┌─────────────────────────────────────────────────────────────────┐
│                     AWS Infrastructure                           │
│                                                                  │
│  ┌──────────────┐    ┌─────────────────────────────────────┐    │
│  │    ALB       │───►│ ECS Fargate (Stream Service)        │    │
│  └──────────────┘    │                                     │    │
│                      │  ┌───────────────────────────────┐   │    │
│                      │  │ Deepgram Nova-3               │   │    │
│                      │  └───────────────────────────────┘   │    │
│                      │                                     │    │
│                      │  ┌─────────────────────────────┐    │    │
│                      │  │ SessionManager              │    │    │
│                      │  │ - S3 (áudio + transcrição)  │    │    │
│                      │  │ - DynamoDB (metadados)      │    │    │
│                      │  └─────────────────────────────┘    │    │
│                      └─────────────────────────────────────┘    │
│                                                                  │
│  ┌──────────────┐    ┌─────────────────────────────────────┐    │
│  │ API Gateway  │───►│ Lambda GenAI                        │    │
│  │ POST/generate│    │                                     │    │
│  └──────────────┘    │  ┌─────────────────────────────┐    │    │
│                      │  │ AWS Bedrock                 │    │    │
│                      │  │ Claude Sonnet 4.5           │    │    │
│                      │  └─────────────────────────────┘    │    │
│                      └─────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘

Fluxo de Operação

1. Gravação

  1. Usuário clica no botão de microfone
  2. Componente solicita permissão do microfone
  3. Conexão WebSocket é estabelecida
  4. Áudio é capturado, processado (VAD) e enviado em chunks
  5. Transcrições parciais e finais são recebidas e exibidas
  6. Usuário pode pausar/retomar a qualquer momento
  7. Ao finalizar, sessionFinished é emitido

2. Geração de Documento

  1. Após finalizar, botão "Gerar Resumo" aparece
  2. Usuário seleciona um template
  3. Componente faz POST para Lambda com sessionId e outputSchema
  4. Lambda recupera transcrição do S3
  5. Bedrock gera documento estruturado
  6. documentGenerated é emitido com o resultado

Requisitos

  • Angular 19+
  • Navegador com suporte a:
    • Web Audio API
    • MediaDevices API
    • WebSocket
  • Permissão de microfone

Troubleshooting

Microfone não detectado

// Verifique permissões
const permission = await navigator.permissions.query({ name: 'microphone' as PermissionName });
if (permission.state === 'denied') {
  // Instruir usuário a permitir nas configurações
}

Conexão WebSocket falha

  • Verifique se a URL usa wss:// (não ws://)
  • Confirme que o token JWT é válido
  • Verifique conectividade de rede

Geração de documento retorna erro

  • Confirme que apiUrl está configurado
  • Verifique se a sessão ainda existe no S3 (TTL de 30 dias)
  • Confira formato do content no template (JSON Schema válido)

Licença

Proprietário - MEDC Sistemas de Saúde