Skip to content

Website JavaScript Modules from PR #169 (Part 2/2)#190

Closed
uelkerd wants to merge 2 commits into
mainfrom
feat/website-js-modules
Closed

Website JavaScript Modules from PR #169 (Part 2/2)#190
uelkerd wants to merge 2 commits into
mainfrom
feat/website-js-modules

Conversation

@uelkerd
Copy link
Copy Markdown
Owner

@uelkerd uelkerd commented Sep 27, 2025

Summary

• Fortress-compliant implementation of interactive JavaScript modules from PR #169
• Part 2/2 of sophisticated website files from feat/clean-demo-website
• Enables full interactive emotion detection demo with 28 emotions

Changes (5/5 files - Fortress Compliant ✅)

comprehensive-demo.js (58KB): Core demo functionality with DeBERTa v3 Large integration
config.js: Configuration management and API endpoints
demo-initialization.js: Demo setup and initialization logic
layout-manager.js: UI layout and responsive design management
voice-recorder.js: Voice recording and transcription capabilities

Technical Details

Interactive Features: Voice recording, real-time emotion analysis, result visualization
AI Integration: DeBERTa v3 Large model for 28-emotion classification
Modern JavaScript: ES6+ modules with clean separation of concerns
Responsive Design: Mobile-friendly layout management
Error Handling: Comprehensive error handling and user feedback

Original Work Attribution

All files sourced from PR #169 feat/clean-demo-website branch.
This PR represents the sophisticated interactive functionality that complements the core website files from PR #189.

Testing

• ✅ All files successfully copied from origin/feat/clean-demo-website
• ✅ Fortress compliance verified (5/5 files)
• ✅ Pre-commit hooks passed
• ✅ Local server integration ready

🤖 Generated with Claude Code

Summary by Sourcery

Implement interactive JavaScript modules for the demo website, enabling voice recording, emotion detection, and summarization with centralized configuration and robust UI state management

New Features:

  • Introduce SAMOAPIClient for unified emotion, summarization, and transcription API interactions with retry logic and mock fallbacks
  • Add VoiceRecorder class to enable microphone access, audio recording, and seamless transcription integration
  • Implement LayoutManager module for UI state transitions, progress tracking, and concurrent request management
  • Create centralized config.js for API endpoints, environment flags, and secure key redaction
  • Add demo-initialization.js to bind UI controls, initialize modules, and set up bootstrap tooltips at startup

Enhancements:

  • Refactor website scripts into ES6 modules with clear separation of concerns
  • Improve user feedback via inline messages, toast notifications, and a progress console
  • Enhance error handling and timeouts across API calls and recording workflows
  • Implement responsive layout transitions, loading states, and guard against concurrent operations

Summary by CodeRabbit

  • New Features
    • Runtime-configurable API base URL with automatic fallback.
    • In-browser voice recording and transcription flow.
    • End-to-end AI demo: transcribe audio, summarize text, and detect emotions with dynamic UI updates and charts.
    • Centralized UI state management for processing/results with progress indicators and emergency resets.
    • Sample text generation, API key management, smooth in-page navigation, and optional debug panel.
    • Initialization enhancements: button handlers, safety resets, and tooltip support.
  • Chores
    • Centralized client-side configuration with environment-aware overrides and secure, redacted debug logging.

Fortress-compliant implementation (5/5 files):
- comprehensive-demo.js: Core demo functionality with DeBERTa v3 Large
- config.js: Configuration management
- demo-initialization.js: Demo setup and initialization
- layout-manager.js: UI layout and responsive design
- voice-recorder.js: Voice recording and transcription

Part 2/2 of sophisticated website files from feat/clean-demo-website.
Enables full interactive emotion detection demo with 28 emotions.

Original work attribution: PR #169 feat/clean-demo-website

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 27, 2025 08:44
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 27, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This change set introduces a centralized, client-side configuration module and a modular structure to the web demo client. It implements configuration-driven API endpoints, robust state and layout management, a comprehensive AI workflow client, a voice recording and transcription module, and enhanced UI orchestration for interactions. Multiple new JavaScript modules and HTML integrations are included.

Changes

Cohort / File(s) Change Summary
HTML Integration & Config Sourcing
website/comprehensive-demo.html, website/index.html
Replaced hardcoded API endpoint strings with configuration-driven base URLs; added <script> tags to include the new config.js file and initialized client scripts to read from global config, falling back to static values if not set.
Comprehensive AI Demo Client
website/js/comprehensive-demo.js
Introduced a feature-rich, resilient demo client managing API endpoints, timeouts, retries, state, error handling, and end-to-end workflows for transcription, summarization, and emotion detection. Provides robust UI helpers and sample data generators.
Centralized Configuration Module
website/js/config.js
Added a singleton-style, deeply-mergeable global config object (SAMO_CONFIG), supporting environment-based overrides, sensitive key redaction, external config injection, and secure console logging for debug.
Demo Initialization Orchestration
website/js/demo-initialization.js
Added DOMContentLoaded initializer attaching button handlers, managing fallback chains, enforcing layout and state resets, enabling tooltips, and toggling debug sections based on query params.
UI Layout & State Management
website/js/layout-manager.js
Added a LayoutManager singleton for handling UI transitions, processing guards, request tracking, result/processing state switching, emergency resets, progress indicators, and coordinated function exposure.
Voice Recorder & Transcription
website/js/voice-recorder.js
Added a global VoiceRecorder module to handle microphone access, recording controls, MediaRecorder integration, robust error/UI handling, and interaction with API or fallback transcription and result display.

Sequence Diagram(s)

sequenceDiagram
    %% Modular Demo Client Core Workflow
    participant User as User
    participant UI as UI/DOM
    participant Config as config.js (SAMO_CONFIG)
    participant Client as SAMOAPIClient
    participant Recorder as VoiceRecorder
    participant Layout as LayoutManager
    participant API as API Server

    %% Initialization/Config
    Note left of UI: (DOMContentLoaded)
    UI->>Config: Load config.js, set SAMO_CONFIG
    Config-->>UI: Expose config, possibly merge with server config

    %% Processing Workflow
    User->>UI: Click "Process"
    UI->>Layout: showProcessingState()\n(track request, lock UI)
    UI->>Client: processCompleteWorkflow(input)
    Client->>API: /transcribe (if needed)
    API-->>Client: return transcription result
    Client->>API: /summarize
    API-->>Client: summary result
    Client->>API: /emotion
    API-->>Client: emotion results
    Client-->>UI: Results payload
    UI->>Layout: showResultsState()
    UI->>UI: Render summary, emotions, etc.

    %% Voice Recorder
    User->>Recorder: Click "Record"
    Recorder->>UI: Start Timer, update UI
    Recorder->>Recorder: MediaRecorder.start
    User->>Recorder: Click "Stop"
    Recorder->>Recorder: MediaRecorder.stop, create Blob
    Recorder->>Client: transcribeAudio(blob)
    Client->>API: /transcribe (audio)
    API-->>Client: transcription
    Client-->>Recorder: Return text result
    Recorder->>UI: Display transcription

    %% Error Handling and Fallback
    alt API errors or rate limiting
        Client-->>UI: Show mock/fallback data, showError
        Layout->>Layout: forceResetProcessing/emergencyReset
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

In the meadow of code, a rabbit does cheer,
With configs and modules, all changes are clear!
Now voices get captured, then lively transcribed,
The AI hops swiftly with feedback supplied.
State managed, charts painted, results on display—
Hip-hop-hooray for a smarter web demo today!
🐰✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.71% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title “Website JavaScript Modules from PR #169 (Part 2/2)” is generic and meta, referring to a previous pull request rather than summarizing the concrete changes in this branch, and it does not clearly convey the specific modules or functionality being introduced. Consider renaming the PR to directly reflect its primary additions—for example, “Add configurable client-side JS modules for demo, configuration, layout management, and voice recording”—so that the title succinctly communicates the main changes without referencing internal PR numbers.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/website-js-modules

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Sep 27, 2025

Reviewer's Guide

This PR modularizes the interactive emotion detection demo into ES6-compliant JavaScript modules, introducing a robust API client, voice recording support, enhanced UI state management, centralized configuration, and a lightweight initialization script to orchestrate demo flows.

Sequence diagram for user voice recording and emotion analysis workflow

sequenceDiagram
    actor User
    participant "VoiceRecorder"
    participant "SAMOAPIClient"
    participant "LayoutManager"
    participant "UI Elements"
    User->>"VoiceRecorder": Clicks recordBtn
    "VoiceRecorder"->>"VoiceRecorder": startRecording()
    User->>"VoiceRecorder": Clicks stopBtn
    "VoiceRecorder"->>"VoiceRecorder": stopRecording()
    "VoiceRecorder"->>"VoiceRecorder": onRecordingStop()
    "VoiceRecorder"->>"SAMOAPIClient": transcribeAudio(audioFile)
    "SAMOAPIClient"->>"SAMOAPIClient": makeRequest(VOICE_JOURNAL)
    "SAMOAPIClient"->>"SAMOAPIClient": process response
    "SAMOAPIClient"-->>"VoiceRecorder": transcription result
    "VoiceRecorder"->>"UI Elements": displayTranscriptionResults(result)
    "VoiceRecorder"->>"LayoutManager": showProcessingState()
    "VoiceRecorder"->>"SAMOAPIClient": (optional) processCompleteWorkflow(audioFile, text)
    "SAMOAPIClient"->>"SAMOAPIClient": summarizeText(), detectEmotions()
    "SAMOAPIClient"-->>"VoiceRecorder": analysis results
    "VoiceRecorder"->>"UI Elements": update UI with results
    "VoiceRecorder"->>"LayoutManager": showResultsState()
Loading

File-Level Changes

Change Details Files
Robust API client implementation with retry logic and mock fallbacks
  • Structured use of window.SAMO_CONFIG for endpoints and timeouts
  • Cold-start tracking with dynamic timeout adjustments
  • Exponential backoff retry mechanism for rate limits and network errors
  • JSON/FormData request handling with X-API-Key injection
  • Mock data fallback for summarization and emotion analysis
  • Integration of a real-time progress console for user feedback
website/js/comprehensive-demo.js
Voice recording integration and UI controls
  • Added VoiceRecorder class encapsulating MediaRecorder setup and audio blob creation
  • Recording timer, indicator, and start/stop button management
  • Error handling with contextual toast notifications
  • Integration with SAMOAPIClient.transcribeAudio for transcription flows
  • Hooks into LayoutManager for processing state transitions
website/js/voice-recorder.js
Layout state management enhancements
  • Introduced LayoutManager object to guard against concurrent operations
  • Active request tracking and cancelation via AbortController
  • UI transitions between input, loading, and results states
  • Emergency reset and stuck-processing safeguards
  • Progress step indicators and debug section toggling
website/js/layout-manager.js
Centralized configuration with environment overrides
  • Defined window.SAMO_CONFIG for API, OpenAI, external services, and feature flags
  • Localhost detection for BASE_URL and endpoint overrides in development
  • Deep merge utility for server-side config injection
  • Recursive redaction of sensitive keys in debug logs
  • Conditional debug console logging
website/js/config.js
Demo initialization and event binding script
  • DOM ready handler logging module availability
  • Bound click listeners for process, generate, API key management, and clear actions
  • URL-driven debug section toggle and safety state resets
  • Bootstrap tooltip initialization
  • Fallback logic for original vs. enhanced processing/clearing functions
website/js/demo-initialization.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@deepsource-io
Copy link
Copy Markdown
Contributor

deepsource-io Bot commented Sep 27, 2025

Here's the code health analysis summary for commits 93d71d2..d1e4c9b. View details on DeepSource ↗.

Analysis Summary

AnalyzerStatusSummaryLink
DeepSource Test coverage LogoTest coverage⚠️ Artifact not reportedTimed out: Artifact was never reportedView Check ↗
DeepSource Python LogoPython✅ SuccessView Check ↗
DeepSource Terraform LogoTerraform✅ SuccessView Check ↗
DeepSource Secrets LogoSecrets✅ SuccessView Check ↗
DeepSource Shell LogoShell✅ SuccessView Check ↗
DeepSource Docker LogoDocker✅ SuccessView Check ↗

💡 If you’re a repository administrator, you can configure the quality gates from the settings.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @uelkerd, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers the interactive components for the website's AI platform demo, completing the work initiated in PR #169. It establishes a sophisticated client-side application capable of voice transcription, text summarization, and detailed emotion detection using the DeBERTa v3 Large model. The implementation emphasizes modularity, robust API interaction, dynamic UI management, and user-friendly features like AI-generated sample text and real-time processing feedback.

Highlights

  • Interactive AI Demo: Introduces a comprehensive AI platform demo with voice recording, real-time emotion analysis (28 emotions), and text summarization capabilities.
  • Modular JavaScript Architecture: Implements the demo using ES6+ modules with clear separation of concerns across comprehensive-demo.js, config.js, demo-initialization.js, layout-manager.js, and voice-recorder.js.
  • Advanced API Client: Features a robust SAMOAPIClient with configurable base URLs, endpoints, timeouts, retry mechanisms, and API key management for interacting with AI services.
  • Dynamic UI State Management: Incorporates a LayoutManager for smooth transitions between UI states (initial, processing, results), preventing concurrent operations, and providing enhanced loading feedback.
  • OpenAI Integration: Includes functionality for generating AI-powered sample journal text using the OpenAI API, with fallback to curated samples if the API is unavailable.
  • Voice Recording & Transcription: Adds VoiceRecorder capabilities for microphone access, audio recording, and transcription, integrating directly into the AI analysis workflow.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements sophisticated interactive JavaScript modules for the SAMO AI platform demo, supporting voice recording, transcription, and emotion detection with 28 emotions. It serves as Part 2/2 of the comprehensive website implementation, introducing advanced client-side functionality to complement the static website files.

Key Changes

  • Interactive voice recording and transcription capabilities with comprehensive error handling
  • Responsive layout management system with state transitions and processing guards
  • Comprehensive AI processing pipeline integrating DeBERTa v3 Large for emotion analysis

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
website/js/voice-recorder.js Voice recording module with microphone access, transcription, and API integration
website/js/layout-manager.js UI state management for processing transitions and responsive design
website/js/demo-initialization.js DOM initialization with event listeners and Bootstrap tooltip setup
website/js/config.js Centralized configuration management for API endpoints and environment settings
website/js/comprehensive-demo.js Main demo functionality with API client, emotion detection, and visualization

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +148 to +154
async processRecordedAudio(audioBlob) {
try {
// Show processing state
this.showProcessingState();

// Create a File object from the blob
const audioFile = new File([audioBlob], 'recording.webm', {
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

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

The filename extension 'recording.webm' is hardcoded but may not match the actual MIME type returned by getSupportedMimeType(). This could cause issues with audio processing on the server side. Consider using a dynamic extension based on the blob's MIME type.

Suggested change
async processRecordedAudio(audioBlob) {
try {
// Show processing state
this.showProcessingState();
// Create a File object from the blob
const audioFile = new File([audioBlob], 'recording.webm', {
// Helper to map MIME type to file extension
getExtensionFromMimeType(mimeType) {
const mimeToExt = {
'audio/webm': 'webm',
'audio/ogg': 'ogg',
'audio/wav': 'wav',
'audio/mp3': 'mp3',
'audio/mpeg': 'mp3',
'audio/x-wav': 'wav',
'audio/mp4': 'mp4',
};
return mimeToExt[mimeType] || 'bin';
}
async processRecordedAudio(audioBlob) {
try {
// Show processing state
this.showProcessingState();
// Create a File object from the blob with dynamic extension
const extension = this.getExtensionFromMimeType(audioBlob.type);
const filename = `recording.${extension}`;
const audioFile = new File([audioBlob], filename, {

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +54
if (this.isProcessing) {
// Check if processing has been stuck for too long
const timeElapsed = Date.now() - this.processingStartTime;
if (timeElapsed > this.maxProcessingTime) {
console.warn(`⚠️ Processing stuck for ${timeElapsed/1000}s, forcing reset...`);
this.forceResetProcessing();
} else {
console.warn('⚠️ Processing already in progress, ignoring request');
console.warn('⚠️ Current state:', this.currentState);
console.warn('⚠️ Active requests:', this.activeRequests.size);
console.warn(`⚠️ Time elapsed: ${timeElapsed/1000}s`);
return false;
}
}
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

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

Potential null reference error when processingStartTime is null. If isProcessing is true but processingStartTime hasn't been set, Date.now() - null will result in NaN, causing the comparison timeElapsed > this.maxProcessingTime to always be false.

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +31
this.timeout = window.SAMO_CONFIG?.API?.TIMEOUT || 15000; // Reduced from 20s to 15s
this.coldStartTimeout = window.SAMO_CONFIG?.API?.COLD_START_TIMEOUT || 45000; // Reduced from 60s to 45s
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

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

The comments mention reductions from previous values (20s to 15s, 60s to 45s), but these previous values are not the current fallback values in the code. The comments should either be updated to reflect the actual current fallback values or removed to avoid confusion.

Suggested change
this.timeout = window.SAMO_CONFIG?.API?.TIMEOUT || 15000; // Reduced from 20s to 15s
this.coldStartTimeout = window.SAMO_CONFIG?.API?.COLD_START_TIMEOUT || 45000; // Reduced from 60s to 45s
this.timeout = window.SAMO_CONFIG?.API?.TIMEOUT || 15000; // Fallback: 15s
this.coldStartTimeout = window.SAMO_CONFIG?.API?.COLD_START_TIMEOUT || 45000; // Fallback: 45s

Copilot uses AI. Check for mistakes.
Comment on lines +767 to +768
// Create API client instance for proper timeout and error handling
const apiClient = new SAMOAPIClient();
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

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

A new SAMOAPIClient instance is created on line 768 when a global apiClient instance already exists. This creates unnecessary overhead and doesn't leverage the existing cold start tracking. Consider using the global window.apiClient instance instead.

Suggested change
// Create API client instance for proper timeout and error handling
const apiClient = new SAMOAPIClient();
// Use global API client instance for proper timeout and error handling
const apiClient = window.apiClient;

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • Consider breaking comprehensive-demo.js into focused modules (e.g., API client, mock data, workflow) to improve maintainability and testability.
  • Many error handling and retry patterns in SAMOAPIClient are duplicated; extracting common utilities or middleware could reduce redundancy.
  • The code currently attaches numerous globals to window, which may lead to namespace collisions—consider leveraging ES6 imports/exports or a dedicated namespace object.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider breaking comprehensive-demo.js into focused modules (e.g., API client, mock data, workflow) to improve maintainability and testability.
- Many error handling and retry patterns in SAMOAPIClient are duplicated; extracting common utilities or middleware could reduce redundancy.
- The code currently attaches numerous globals to window, which may lead to namespace collisions—consider leveraging ES6 imports/exports or a dedicated namespace object.

## Individual Comments

### Comment 1
<location> `website/js/voice-recorder.js:175-176` </location>
<code_context>
+                }
+            }
+
+            // Use API client to transcribe
+            if (apiClient && typeof apiClient.transcribeAudio === 'function') {
+                console.log('🔄 Sending audio for transcription...');
+                const response = await apiClient.transcribeAudio(audioFile);
</code_context>

<issue_to_address>
**issue (bug_risk):** API client response handling may be inconsistent with SAMOAPIClient's return type.

The code assumes apiClient.transcribeAudio returns a Response object with an 'ok' property, but it actually returns parsed JSON or throws on error. Update the response handling to match the actual return type to avoid runtime issues.
</issue_to_address>

### Comment 2
<location> `website/js/voice-recorder.js:247-249` </location>
<code_context>
+            }
+
+            // If we have complete analysis results, display them
+            if (result.emotion_analysis || result.summary) {
+                // Trigger the processing to show results
+                if (typeof processTextWithStateManagement === 'function') {
+                    processTextWithStateManagement();
+                } else if (typeof processText === 'function') {
</code_context>

<issue_to_address>
**suggestion (performance):** Triggering processText after transcription may result in duplicate or unnecessary processing.

If emotion_analysis and summary are already provided, avoid re-calling processText to prevent redundant API usage and UI updates. Refactor to use the existing results.

Suggested implementation:

```javascript
            // If we have complete analysis results, display them
            if (result.emotion_analysis || result.summary) {
                // Use existing results to update the UI, avoid redundant processing
                if (typeof displayAnalysisResults === 'function') {
                    displayAnalysisResults(result.emotion_analysis, result.summary);
                } else {
                    // Fallback: directly update UI elements if displayAnalysisResults is not defined
                    if (result.emotion_analysis && document.getElementById('emotionAnalysis')) {
                        document.getElementById('emotionAnalysis').textContent = JSON.stringify(result.emotion_analysis);
                    }
                    if (result.summary && document.getElementById('summary')) {
                        document.getElementById('summary').textContent = result.summary;
                    }
                }
            }

```

- Ensure that a function named `displayAnalysisResults(emotionAnalysis, summary)` exists elsewhere in your codebase to handle UI updates, or adjust the fallback logic to match your UI structure.
- If you have custom UI update logic for emotion analysis and summary, replace the fallback code with your own implementation.
- Remove or refactor any code that depends on `processTextWithStateManagement` or `processText` being called after transcription if it is no longer needed.
</issue_to_address>

### Comment 3
<location> `website/js/comprehensive-demo.js:73-82` </location>
<code_context>
+        return params.toString();
+    }
+
+    async makeRequestWithRetry(endpoint, data, method = 'POST', isFormData = false, timeoutMs = null, attemptsLeft = null) {
+        // Use class defaults if not specified
+        if (attemptsLeft === null) attemptsLeft = this.retryAttempts;
+
+        const config = {
+            method,
+            headers: {}
+        };
+        const controller = new AbortController();
+
+        // Use cold start timeout for first request, regular timeout otherwise
+        const timeout = timeoutMs || (this.isColdStart ? this.coldStartTimeout : this.timeout);
+        const timer = setTimeout(() => {
+            controller.abort(new Error(`Request timeout after ${timeout/1000}s`));
+        }, timeout);
</code_context>

<issue_to_address>
**suggestion:** AbortController usage may not work as intended with fetch and custom error.

Note that fetch will always throw an AbortError when aborted, regardless of the error passed to controller.abort(). To differentiate abort reasons, track them separately.

Suggested implementation:

```javascript
        const controller = new AbortController();
        let abortReason = null; // Track abort reason

        // Use cold start timeout for first request, regular timeout otherwise
        const timeout = timeoutMs || (this.isColdStart ? this.coldStartTimeout : this.timeout);
        const timer = setTimeout(() => {
            abortReason = 'timeout';
            controller.abort();
        }, timeout);
        config.signal = controller.signal;

```

To fully implement this, you must also:
1. Update the code that catches errors from `fetch` to check the `abortReason` variable. If `abortReason === 'timeout'`, you can handle the timeout case specifically (e.g., by logging or throwing a custom error).
2. Remove any reliance on the error passed to `controller.abort()` elsewhere in the code.
</issue_to_address>

### Comment 4
<location> `website/js/comprehensive-demo.js:101-102` </location>
<code_context>
+            config.headers['X-API-Key'] = apiKey;
+        }
+
+        if (data && method === 'POST') {
+            if (isFormData) {
+                // For FormData, don't set Content-Type header - let browser set it with boundary
+                config.body = data;
</code_context>

<issue_to_address>
**suggestion:** No check for GET requests with data payload.

For GET requests with data, build a query string from the data and append it to the URL.
</issue_to_address>

### Comment 5
<location> `website/js/layout-manager.js:284-285` </location>
<code_context>
+        });
+    },
+
+    // Update progress steps
+    updateProgressStep(stepNumber, state) {
+        // Update both horizontal and vertical progress indicators
+        const stepElement = document.getElementById(`step${stepNumber}`);
</code_context>

<issue_to_address>
**suggestion:** updateProgressStep assumes existence of step elements and icons.

Add a warning or error log if step elements or icons are missing to improve debuggability.
</issue_to_address>

### Comment 6
<location> `website/js/demo-initialization.js:117-124` </location>
<code_context>
+        window.LayoutManager.resetToInitialState();
+    }
+
+    // Initialize Bootstrap tooltips
+    const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
+    const tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
+        return new bootstrap.Tooltip(tooltipTriggerEl);
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Bootstrap tooltips initialization may fail if Bootstrap is not loaded.

Check for window.bootstrap before initializing tooltips to prevent ReferenceError if Bootstrap is not loaded.

```suggestion
    // Initialize Bootstrap tooltips if Bootstrap is loaded
    let tooltipList = [];
    if (window.bootstrap) {
        const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
        tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
            return new bootstrap.Tooltip(tooltipTriggerEl);
        });
        console.log('✅ Bootstrap tooltips initialized:', tooltipList.length);
    } else {
        console.warn('⚠️ Bootstrap is not loaded. Tooltips not initialized.');
    }

    console.log('✅ Enhanced layout manager initialized');
```
</issue_to_address>

### Comment 7
<location> `website/js/comprehensive-demo.js:134-147` </location>
<code_context>
                if (response.status === 429 || response.status >= 500) {
                    if (attemptsLeft > 1) {
                        const backoffDelay = Math.pow(2, this.retryAttempts - attemptsLeft) * 1000; // Exponential backoff
                        console.warn(`Request failed (${response.status}), retrying in ${backoffDelay}ms. Attempts left: ${attemptsLeft - 1}`);

                        // Provide user feedback about retry
                        if (typeof addToProgressConsole === 'function') {
                            addToProgressConsole(`Request failed (${response.status}), retrying in ${backoffDelay/1000}s...`, 'warning');
                        }

                        await new Promise(resolve => setTimeout(resolve, backoffDelay));
                        return this.makeRequestWithRetry(endpoint, data, method, isFormData, timeoutMs, attemptsLeft - 1);
                    }
                }

</code_context>

<issue_to_address>
**suggestion (code-quality):** Merge nested if conditions ([`merge-nested-ifs`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/JavaScript/Default-Rules/merge-nested-ifs))

```suggestion
                if ((response.status === 429 || response.status >= 500) && attemptsLeft > 1) {
                      const backoffDelay = Math.pow(2, this.retryAttempts - attemptsLeft) * 1000; // Exponential backoff
                      console.warn(`Request failed (${response.status}), retrying in ${backoffDelay}ms. Attempts left: ${attemptsLeft - 1}`);

                      // Provide user feedback about retry
                      if (typeof addToProgressConsole === 'function') {
                          addToProgressConsole(`Request failed (${response.status}), retrying in ${backoffDelay/1000}s...`, 'warning');
                      }

                      await new Promise(resolve => setTimeout(resolve, backoffDelay));
                      return this.makeRequestWithRetry(endpoint, data, method, isFormData, timeoutMs, attemptsLeft - 1);
                }

```

<br/><details><summary>Explanation</summary>Reading deeply nested conditional code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two `if` conditions can be combined using
`and` is an easy win.
</details>
</issue_to_address>

### Comment 8
<location> `website/js/comprehensive-demo.js:206-209` </location>
<code_context>
            const response = await this.makeRequest(this.endpoints.SUMMARIZE, { text }, 'POST');

            // The makeRequest method already handles JSON parsing, so response is the data
            return response;

</code_context>

<issue_to_address>
**suggestion (code-quality):** Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/JavaScript/Default-Rules/inline-immediately-returned-variable))

```suggestion
            return await this.makeRequest(this.endpoints.SUMMARIZE, { text }, 'POST');

```

<br/><details><summary>Explanation</summary>Something that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.
</details>
</issue_to_address>

### Comment 9
<location> `website/js/voice-recorder.js:159` </location>
<code_context>
            let apiClient = window.apiClient;

</code_context>

<issue_to_address>
**suggestion (code-quality):** Prefer object destructuring when accessing and using properties. ([`use-object-destructuring`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/JavaScript/Default-Rules/use-object-destructuring))

```suggestion
            let {apiClient} = window;
```

<br/><details><summary>Explanation</summary>Object destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the [Airbnb Javascript Style Guide](https://airbnb.io/javascript/#destructuring--object)
</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +175 to +176
// Use API client to transcribe
if (apiClient && typeof apiClient.transcribeAudio === 'function') {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): API client response handling may be inconsistent with SAMOAPIClient's return type.

The code assumes apiClient.transcribeAudio returns a Response object with an 'ok' property, but it actually returns parsed JSON or throws on error. Update the response handling to match the actual return type to avoid runtime issues.

Comment on lines +247 to +249
if (result.emotion_analysis || result.summary) {
// Trigger the processing to show results
if (typeof processTextWithStateManagement === 'function') {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (performance): Triggering processText after transcription may result in duplicate or unnecessary processing.

If emotion_analysis and summary are already provided, avoid re-calling processText to prevent redundant API usage and UI updates. Refactor to use the existing results.

Suggested implementation:

            // If we have complete analysis results, display them
            if (result.emotion_analysis || result.summary) {
                // Use existing results to update the UI, avoid redundant processing
                if (typeof displayAnalysisResults === 'function') {
                    displayAnalysisResults(result.emotion_analysis, result.summary);
                } else {
                    // Fallback: directly update UI elements if displayAnalysisResults is not defined
                    if (result.emotion_analysis && document.getElementById('emotionAnalysis')) {
                        document.getElementById('emotionAnalysis').textContent = JSON.stringify(result.emotion_analysis);
                    }
                    if (result.summary && document.getElementById('summary')) {
                        document.getElementById('summary').textContent = result.summary;
                    }
                }
            }
  • Ensure that a function named displayAnalysisResults(emotionAnalysis, summary) exists elsewhere in your codebase to handle UI updates, or adjust the fallback logic to match your UI structure.
  • If you have custom UI update logic for emotion analysis and summary, replace the fallback code with your own implementation.
  • Remove or refactor any code that depends on processTextWithStateManagement or processText being called after transcription if it is no longer needed.

Comment on lines +73 to +82
async makeRequestWithRetry(endpoint, data, method = 'POST', isFormData = false, timeoutMs = null, attemptsLeft = null) {
// Use class defaults if not specified
if (attemptsLeft === null) attemptsLeft = this.retryAttempts;

const config = {
method,
headers: {}
};
const controller = new AbortController();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: AbortController usage may not work as intended with fetch and custom error.

Note that fetch will always throw an AbortError when aborted, regardless of the error passed to controller.abort(). To differentiate abort reasons, track them separately.

Suggested implementation:

        const controller = new AbortController();
        let abortReason = null; // Track abort reason

        // Use cold start timeout for first request, regular timeout otherwise
        const timeout = timeoutMs || (this.isColdStart ? this.coldStartTimeout : this.timeout);
        const timer = setTimeout(() => {
            abortReason = 'timeout';
            controller.abort();
        }, timeout);
        config.signal = controller.signal;

To fully implement this, you must also:

  1. Update the code that catches errors from fetch to check the abortReason variable. If abortReason === 'timeout', you can handle the timeout case specifically (e.g., by logging or throwing a custom error).
  2. Remove any reliance on the error passed to controller.abort() elsewhere in the code.

Comment on lines +101 to +102
if (data && method === 'POST') {
if (isFormData) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: No check for GET requests with data payload.

For GET requests with data, build a query string from the data and append it to the URL.

Comment on lines +284 to +285
// Update progress steps
updateProgressStep(stepNumber, state) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: updateProgressStep assumes existence of step elements and icons.

Add a warning or error log if step elements or icons are missing to improve debuggability.

Comment on lines +117 to +124
// Initialize Bootstrap tooltips
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
const tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});

console.log('✅ Enhanced layout manager initialized');
console.log('✅ Bootstrap tooltips initialized:', tooltipList.length);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): Bootstrap tooltips initialization may fail if Bootstrap is not loaded.

Check for window.bootstrap before initializing tooltips to prevent ReferenceError if Bootstrap is not loaded.

Suggested change
// Initialize Bootstrap tooltips
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
const tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
console.log('✅ Enhanced layout manager initialized');
console.log('✅ Bootstrap tooltips initialized:', tooltipList.length);
// Initialize Bootstrap tooltips if Bootstrap is loaded
let tooltipList = [];
if (window.bootstrap) {
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
console.log('✅ Bootstrap tooltips initialized:', tooltipList.length);
} else {
console.warn('⚠️ Bootstrap is not loaded. Tooltips not initialized.');
}
console.log('✅ Enhanced layout manager initialized');

Comment on lines +134 to +147
if (response.status === 429 || response.status >= 500) {
if (attemptsLeft > 1) {
const backoffDelay = Math.pow(2, this.retryAttempts - attemptsLeft) * 1000; // Exponential backoff
console.warn(`Request failed (${response.status}), retrying in ${backoffDelay}ms. Attempts left: ${attemptsLeft - 1}`);

// Provide user feedback about retry
if (typeof addToProgressConsole === 'function') {
addToProgressConsole(`Request failed (${response.status}), retrying in ${backoffDelay/1000}s...`, 'warning');
}

await new Promise(resolve => setTimeout(resolve, backoffDelay));
return this.makeRequestWithRetry(endpoint, data, method, isFormData, timeoutMs, attemptsLeft - 1);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (code-quality): Merge nested if conditions (merge-nested-ifs)

Suggested change
if (response.status === 429 || response.status >= 500) {
if (attemptsLeft > 1) {
const backoffDelay = Math.pow(2, this.retryAttempts - attemptsLeft) * 1000; // Exponential backoff
console.warn(`Request failed (${response.status}), retrying in ${backoffDelay}ms. Attempts left: ${attemptsLeft - 1}`);
// Provide user feedback about retry
if (typeof addToProgressConsole === 'function') {
addToProgressConsole(`Request failed (${response.status}), retrying in ${backoffDelay/1000}s...`, 'warning');
}
await new Promise(resolve => setTimeout(resolve, backoffDelay));
return this.makeRequestWithRetry(endpoint, data, method, isFormData, timeoutMs, attemptsLeft - 1);
}
}
if ((response.status === 429 || response.status >= 500) && attemptsLeft > 1) {
const backoffDelay = Math.pow(2, this.retryAttempts - attemptsLeft) * 1000; // Exponential backoff
console.warn(`Request failed (${response.status}), retrying in ${backoffDelay}ms. Attempts left: ${attemptsLeft - 1}`);
// Provide user feedback about retry
if (typeof addToProgressConsole === 'function') {
addToProgressConsole(`Request failed (${response.status}), retrying in ${backoffDelay/1000}s...`, 'warning');
}
await new Promise(resolve => setTimeout(resolve, backoffDelay));
return this.makeRequestWithRetry(endpoint, data, method, isFormData, timeoutMs, attemptsLeft - 1);
}


ExplanationReading deeply nested conditional code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two if conditions can be combined using
and is an easy win.

Comment on lines +206 to +209
const response = await this.makeRequest(this.endpoints.SUMMARIZE, { text }, 'POST');

// The makeRequest method already handles JSON parsing, so response is the data
return response;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)

Suggested change
const response = await this.makeRequest(this.endpoints.SUMMARIZE, { text }, 'POST');
// The makeRequest method already handles JSON parsing, so response is the data
return response;
return await this.makeRequest(this.endpoints.SUMMARIZE, { text }, 'POST');


ExplanationSomething that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.

});

// Get or create API client
let apiClient = window.apiClient;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)

Suggested change
let apiClient = window.apiClient;
let {apiClient} = window;


ExplanationObject destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the Airbnb Javascript Style Guide

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several new JavaScript modules to build an interactive demo website. The code is well-structured with a clear separation of concerns into modules for API communication, configuration, layout management, and voice recording. The use of classes and modern JavaScript features is commendable.

However, I've identified a few areas for improvement. There's a critical bug in voice-recorder.js where the API response is handled incorrectly, which will prevent voice transcription from working. I've also found several instances of redundant code, such as re-creating the API client instead of using the global instance, and duplicated UI notification logic. Some parts of the code could be made more robust and maintainable by better leveraging the existing SAMOAPIClient for all API calls, including the one to OpenAI. Finally, there are some minor issues like dead code and a misleading log message.

My feedback focuses on fixing the critical bug, reducing redundancy, and improving the overall consistency and maintainability of the new modules.

Comment on lines +178 to +189
const response = await apiClient.transcribeAudio(audioFile);

if (response.ok) {
const result = await response.json();
console.log('✅ Transcription successful:', result);

// Display results in the UI
this.displayTranscriptionResults(result);
} else {
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(`API request failed (${response.status}): ${errorText}`);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

There is a critical issue in how the response from apiClient.transcribeAudio(audioFile) is handled. The apiClient.makeRequest method (used by transcribeAudio) already processes the fetch response, checks for errors, and returns the parsed JSON data. However, this code treats the returned data as a raw Response object, incorrectly checking response.ok and calling response.json(). This will cause the transcription to fail. You should directly use the response variable as it already contains the final JSON result.

                console.log('🔄 Sending audio for transcription...');
                const result = await apiClient.transcribeAudio(audioFile);
                console.log('✅ Transcription successful:', result);

                // Display results in the UI
                this.displayTranscriptionResults(result);

console.log('🔥 Calling emotion API...');
addToProgressConsole('🌐 Sending request to emotion analysis API...', 'processing');
// Create API client instance for proper timeout and error handling
const apiClient = new SAMOAPIClient();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

A new SAMOAPIClient is instantiated here. This is inefficient and bypasses the stateful features of the global window.apiClient instance (like the isColdStart optimization). You should use the globally-initialized window.apiClient instead.

Suggested change
const apiClient = new SAMOAPIClient();
const apiClient = window.apiClient;

addToProgressConsole('🌐 Sending request to emotion analysis API...', 'processing');
// Create API client instance for proper timeout and error handling
const apiClient = new SAMOAPIClient();
const data = await apiClient.makeRequest(apiClient.endpoints.EMOTION, { text: testText }, 'POST');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Instead of calling makeRequest directly, you should use the higher-level detectEmotions method. This encapsulates the endpoint logic and includes valuable features like the mock data fallback, making the code more robust and maintainable.

Suggested change
const data = await apiClient.makeRequest(apiClient.endpoints.EMOTION, { text: testText }, 'POST');
const data = await apiClient.detectEmotions(testText);


try {
// Create API client instance for proper timeout and error handling
const apiClient = new SAMOAPIClient();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

A new SAMOAPIClient is instantiated here, similar to the issue in testWithRealAPI. This is inefficient and bypasses the stateful features of the global window.apiClient instance. You should use the globally-initialized window.apiClient instead.

Suggested change
const apiClient = new SAMOAPIClient();
const apiClient = window.apiClient;

console.log('📝 Summarization endpoint:', apiClient.endpoints.SUMMARIZE);
console.log('📝 Full API URL:', `${apiClient.baseURL}${apiClient.endpoints.SUMMARIZE}`);

const data = await apiClient.makeRequest(apiClient.endpoints.SUMMARIZE, { text: text }, 'POST');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Instead of calling makeRequest directly, you should use the higher-level summarizeText method. This encapsulates the endpoint logic and includes the mock data fallback, which improves robustness and aligns with the pattern used elsewhere in the SAMOAPIClient.

Suggested change
const data = await apiClient.makeRequest(apiClient.endpoints.SUMMARIZE, { text: text }, 'POST');
const data = await apiClient.summarizeText(text);

Comment on lines +1287 to +1300
// Make functions globally available
window.generateSampleText = generateSampleText;
window.processText = processText;
window.testWithRealAPI = testWithRealAPI;
window.callSummarizationAPI = callSummarizationAPI;
window.updateElement = updateElement;
window.showResultsSections = showResultsSections;
window.addToProgressConsole = addToProgressConsole;
window.clearProgressConsole = clearProgressConsole;
window.createEmotionChart = createEmotionChart;
window.resetToInputScreen = resetToInputScreen;
window.clearAllResultContent = clearAllResultContent;
window.manageApiKey = manageApiKey;
window.clearAll = clearAllResultContent; // Alias for clearAll function
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Assigning multiple functions to the window object pollutes the global namespace and can lead to conflicts. Since demo-initialization.js already attaches event listeners to the UI elements, these global assignments are likely redundant. It's better to rely on the event listeners and remove these assignments. If some functions must be global, consider namespacing them under a single object, e.g., window.SAMODemo = { ... }.

Comment thread website/js/config.js
OPENAI_PROXY: '/proxy/openai'
};

console.log('🔧 Running in localhost development mode - using deployed Cloud Run API');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The log message is misleading. It states "using deployed Cloud Run API" but the BASE_URL is set to http://localhost:8002. This can cause confusion during development. The message should accurately reflect that a local API server is being used.

Suggested change
console.log('🔧 Running in localhost development mode - using deployed Cloud Run API');
console.log('🔧 Running in localhost development mode - using local API server at http://localhost:8002');

Comment on lines +17 to +31
processBtn.addEventListener('click', function() {
console.log('🔘 Process button clicked!');
// Use enhanced state management processing
if (typeof processTextWithStateManagement === 'function') {
processTextWithStateManagement();
} else if (typeof window.processText === 'function') {
// Fallback to original function
if (window.LayoutManager?.showProcessingState) {
window.LayoutManager.showProcessingState();
}
window.processText(true); // Skip state check since showProcessingState() handles it
} else {
console.error('❌ processText function not available');
}
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The event listener for the process button contains complex fallback logic, checking for processTextWithStateManagement before falling back to window.processText. This indicates a fragile integration between the modules. To simplify and improve robustness, you should ensure processTextWithStateManagement is always available and remove the fallback, making it the single entry point for this action. A similar simplification should be applied to the 'Clear' button listener.

Comment on lines +411 to +418
if (maybe && typeof maybe.then === 'function') {
maybe.then(onDone).catch((error) => {
console.error('Processing error:', error);
LayoutManager.resetToInitialState();
});
} else {
onDone();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The check if (maybe && typeof maybe.then === 'function') is redundant. Since processText is an async function, it will always return a Promise. You can directly use .then() and .catch() on its return value, and the else block is unreachable.

        maybe.then(onDone).catch((error) => {
            console.error('Processing error:', error);
            LayoutManager.resetToInitialState();
        });

Comment on lines +353 to +389
showMessage(message, type = 'info') {
// Create a simple toast notification
const toast = document.createElement('div');
toast.className = `toast-notification toast-${type}`;
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 6px;
color: white;
font-weight: 500;
z-index: 10000;
opacity: 0;
transition: opacity 0.3s ease;
`;

// Set background color based on type
const colors = {
success: '#28a745',
error: '#dc3545',
info: '#17a2b8'
};
toast.style.backgroundColor = colors[type] || colors.info;

document.body.appendChild(toast);

// Animate in
setTimeout(() => toast.style.opacity = '1', 100);

// Remove after delay
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => document.body.removeChild(toast), 300);
}, 3000);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This file implements a custom showMessage function to display toast notifications. However, comprehensive-demo.js already defines showInlineMessage for a similar purpose. Having duplicated UI notification logic makes the code harder to maintain and can lead to an inconsistent user experience. This logic should be consolidated into a single, shared utility, perhaps within layout-manager.js or its own file, that all modules can use.

- Replace HTML redirect content in favicon.ico with proper favicon file
- Move hardcoded production URLs to use centralized config.js configuration
- Add config.js script tags to load configuration before other scripts
- Update API_BASE_URL references to use window.SAMO_CONFIG
- Improve maintainability by centralizing API endpoint configuration

This resolves favicon display issues and eliminates hardcoded URLs
for better environment management and deployment flexibility.
@uelkerd
Copy link
Copy Markdown
Owner Author

uelkerd commented Sep 27, 2025

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 27, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 93d71d2 and d1e4c9b.

⛔ Files ignored due to path filters (2)
  • website/favicon.ico is excluded by !**/*.ico
  • website/favicon.svg is excluded by !**/*.svg
📒 Files selected for processing (7)
  • website/comprehensive-demo.html (1 hunks)
  • website/index.html (4 hunks)
  • website/js/comprehensive-demo.js (1 hunks)
  • website/js/config.js (1 hunks)
  • website/js/demo-initialization.js (1 hunks)
  • website/js/layout-manager.js (1 hunks)
  • website/js/voice-recorder.js (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (5)
website/js/demo-initialization.js (1)

117-124: Guard Bootstrap tooltip initialization

Line 120 references the global bootstrap symbol without checking it exists; if the CDN fails or Bootstrap loads after this script, you get a ReferenceError and the whole initialization short-circuits. Please verify window.bootstrap?.Tooltip before instantiating tooltips (logging a warning otherwise).

-    const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
-    const tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
-        return new bootstrap.Tooltip(tooltipTriggerEl);
-    });
+    let tooltipList = [];
+    if (window.bootstrap?.Tooltip) {
+        const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
+        tooltipList = tooltipTriggerList.map((tooltipTriggerEl) => new window.bootstrap.Tooltip(tooltipTriggerEl));
+    } else {
+        console.warn('⚠️ Bootstrap library not detected. Tooltips were not initialized.');
+    }
website/js/config.js (1)

80-80: Correct the localhost log message

Line 80 says “using deployed Cloud Run API” even though the override just pointed BASE_URL at http://localhost:8002. That misleads anyone debugging environment routing—please update the log string to match the actual destination.

-    console.log('🔧 Running in localhost development mode - using deployed Cloud Run API');
+    console.log('🔧 Running in localhost development mode - using local API server at http://localhost:8002');
website/js/voice-recorder.js (1)

176-189: Handle transcribeAudio return value correctly

SAMOAPIClient.transcribeAudio already returns the parsed JSON from makeRequest; it is not a raw Response with ok/json() helpers. Treating it as a Response makes the happy path throw (“response.ok is undefined”), so transcription never reaches the UI. Please use the returned object directly.

-                const response = await apiClient.transcribeAudio(audioFile);
-
-                if (response.ok) {
-                    const result = await response.json();
-                    console.log('✅ Transcription successful:', result);
-
-                    // Display results in the UI
-                    this.displayTranscriptionResults(result);
-                } else {
-                    const errorText = await response.text().catch(() => 'Unknown error');
-                    throw new Error(`API request failed (${response.status}): ${errorText}`);
-                }
+                const result = await apiClient.transcribeAudio(audioFile);
+                console.log('✅ Transcription successful:', result);
+
+                this.displayTranscriptionResults(result);
website/js/comprehensive-demo.js (2)

768-770: Re-use window.apiClient + detectEmotions fallback

Echoing the earlier feedback: instantiating a fresh SAMOAPIClient here resets the cold-start optimisation every run and, more importantly, calling makeRequest directly skips the resiliency logic in detectEmotions (mock fallback, friendly errors). When the API 429s/5xxs, the demo now hard fails instead of degrading gracefully. Please reuse the global client and call the high-level helper so the built-in safeguards stay active.

-        const apiClient = new SAMOAPIClient();
-        const data = await apiClient.makeRequest(apiClient.endpoints.EMOTION, { text: testText }, 'POST');
+        const apiClient = window.apiClient ?? (window.apiClient = new SAMOAPIClient());
+        const data = await apiClient.detectEmotions(testText);

872-878: Leverage the global client + summarizeText fallback

Same underlying issue as above: this reinvents the API client each call and bypasses summarizeText, so cold-start tracking and the mock-summary fallback never run. Any transient failure now blocks the user. Please reuse window.apiClient (initialised on DOM ready) and delegate to summarizeText to retain the error-handling contract.

-        const apiClient = new SAMOAPIClient();
-        const data = await apiClient.makeRequest(apiClient.endpoints.SUMMARIZE, { text: text }, 'POST');
+        const apiClient = window.apiClient ?? (window.apiClient = new SAMOAPIClient());
+        const data = await apiClient.summarizeText(text);

Comment on lines +102 to +106
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('showDebug') === '1') {
LayoutManager.toggleDebugSection(true);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard LayoutManager before toggling debug section

Line 104 assumes LayoutManager is always defined; if the module fails to load, this throws a ReferenceError as soon as ?showDebug=1 is used, aborting the entire initializer. Please null-check via window.LayoutManager?.toggleDebugSection(...) (and log a warning when missing) before calling.

-        LayoutManager.toggleDebugSection(true);
+        if (window.LayoutManager?.toggleDebugSection) {
+            window.LayoutManager.toggleDebugSection(true);
+        } else {
+            console.warn('⚠️ LayoutManager not available; debug section remains hidden.');
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('showDebug') === '1') {
LayoutManager.toggleDebugSection(true);
}
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('showDebug') === '1') {
if (window.LayoutManager?.toggleDebugSection) {
window.LayoutManager.toggleDebugSection(true);
} else {
console.warn('⚠️ LayoutManager not available; debug section remains hidden.');
}
}
🤖 Prompt for AI Agents
In website/js/demo-initialization.js around lines 102 to 106, the code calls
LayoutManager.toggleDebugSection(true) without ensuring LayoutManager exists
which can throw a ReferenceError if the module failed to load; update the call
to guard via window.LayoutManager?.toggleDebugSection(true) (or check if
window.LayoutManager and typeof toggleDebugSection === 'function') and if
missing emit a console.warn explaining that LayoutManager is unavailable so
initialization can continue without throwing.

Comment on lines +23 to +32
emergencyReset() {
console.warn('🚨 Emergency reset: processing state appears stuck, forcing reset...');
this.isProcessing = false;
this.activeRequests.clear();
this.currentState = 'initial';
// Also clear any UI elements that might be stuck
if (typeof clearAllResultContent === 'function') {
clearAllResultContent();
}
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Emergency reset must abort outstanding requests

emergencyReset() currently drops the references in activeRequests without aborting the controllers, so those fetches continue running and can mutate the UI after we've “reset” the layout. This is easy to trigger because showProcessingState() invokes emergencyReset() on any guarded re-entry. Please cancel the tracked requests (and clear the timestamp) before wiping the state.

     emergencyReset() {
         console.warn('🚨 Emergency reset: processing state appears stuck, forcing reset...');
-        this.isProcessing = false;
-        this.activeRequests.clear();
-        this.currentState = 'initial';
+        this.cancelActiveRequests();
+        this.isProcessing = false;
+        this.processingStartTime = null;
+        this.currentState = 'initial';
         // Also clear any UI elements that might be stuck
         if (typeof clearAllResultContent === 'function') {
             clearAllResultContent();
         }
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
emergencyReset() {
console.warn('🚨 Emergency reset: processing state appears stuck, forcing reset...');
this.isProcessing = false;
this.activeRequests.clear();
this.currentState = 'initial';
// Also clear any UI elements that might be stuck
if (typeof clearAllResultContent === 'function') {
clearAllResultContent();
}
},
emergencyReset() {
console.warn('🚨 Emergency reset: processing state appears stuck, forcing reset...');
this.cancelActiveRequests();
this.isProcessing = false;
this.processingStartTime = null;
this.currentState = 'initial';
// Also clear any UI elements that might be stuck
if (typeof clearAllResultContent === 'function') {
clearAllResultContent();
}
},
🤖 Prompt for AI Agents
In website/js/layout-manager.js around lines 23 to 32, emergencyReset()
currently clears activeRequests without aborting ongoing fetches; update it to
first iterate the entries in this.activeRequests and for each entry: if it has a
controller property that is an AbortController (or the entry itself is an
AbortController) call controller.abort(); remove any timestamp property/field on
the entry (or set entry.timestamp = null) to prevent stale time-based logic;
only after aborting and clearing timestamps call this.activeRequests.clear(),
then proceed to set this.isProcessing = false and this.currentState = 'initial'
and clear UI. Ensure you null-check entries and swallow any exceptions from
abort to avoid throwing during reset.

Comment on lines +110 to +121
// Check if processing is allowed
if (!this.startProcessing()) {
console.warn('⚠️ Cannot start processing - operation already in progress');
// Try emergency reset and retry once
console.warn('🔄 Attempting emergency reset and retry...');
this.emergencyReset();
if (!this.startProcessing()) {
console.error('❌ Emergency reset failed - processing still blocked');
return false;
}
console.log('✅ Emergency reset successful - processing can proceed');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid emergency reset when a run is still active

When startProcessing() returns false, it means a run is legitimately in progress (and still within the 2‑minute window). Forcing emergencyReset() at that moment drops the guard while the original network requests keep running, so you can end up with overlapping jobs and stale callbacks racing the UI. Since startProcessing() already force-resets once the timeout is exceeded, this branch should just bail out.

         // Check if processing is allowed
         if (!this.startProcessing()) {
             console.warn('⚠️ Cannot start processing - operation already in progress');
-            // Try emergency reset and retry once
-            console.warn('🔄 Attempting emergency reset and retry...');
-            this.emergencyReset();
-            if (!this.startProcessing()) {
-                console.error('❌ Emergency reset failed - processing still blocked');
-                return false;
-            }
-            console.log('✅ Emergency reset successful - processing can proceed');
+            return false;
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Check if processing is allowed
if (!this.startProcessing()) {
console.warn('⚠️ Cannot start processing - operation already in progress');
// Try emergency reset and retry once
console.warn('🔄 Attempting emergency reset and retry...');
this.emergencyReset();
if (!this.startProcessing()) {
console.error('❌ Emergency reset failed - processing still blocked');
return false;
}
console.log('✅ Emergency reset successful - processing can proceed');
}
// Check if processing is allowed
if (!this.startProcessing()) {
console.warn('⚠️ Cannot start processing - operation already in progress');
return false;
}
🤖 Prompt for AI Agents
In website/js/layout-manager.js around lines 110 to 121, the code attempts an
emergencyReset() and retry when startProcessing() returns false; this can drop
the guard while an active run is still within its 2-minute window and cause
overlapping jobs. Change the branch so that if startProcessing() returns false
you do not call emergencyReset() or retry — simply log/warn and return false
immediately, letting the existing timeout-based reset logic handle expired runs.

Comment on lines +291 to +297
// Remove existing state classes
stepElement.classList.remove('active', 'completed', 'error');
stepIcon.classList.remove('pending', 'active', 'completed', 'error');

// Add new state
stepElement.classList.add(state);
stepIcon.classList.add(state);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Resetting steps should remove previous state classes

updateProgressStep() never removes the pending class from stepElement, so when you mark a step as active or completed the element keeps both classes and the visual state is inconsistent. Drop pending alongside the other state classes when resetting.

-            stepElement.classList.remove('active', 'completed', 'error');
+            stepElement.classList.remove('pending', 'active', 'completed', 'error');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Remove existing state classes
stepElement.classList.remove('active', 'completed', 'error');
stepIcon.classList.remove('pending', 'active', 'completed', 'error');
// Add new state
stepElement.classList.add(state);
stepIcon.classList.add(state);
// Remove existing state classes
stepElement.classList.remove('pending', 'active', 'completed', 'error');
stepIcon.classList.remove('pending', 'active', 'completed', 'error');
// Add new state
stepElement.classList.add(state);
stepIcon.classList.add(state);
🤖 Prompt for AI Agents
In website/js/layout-manager.js around lines 291 to 297, the reset logic removes
state classes from stepElement but omits 'pending', causing stepElement to
retain 'pending' when switching to 'active' or 'completed'; update the remove
call to include 'pending' (i.e., remove 'pending', 'active', 'completed',
'error' from stepElement) so the element no longer keeps the prior pending class
before adding the new state.

Comment on lines +149 to +223
try {
// Show processing state
this.showProcessingState();

// Create a File object from the blob
const audioFile = new File([audioBlob], 'recording.webm', {
type: audioBlob.type
});

// Get or create API client
let apiClient = window.apiClient;
if (!apiClient) {
console.log('⚠️ Global API client not available, creating new instance...');
try {
// Try to create a new SAMOAPIClient instance
if (typeof SAMOAPIClient !== 'undefined') {
apiClient = new SAMOAPIClient();
console.log('✅ Created new API client instance');
} else {
throw new Error('SAMOAPIClient class not available');
}
} catch (createError) {
throw new Error(`Unable to create API client: ${createError.message}`);
}
}

// Use API client to transcribe
if (apiClient && typeof apiClient.transcribeAudio === 'function') {
console.log('🔄 Sending audio for transcription...');
const response = await apiClient.transcribeAudio(audioFile);

if (response.ok) {
const result = await response.json();
console.log('✅ Transcription successful:', result);

// Display results in the UI
this.displayTranscriptionResults(result);
} else {
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(`API request failed (${response.status}): ${errorText}`);
}
} else {
throw new Error('API client transcribeAudio method not available');
}

} catch (error) {
console.error('Failed to transcribe audio:', error);

// Provide specific error messages based on error type
let userMessage = 'Transcription failed';
if (error.message.includes('API client not available')) {
userMessage = 'Voice service unavailable. Please refresh the page and try again.';
} else if (error.message.includes('Failed to fetch') || error.message.includes('Network')) {
userMessage = 'Network error. Please check your connection and try again.';
} else if (error.message.includes('timeout')) {
userMessage = 'Request timeout. Please try with a shorter recording.';
} else if (error.message.includes('400')) {
userMessage = 'Invalid audio format. Please try recording again.';
} else if (error.message.includes('500')) {
userMessage = 'Server error. Please try again in a moment.';
} else {
userMessage = `Transcription failed: ${error.message}`;
}

this.showError(userMessage);

// Reset processing state on error
if (window.LayoutManager && window.LayoutManager.isProcessing) {
window.LayoutManager.endProcessing();
console.log('🔧 Processing state reset due to transcription error');
}
} finally {
this.hideProcessingState();
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Respect LayoutManager’s “already processing” guard

showProcessingState() returns false when LayoutManager.canStartProcessing() says “busy”. Ignoring that and continuing into the API call bypasses the concurrency guard and still triggers hideProcessingState() at the end, leaving the UI in an inconsistent state. Bail out early when the guard fires and only call hideProcessingState() if we actually entered the processing state.

-    async processRecordedAudio(audioBlob) {
-        try {
-            // Show processing state
-            this.showProcessingState();
+    async processRecordedAudio(audioBlob) {
+        let startedProcessing = false;
+        try {
+            // Show processing state
+            const processingState = this.showProcessingState();
+            if (processingState === false) {
+                console.warn('⚠️ A processing operation is already running; skipping transcription request.');
+                return;
+            }
+            startedProcessing = true;
@@
-        } finally {
-            this.hideProcessingState();
+        } finally {
+            if (startedProcessing) {
+                this.hideProcessingState();
+            }
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
// Show processing state
this.showProcessingState();
// Create a File object from the blob
const audioFile = new File([audioBlob], 'recording.webm', {
type: audioBlob.type
});
// Get or create API client
let apiClient = window.apiClient;
if (!apiClient) {
console.log('⚠️ Global API client not available, creating new instance...');
try {
// Try to create a new SAMOAPIClient instance
if (typeof SAMOAPIClient !== 'undefined') {
apiClient = new SAMOAPIClient();
console.log('✅ Created new API client instance');
} else {
throw new Error('SAMOAPIClient class not available');
}
} catch (createError) {
throw new Error(`Unable to create API client: ${createError.message}`);
}
}
// Use API client to transcribe
if (apiClient && typeof apiClient.transcribeAudio === 'function') {
console.log('🔄 Sending audio for transcription...');
const response = await apiClient.transcribeAudio(audioFile);
if (response.ok) {
const result = await response.json();
console.log('✅ Transcription successful:', result);
// Display results in the UI
this.displayTranscriptionResults(result);
} else {
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(`API request failed (${response.status}): ${errorText}`);
}
} else {
throw new Error('API client transcribeAudio method not available');
}
} catch (error) {
console.error('Failed to transcribe audio:', error);
// Provide specific error messages based on error type
let userMessage = 'Transcription failed';
if (error.message.includes('API client not available')) {
userMessage = 'Voice service unavailable. Please refresh the page and try again.';
} else if (error.message.includes('Failed to fetch') || error.message.includes('Network')) {
userMessage = 'Network error. Please check your connection and try again.';
} else if (error.message.includes('timeout')) {
userMessage = 'Request timeout. Please try with a shorter recording.';
} else if (error.message.includes('400')) {
userMessage = 'Invalid audio format. Please try recording again.';
} else if (error.message.includes('500')) {
userMessage = 'Server error. Please try again in a moment.';
} else {
userMessage = `Transcription failed: ${error.message}`;
}
this.showError(userMessage);
// Reset processing state on error
if (window.LayoutManager && window.LayoutManager.isProcessing) {
window.LayoutManager.endProcessing();
console.log('🔧 Processing state reset due to transcription error');
}
} finally {
this.hideProcessingState();
}
}
async processRecordedAudio(audioBlob) {
let startedProcessing = false;
try {
// Show processing state
const processingState = this.showProcessingState();
if (processingState === false) {
console.warn('⚠️ A processing operation is already running; skipping transcription request.');
return;
}
startedProcessing = true;
// Create a File object from the blob
const audioFile = new File([audioBlob], 'recording.webm', {
type: audioBlob.type
});
// Get or create API client
let apiClient = window.apiClient;
if (!apiClient) {
console.log('⚠️ Global API client not available, creating new instance...');
try {
// Try to create a new SAMOAPIClient instance
if (typeof SAMOAPIClient !== 'undefined') {
apiClient = new SAMOAPIClient();
console.log('✅ Created new API client instance');
} else {
throw new Error('SAMOAPIClient class not available');
}
} catch (createError) {
throw new Error(`Unable to create API client: ${createError.message}`);
}
}
// Use API client to transcribe
if (apiClient && typeof apiClient.transcribeAudio === 'function') {
console.log('🔄 Sending audio for transcription...');
const response = await apiClient.transcribeAudio(audioFile);
if (response.ok) {
const result = await response.json();
console.log('✅ Transcription successful:', result);
// Display results in the UI
this.displayTranscriptionResults(result);
} else {
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(`API request failed (${response.status}): ${errorText}`);
}
} else {
throw new Error('API client transcribeAudio method not available');
}
} catch (error) {
console.error('Failed to transcribe audio:', error);
// Provide specific error messages based on error type
let userMessage = 'Transcription failed';
if (error.message.includes('API client not available')) {
userMessage = 'Voice service unavailable. Please refresh the page and try again.';
} else if (error.message.includes('Failed to fetch') || error.message.includes('Network')) {
userMessage = 'Network error. Please check your connection and try again.';
} else if (error.message.includes('timeout')) {
userMessage = 'Request timeout. Please try with a shorter recording.';
} else if (error.message.includes('400')) {
userMessage = 'Invalid audio format. Please try recording again.';
} else if (error.message.includes('500')) {
userMessage = 'Server error. Please try again in a moment.';
} else {
userMessage = `Transcription failed: ${error.message}`;
}
this.showError(userMessage);
// Reset processing state on error
if (window.LayoutManager && window.LayoutManager.isProcessing) {
window.LayoutManager.endProcessing();
console.log('🔧 Processing state reset due to transcription error');
}
} finally {
if (startedProcessing) {
this.hideProcessingState();
}
}
}
🤖 Prompt for AI Agents
In website/js/voice-recorder.js around lines 149 to 223, showProcessingState()
can return false when LayoutManager reports busy; the current code ignores that
and proceeds, causing hideProcessingState() to run later and leaving the UI
inconsistent. Modify the method to capture the return value (e.g., started =
this.showProcessingState()), immediately bail out (return) if started is false,
and only call hideProcessingState() and LayoutManager.endProcessing() in the
catch/finally blocks when started is true so the processing state is only
cleared when you actually entered it.

@uelkerd uelkerd self-assigned this Sep 27, 2025
uelkerd added a commit that referenced this pull request Sep 27, 2025
- Update comprehensive-demo.html with improved layout and functionality
- Add favicon.svg for modern vector icon support
- Update favicon.ico with enhanced branding
- Enhance index.html with Priority 1 features showcase and improved accessibility

Fortress-compliant PR #190-A (4 files) - HTML Updates & Configuration
Part of fortress split from original PR #190 for better review compliance.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
uelkerd added a commit that referenced this pull request Sep 27, 2025
- Add config.js for centralized configuration management
- Add layout-manager.js for responsive layout and UI state management
- Add voice-recorder.js for advanced audio recording capabilities

Core modules provide foundation for website interactivity and user experience.
These modules are designed to be imported by integration scripts in subsequent PRs.

Fortress-compliant PR #190-B (3 files) - Core JavaScript Modules
Part of fortress split from original PR #190 for better review compliance.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
uelkerd added a commit that referenced this pull request Sep 27, 2025
- Add comprehensive-demo.js for complete demo page functionality and user interactions
- Add demo-initialization.js for application startup and module coordination

These integration scripts bring together the core modules from PR #190-B
to create the complete interactive website experience. Handles user workflows,
API integration, and coordinated initialization of all website features.

Fortress-compliant PR #190-C (2 files) - Integration & Initialization
Final part of fortress split from original PR #190 for better review compliance.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@uelkerd
Copy link
Copy Markdown
Owner Author

uelkerd commented Sep 27, 2025

🏰 Fortress Strategy Implementation Complete

This PR has been superseded by fortress-compliant split PRs for better review compliance and manageable code review process.

Fortress Split Summary

Original PR #190 (9 files) has been strategically split into three fortress-compliant PRs:

PR #200 (PR #190-A): HTML Updates & Configuration

PR #201 (PR #190-B): Core JavaScript Modules

PR #202 (PR #190-C): Integration & Initialization

Strategic Benefits

  • 🎯 Fortress Compliance: All PRs ≤5 files for automated review compatibility
  • 🔍 Better Review Process: Smaller, focused PRs enable thorough code review
  • 📦 Logical Separation: HTML → Core Modules → Integration workflow
  • Parallel Review: Multiple reviewers can work simultaneously
  • 🛡️ Risk Reduction: Independent merge capability reduces integration risk

Next Steps

  1. Review and merge PR feat: enhance website HTML files and configuration (Fortress PR #190-A) #200 (HTML foundation)
  2. Review and merge PR feat: add core JavaScript modules for website functionality (Fortress PR #190-B) #201 (core modules)
  3. Review and merge PR feat: add integration and initialization scripts for website (Fortress PR #190-C) #202 (integration)

All original functionality preserved - just organized for better review compliance!

🤖 Generated with Claude Code

@uelkerd uelkerd closed this Sep 27, 2025
uelkerd added a commit that referenced this pull request Sep 27, 2025
…A) (#200)

* feat: add enhanced HTML files and configuration for website

- Update comprehensive-demo.html with improved layout and functionality
- Add favicon.svg for modern vector icon support
- Update favicon.ico with enhanced branding
- Enhance index.html with Priority 1 features showcase and improved accessibility

Fortress-compliant PR #190-A (4 files) - HTML Updates & Configuration
Part of fortress split from original PR #190 for better review compliance.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: address Copilot AI and coderabbitai review comments

- Create missing js/config.js with centralized API configuration
- Add proper accessibility attributes to navigation elements
- Add aria-hidden=true to decorative icons in buttons
- Update placeholder URL from api.example.com to your-api-domain.com
- Add error handling for missing config.js file in both HTML files
- Fix invalid JavaScript template literal syntax in Python code snippet
- Use proper Python f-string syntax instead of ${API_BASE_URL}

All changes improve accessibility, configuration management, and code quality.

* fix: pin apt-get package versions to resolve DOK-DL3008

- Pin ca-certificates=20230311 for Debian bookworm
- Pin curl=7.88.1-10+deb12u5 for Debian bookworm
- Ensures reproducible builds and prevents version drift
- Resolves Docker linting rule DOK-DL3008

---------

Co-authored-by: Claude <noreply@anthropic.com>
uelkerd added a commit that referenced this pull request Sep 27, 2025
…s PR #190-C) (#202)

* feat: add integration and initialization scripts for website

- Add comprehensive-demo.js for complete demo page functionality and user interactions
- Add demo-initialization.js for application startup and module coordination

These integration scripts bring together the core modules from PR #190-B
to create the complete interactive website experience. Handles user workflows,
API integration, and coordinated initialization of all website features.

Fortress-compliant PR #190-C (2 files) - Integration & Initialization
Final part of fortress split from original PR #190 for better review compliance.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: guard LayoutManager.toggleDebugSection call with optional chaining to prevent ReferenceError

* fix: reuse global API client and use detectEmotions method for proper fallbacks and tracking

* fix: prefer sessionStorage over localStorage for API key storage to improve security

* refactor: improve code organization and error handling

- Combine multiple DOMContentLoaded listeners into single listener for clarity
- Remove redundant setTimeout loading messages (already handled by addToProgressConsole)
- Use global API client instance instead of creating new instances
- Namespace functions under window.SamoDemo to avoid global pollution
- Add user-visible error alerts when core functions are unavailable

* fix: address code quality nitpicks for better maintainability

demo-initialization.js:
- Reference optional globals via window for module safety
- Avoid enumerating window object for cleaner logging
- Make initialization idempotent to prevent duplicate listeners
- Remove unused tooltipList variable declaration

comprehensive-demo.js:
- Reuse global API client for summarization calls
- Broaden network error detection to include 'Failed to fetch'
- Add Accept header to all API requests
- Remove unused summaryLength variable and inline calculation
- Add updateApiKeyButtonStatus helper function
- Prevent overwriting existing global API client

* fix: add missing script imports for comprehensive-demo.js and demo-initialization.js

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants