Skip to content

Add live dashboard to AI Language Learning Phone Tutor#43

Open
harpreetTelnyx wants to merge 2 commits into
mainfrom
harpreet/language-tutor-dashboard
Open

Add live dashboard to AI Language Learning Phone Tutor#43
harpreetTelnyx wants to merge 2 commits into
mainfrom
harpreet/language-tutor-dashboard

Conversation

@harpreetTelnyx

Copy link
Copy Markdown
Collaborator

What this adds

A live dashboard at / for the AI Language Learning Phone Tutor, designed for YouTube demo recording. Dark theme, SSE streaming, two-panel layout — same pattern as the Conversation Relay bridge dashboard.

What it shows

Left panel: Live events
Every webhook event as it happens — incoming call, answering, greeting, language selection, caller speech, AI tutor reply, hangup. Each with icon, label, timestamp, and content.

Right panel: Conversation transcript
Chat-bubble view — caller said (left) vs tutor replied (right). Shows turn count, call duration, and language badge (flag + name) when a language is selected.

Header: Architecture flow
Caller dials → Telnyx Voice AI → This app (webhook) → AI Inference → reply → Telnyx TTS → Caller hears it

How it works

  • SSE (/events) pushes events to the browser in real time — no polling
  • Event bus emits at every webhook stage in handle_voice()
  • /api/state returns current call state + conversation history for initial load
  • Dashboard updates conversation panel on prompt, reply, and lang events

Endpoints

Route Purpose
GET / Live dashboard (embedded HTML)
GET /events SSE stream of events
GET /api/state Current call state + event history (JSON)
GET /health Health check (unchanged)
GET /sessions Session history (unchanged)
POST /webhooks/voice Telnyx webhook handler (unchanged logic, added event emission)

Related

Ultraworked with Sisyphus

Embedded HTML dashboard at / with dark theme, SSE streaming, two-panel layout: live events (left) and conversation transcript (right). Architecture flow diagram in header. Shows caller number, language selection with flag badge, caller speech, AI tutor replies, and call duration in real time. Events emitted at every webhook stage: incoming call, answering, greeting, language selection, caller speech, AI reply, hangup. Ready for YouTube demo recording.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Call Control webhooks were not receiving inbound calls from Telnyx. Switched to TeXML + Conversation Relay (same pattern as the Conversation Relay voice bot example). Telnyx now fetches TeXML from /texml/inbound, opens a WebSocket to /ws/conversation-relay, and the app exchanges text frames. Caller says a language name (Spanish, French, Japanese, Mandarin) to start tutoring. AI Inference (Llama-3.3-70B) runs the conversation with streaming replies. Dashboard, SSE events, and all API endpoints preserved.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
if base.startswith("https://"):
return "wss://" + base.removeprefix("https://") + "/ws/conversation-relay"
if base.startswith("http://"):
return "ws://" + base.removeprefix("http://") + "/ws/conversation-relay"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:
User data flows into the host portion of this manually-constructed URL. This could allow an attacker to send data to their own server, potentially exposing sensitive data such as cookies or authorization information sent with this request. They could also probe internal servers or other resources that the server running this code can access. (This is called server-side request forgery, or SSRF.) Do not allow arbitrary hosts. Instead, create an allowlist for approved hosts, or hardcode the correct host.

Dataflow graph
flowchart LR
    classDef invis fill:white, stroke: none
    classDef default fill:#e7f5ff, color:#1c7fd6, stroke: none

    subgraph File0["<b>ai-language-learning-phone-tutor-python/app.py</b>"]
        direction LR
        %% Source

        subgraph Source
            direction LR

            v0["<a href=https://github.com/team-telnyx/telnyx-code-examples/blob/6cae855175443010e6db8d6146583530737020b2/ai-language-learning-phone-tutor-python/app.py#L152 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 152] request.url_root</a>"]
        end
        %% Intermediate

        subgraph Traces0[Traces]
            direction TB

            v2["<a href=https://github.com/team-telnyx/telnyx-code-examples/blob/6cae855175443010e6db8d6146583530737020b2/ai-language-learning-phone-tutor-python/app.py#L156 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 156] public_base_url</a>"]

            v3["<a href=https://github.com/team-telnyx/telnyx-code-examples/blob/6cae855175443010e6db8d6146583530737020b2/ai-language-learning-phone-tutor-python/app.py#L156 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 156] base</a>"]
        end
            v2 --> v3
        %% Sink

        subgraph Sink
            direction LR

            v1["<a href=https://github.com/team-telnyx/telnyx-code-examples/blob/6cae855175443010e6db8d6146583530737020b2/ai-language-learning-phone-tutor-python/app.py#L160 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 160] &quot;ws://&quot; + base.removeprefix(&quot;http://&quot;) + &quot;/ws/conversation-relay&quot;</a>"]
        end
    end
    %% Class Assignment
    Source:::invis
    Sink:::invis

    Traces0:::invis
    File0:::invis

    %% Connections

    Source --> Traces0
    Traces0 --> Sink


Loading

To resolve this comment:

🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by tainted-url-host.

You can view more details about this finding in the Semgrep AppSec Platform.

def conversation_relay_ws_url() -> str:
base = public_base_url()
if base.startswith("https://"):
return "wss://" + base.removeprefix("https://") + "/ws/conversation-relay"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:
User data flows into the host portion of this manually-constructed URL. This could allow an attacker to send data to their own server, potentially exposing sensitive data such as cookies or authorization information sent with this request. They could also probe internal servers or other resources that the server running this code can access. (This is called server-side request forgery, or SSRF.) Do not allow arbitrary hosts. Instead, create an allowlist for approved hosts, or hardcode the correct host.

Dataflow graph
flowchart LR
    classDef invis fill:white, stroke: none
    classDef default fill:#e7f5ff, color:#1c7fd6, stroke: none

    subgraph File0["<b>ai-language-learning-phone-tutor-python/app.py</b>"]
        direction LR
        %% Source

        subgraph Source
            direction LR

            v0["<a href=https://github.com/team-telnyx/telnyx-code-examples/blob/6cae855175443010e6db8d6146583530737020b2/ai-language-learning-phone-tutor-python/app.py#L152 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 152] request.url_root</a>"]
        end
        %% Intermediate

        subgraph Traces0[Traces]
            direction TB

            v2["<a href=https://github.com/team-telnyx/telnyx-code-examples/blob/6cae855175443010e6db8d6146583530737020b2/ai-language-learning-phone-tutor-python/app.py#L156 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 156] public_base_url</a>"]

            v3["<a href=https://github.com/team-telnyx/telnyx-code-examples/blob/6cae855175443010e6db8d6146583530737020b2/ai-language-learning-phone-tutor-python/app.py#L156 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 156] base</a>"]
        end
            v2 --> v3
        %% Sink

        subgraph Sink
            direction LR

            v1["<a href=https://github.com/team-telnyx/telnyx-code-examples/blob/6cae855175443010e6db8d6146583530737020b2/ai-language-learning-phone-tutor-python/app.py#L158 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 158] &quot;wss://&quot; + base.removeprefix(&quot;https://&quot;) + &quot;/ws/conversation-relay&quot;</a>"]
        end
    end
    %% Class Assignment
    Source:::invis
    Sink:::invis

    Traces0:::invis
    File0:::invis

    %% Connections

    Source --> Traces0
    Traces0 --> Sink


Loading

To resolve this comment:

🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by tainted-url-host.

You can view more details about this finding in the Semgrep AppSec Platform.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant