Skip to content
Closed
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
24 changes: 24 additions & 0 deletions sample-apps/react/ai-demo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
28 changes: 28 additions & 0 deletions sample-apps/react/ai-demo/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'

export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)
12 changes: 12 additions & 0 deletions sample-apps/react/ai-demo/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AI Components Demo</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
30 changes: 30 additions & 0 deletions sample-apps/react/ai-demo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "ai-demo",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@stream-io/video-react-sdk": "workspace:^",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.17.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.14.0",
"typescript": "~5.6.2",
"typescript-eslint": "^8.18.2",
"vite": "^6.0.5"
}
}
36 changes: 36 additions & 0 deletions sample-apps/react/ai-demo/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useEffect, useState } from 'react';
import { Aura } from './components/Aura';
import { AiCaptions } from './components/AiCaptions';
import { CallClosedCaption } from '@stream-io/video-react-sdk';

export function App() {
const [mediaStream, setMediaStream] = useState<MediaStream | null>(null);
const [captions, setCaptions] = useState<CallClosedCaption[]>([]);

useEffect(() => {
navigator.mediaDevices
.getUserMedia({ audio: true })
.then((ms) => setMediaStream(ms));
}, []);

return (
<>
<Aura activity="listening" height={800} mediaStream={mediaStream} />
<AiCaptions captions={captions} />
Comment on lines +18 to +19
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Example usage ☝️

<button
type="button"
onClick={() =>
setCaptions((captions) => [
{
start_time: Date.now().toString(),
text: `Caption ${captions.length + 1}`,
} as CallClosedCaption,
...captions,
])
}
>
Add caption
</button>
</>
);
}
59 changes: 59 additions & 0 deletions sample-apps/react/ai-demo/src/components/AiCaptions.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.ai-captions {
position: fixed;
bottom: 20px;
left: 0;
right: 0;
font: 14px/16px monospace;
}

.ai-captions__caption {
position: absolute;
bottom: 0;
left: 0;
right: 0;
text-align: center;
transition: 0.3s ease-in-out;
transition-property: transform, filter, opacity;
}

.ai-captions__caption:nth-child(1) {
animation: ai-caption-fly-in 0.3s ease-out;
}

@keyframes ai-caption-fly-in {
from {
transform: translateY(20px) scale(1.2);
opacity: 0;
}

to {
transform: translateY(0) scale(1);
opacity: 1;
}
}

.ai-captions__caption:nth-child(2) {
transform: translateY(-20px);
filter: blur(3px);
}

.ai-captions__caption:nth-child(3) {
transform: translateY(-40px);
filter: blur(5px);
}

.ai-captions__caption:nth-child(4) {
transform: translateY(-60px);
filter: blur(8px);
}

.ai-captions__caption:nth-child(5) {
transform: translateY(-80px);
filter: blur(13px);
}

.ai-captions__caption:nth-child(6) {
transform: translateY(-100px);
filter: blur(21px);
opacity: 0;
}
14 changes: 14 additions & 0 deletions sample-apps/react/ai-demo/src/components/AiCaptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { CallClosedCaption } from '@stream-io/video-react-sdk';
import './AiCaptions.css';

export function AiCaptions(props: { captions: CallClosedCaption[] }) {
return (
<div className="ai-captions">
{props.captions.slice(0, 6).map((caption) => (
<div key={caption.start_time} className="ai-captions__caption">
{caption.text}
</div>
))}
</div>
);
}
87 changes: 87 additions & 0 deletions sample-apps/react/ai-demo/src/components/Aura.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
.aura {
border-radius: 50%;
}

[debug] .aura {
filter: none !important;
}

.aura_alpha {
width: 100px;
height: 42.5px;
}

.aura_beta {
width: 55px;
height: 22px;
}

.aura_speaking.aura_alpha {
filter: blur(24px);
background: #0055ff;
}

.aura_speaking.aura_beta {
filter: blur(12px);
background: #1af0ff60;
}

.aura_listening {
filter: blur(30px);
}

.aura_listening.aura_alpha {
background: #f74069;
}

.aura_listening.aura_beta {
background: #f759db60;
}

.aura-container {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}

.aura-container::before {
content: '';
position: absolute;
inset: -20%;
z-index: -1;
background: radial-gradient(
ellipse 60% 50%,
#000 58%,
#f1f1f180 74%,
#fff 80%,
#fff 90%,
transparent 100%
);
transform: rotate(-30deg);
opacity: 7%;
}

.aura-container::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
background: radial-gradient(
ellipse farthest-side at top,
transparent 50%,
#ffffffb4 100%
);
opacity: 5%;
}

.aura-container__scaler {
transform: scale(var(--aura-container-factor));
transform-origin: center center;
}

.aura-volumeter {
transform: scale(var(--aura-volumeter-scale));
filter: brightness(var(--aura-volumeter-brightness));
}
Loading
Loading