Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions docs/architecture-diagrams.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,73 @@
# Architecture Diagrams

## Simplified Architecture (Presentation View)

```mermaid
graph LR
subgraph Client
React[React App<br/>TypeScript + Vite]
end

subgraph Auth
Cognito[Cognito<br/>User Pool]
end

subgraph API
Gateway[API Gateway<br/>REST API]
Auth[Lambda<br/>Authorizer]
end

subgraph Core
Lambda[Lambda Functions<br/>Episodes, Clips, Teams]
end

subgraph AI
Bedrock[AWS Bedrock<br/>Nova Pro Agent]
end

subgraph Processing
MediaConvert[MediaConvert<br/>Video Chunks]
StepFn[Step Functions<br/>Clip Workflow]
end

subgraph Storage
DynamoDB[(DynamoDB<br/>Single Table)]
S3[(S3<br/>Videos/Transcripts)]
end

subgraph Events
EventBridge[EventBridge<br/>Event Bus]
Momento[Momento<br/>Real-time]
end

React -->|HTTPS| Gateway
React -.->|Auth| Cognito
Gateway --> Auth
Auth --> Lambda
Lambda --> DynamoDB
Lambda --> S3
S3 --> EventBridge
EventBridge --> Bedrock
EventBridge --> MediaConvert
EventBridge --> StepFn
Bedrock --> DynamoDB
StepFn --> S3
EventBridge --> Momento
Momento -.->|Subscribe| React

style React fill:#61dafb,stroke:#333,stroke-width:2px
style Cognito fill:#ff9900,stroke:#333,stroke-width:2px
style Gateway fill:#ff9900,stroke:#333,stroke-width:2px
style Lambda fill:#ff9900,stroke:#333,stroke-width:2px
style Bedrock fill:#ff9900,stroke:#333,stroke-width:2px
style DynamoDB fill:#4053d6,stroke:#333,stroke-width:2px
style S3 fill:#569a31,stroke:#333,stroke-width:2px
style EventBridge fill:#ff4081,stroke:#333,stroke-width:2px
style Momento fill:#00d4ff,stroke:#333,stroke-width:2px
style MediaConvert fill:#ff9900,stroke:#333,stroke-width:2px
style StepFn fill:#ff9900,stroke:#333,stroke-width:2px
```

## 1. Backend Architecture Diagram

```mermaid
Expand Down
16 changes: 14 additions & 2 deletions functions/agents/clip-detector.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,20 @@ Each clip you pass to **createClip** must contain the schema:

{
"segments": [
{ "startTime": "00:14:32", "endTime": "00:15:18", "speaker": "Allen", "order": 1, "transcript": "Did you know agents could do this?" }
{ "startTime": "00:41:01", "endTime": "00:41:05", "speaker": "Andres": "order": 2, "transcript": "No I didn't, but now we can use it" }
{
"startTime": "00:14:32,000",
"endTime": "00:15:18,500",
"speaker": "Allen",
"order": 1,
"transcript": "Did you know agents could do this? I was blown away the first time I saw it work end-to-end. You basically hand it a tool and it figures out the rest — no scaffolding, no hand-holding. It just goes. And the crazy part is it gets it right most of the time."
},
{
"startTime": "00:41:01,000",
"endTime": "00:41:05,200",
"speaker": "Andres",
"order": 2,
"transcript": "No I didn't, but now we can use it in production."
}
Comment on lines +200 to +213

Copilot AI Feb 18, 2026

Copy link

Choose a reason for hiding this comment

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

The example timestamps in the prompt now include milliseconds (e.g., "00:14:32,000"), but there's an inconsistency with the broader codebase. The global TimestampSchema in schemas/common.mjs still only accepts HH:MM:SS format without milliseconds (/^\d{2}:\d{2}:\d{2}$/), and tests explicitly verify this. While the create-clips tool schema was updated locally to accept milliseconds, this creates a potential validation mismatch. If segments are stored with millisecond timestamps, they may fail validation in other parts of the system that use the global schema. Consider updating the global TimestampSchema or documenting why some timestamps have milliseconds while others don't.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

],
"title": "Why we let our AI agent go rogue (on purpose)",
"summary": "Allen and Andres debate what happens when you remove safety guardrails from an agent and whether chaos teaches more than control.",
Expand Down
46 changes: 44 additions & 2 deletions functions/clips/get-clip.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Logger } from '@aws-lambda-powertools/logger';
import { DynamoDBClient, GetItemCommand } from '@aws-sdk/client-dynamodb';
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { marshall, unmarshall } from '@aws-sdk/util-dynamodb';
import { formatResponse } from '../utils/api.mjs';
import { getCurrentClipStatus } from '../utils/clips.mjs';
import { parseSrtFile, timeToSeconds } from '../utils/transcripts.mjs';

const logger = new Logger({ serviceName: 'clips' });
const ddb = new DynamoDBClient();
const s3 = new S3Client();

export const handler = async (event) => {
try {
Expand Down Expand Up @@ -46,12 +49,51 @@ export const handler = async (event) => {
const segments = clip.segments || [];
const segmentCount = segments.length;

// Attempt to extract accurate transcript text from the source SRT by matching
// each segment's time range. Falls back to the AI-stored text if the SRT is
// unavailable or yields no matching entries.
let srtEntries = [];
try {
const transcriptKey = `${tenantId}/${episodeId}/transcript.srt`;
const s3Response = await s3.send(new GetObjectCommand({
Bucket: process.env.BUCKET_NAME,
Key: transcriptKey
}));
const srtContent = await s3Response.Body.transformToString();
srtEntries = parseSrtFile(srtContent);
} catch (err) {
logger.warn('Could not load SRT for transcript extraction, falling back to stored text', {
error: err.message,
episodeId,
tenantId
});
}

const transcript = segments
.sort((a, b) => (a.order || 0) - (b.order || 0))
.map(segment => {
const speaker = segment.speaker || 'unknown';
const speakerLabel = segment.speaker ? `[${segment.speaker}]: ` : '';

if (srtEntries.length > 0) {
const segStart = timeToSeconds(segment.startTime);
const segEnd = timeToSeconds(segment.endTime);

const relevantEntries = srtEntries.filter(entry => {
const entryStart = timeToSeconds(entry.startTime);
const entryEnd = timeToSeconds(entry.endTime);
return entryStart < segEnd && entryEnd > segStart;
});
Comment on lines +77 to +85

Copilot AI Feb 18, 2026

Copy link

Choose a reason for hiding this comment

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

The SRT file is loaded from S3 on every get-clip request, and then every segment's time range is compared against all SRT entries using filter operations. For clips with multiple segments and large SRT files (hundreds of entries), this results in O(segments × entries) time complexity. Consider caching the parsed SRT entries or implementing a more efficient lookup strategy (e.g., building a time-based index). Alternatively, document that this is acceptable for the expected scale of operations.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback


if (relevantEntries.length > 0) {
// Use the full SRT text
const text = relevantEntries.map(e => e.text).join(' ');
return `${speakerLabel}${text}`;
}
}

// Fallback: use what the AI stored
const text = segment.transcript || '';
return `[${speaker}]: ${text}`;
return `${speakerLabel}${text}`;
Comment thread
andmoredev marked this conversation as resolved.
})
.join('\n\n');

Expand Down
2 changes: 1 addition & 1 deletion functions/utils/transcripts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const extractSpeakerFromText = (text) => {
export const timeToSeconds = (timeStr) => {
const [time, ms] = timeStr.split(',');
const [hours, minutes, seconds] = time.split(':').map(Number);
return hours * 3600 + minutes * 60 + seconds + parseInt(ms) / 1000;
return hours * 3600 + minutes * 60 + seconds + (ms ? parseInt(ms) / 1000 : 0);

Copilot AI Feb 18, 2026

Copy link

Choose a reason for hiding this comment

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

The parseInt function should explicitly specify base 10 as the second parameter to ensure consistent parsing. Use parseInt(ms, 10) instead of parseInt(ms) to avoid potential issues with strings that have leading zeros.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

};

export const secondsToTime = (totalSeconds) => {
Expand Down
8 changes: 5 additions & 3 deletions functions/utils/video-processing.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ export const timeToSeconds = (timeStr) => {
throw new Error('Invalid time string');
}

const parts = timeStr.split(':').map(part => parseInt(part, 10));
const [timePart, msPart] = timeStr.split(',');
const parts = timePart.split(':').map(Number);
const milliseconds = msPart ? parseInt(msPart) / 1000 : 0;

Copilot AI Feb 18, 2026

Copy link

Choose a reason for hiding this comment

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

The milliseconds parsing logic doesn't handle base 10 correctly. The parseInt function should explicitly specify base 10 as the second parameter. Without it, strings with leading zeros like "060" might be parsed incorrectly in some JavaScript environments. Use parseInt(msPart, 10) instead of parseInt(msPart).

Suggested change
const milliseconds = msPart ? parseInt(msPart) / 1000 : 0;
const milliseconds = msPart ? parseInt(msPart, 10) / 1000 : 0;

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +14

Copilot AI Feb 18, 2026

Copy link

Choose a reason for hiding this comment

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

The new millisecond support in timeToSeconds functions is not adequately tested. The existing tests in tests/unit/utils/video-processing.test.js only test the HH:MM:SS and MM:SS formats without milliseconds. Tests should be added to verify parsing of HH:MM:SS,mmm and MM:SS,mmm formats, including edge cases like '00:00:00,000', '01:30:45,500', and '10:00,100'.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback


if (parts.length === 2) {
const [minutes, seconds] = parts;
return minutes * 60 + seconds;
return minutes * 60 + seconds + milliseconds;
} else if (parts.length === 3) {
const [hours, minutes, seconds] = parts;
return hours * 3600 + minutes * 60 + seconds;
return hours * 3600 + minutes * 60 + seconds + milliseconds;
} else {
throw new Error('Time string must be in HH:MM:SS or MM:SS format');

Copilot AI Feb 18, 2026

Copy link

Choose a reason for hiding this comment

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

The error message states 'Time string must be in HH:MM:SS or MM:SS format' but this is now outdated. The function now supports milliseconds with the format HH:MM:SS,mmm or MM:SS,mmm. The error message should be updated to reflect this: 'Time string must be in HH:MM:SS, MM:SS, HH:MM:SS,mmm, or MM:SS,mmm format'.

Suggested change
throw new Error('Time string must be in HH:MM:SS or MM:SS format');
throw new Error('Time string must be in HH:MM:SS, MM:SS, HH:MM:SS,mmm, or MM:SS,mmm format');

Copilot uses AI. Check for mistakes.
}
Expand Down
5 changes: 5 additions & 0 deletions template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -929,9 +929,14 @@ Resources:
Action:
- dynamodb:GetItem
Resource: !GetAtt StreamPostProcessingTable.Arn
- Effect: Allow
Action:
- s3:GetObject
Resource: !Sub arn:${AWS::Partition}:s3:::${TranscriptBucket}/*
Environment:
Variables:
TABLE_NAME: !Ref StreamPostProcessingTable
BUCKET_NAME: !Ref TranscriptBucket
Events:
Get:
Type: Api
Expand Down
Loading