Compare commits
2 Commits
1f0365e716
...
3e0276d6c0
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e0276d6c0 | |||
| 4a6a6619df |
@ -84,30 +84,41 @@ export function PhoneSimulator({
|
|||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const dragOffset = useRef({ x: 0, y: 0 });
|
const dragOffset = useRef({ x: 0, y: 0 });
|
||||||
|
|
||||||
const handleDragStart = (e: React.MouseEvent) => {
|
const handleDragStart = (e: React.MouseEvent | React.TouchEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
setIsDragging(true);
|
setIsDragging(true);
|
||||||
|
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
||||||
|
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
||||||
dragOffset.current = {
|
dragOffset.current = {
|
||||||
x: e.clientX - visualizerPosition.x,
|
x: clientX - visualizerPosition.x,
|
||||||
y: e.clientY - visualizerPosition.y,
|
y: clientY - visualizerPosition.y,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragMove = (e: MouseEvent) => {
|
const handleDragMove = (e: MouseEvent | TouchEvent) => {
|
||||||
if (!isDragging || !phoneContainerRef.current || !visualizerRef.current) return;
|
if (!isDragging || !phoneContainerRef.current || !visualizerRef.current) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
const containerRect = phoneContainerRef.current.getBoundingClientRect();
|
const containerRect = phoneContainerRef.current.getBoundingClientRect();
|
||||||
const visualizerRect = visualizerRef.current.getBoundingClientRect();
|
const visualizerRect = visualizerRef.current.getBoundingClientRect();
|
||||||
|
|
||||||
let newX = e.clientX - dragOffset.current.x;
|
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
||||||
let newY = e.clientY - dragOffset.current.y;
|
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
||||||
|
|
||||||
|
let newX = clientX - dragOffset.current.x;
|
||||||
|
let newY = clientY - dragOffset.current.y;
|
||||||
|
|
||||||
// Constrain within container
|
// Constrain within container
|
||||||
const maxX = containerRect.width - visualizerRect.width;
|
const maxX = containerRect.width - visualizerRect.width;
|
||||||
const maxY = containerRect.height - visualizerRect.height;
|
const maxY = containerRect.height - visualizerRect.height;
|
||||||
const statusBarHeight = 48; // h-12 = 48px
|
// On mobile (width < 768px), status bar is hidden, so allow dragging to top (y=0)
|
||||||
|
// On desktop, keep status bar height constraint (48px)
|
||||||
|
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768;
|
||||||
|
const minY = isMobile ? 0 : 48; // statusBarHeight = 48px
|
||||||
|
|
||||||
newX = Math.max(0, Math.min(newX, maxX));
|
newX = Math.max(0, Math.min(newX, maxX));
|
||||||
newY = Math.max(statusBarHeight, Math.min(newY, maxY));
|
newY = Math.max(minY, Math.min(newY, maxY));
|
||||||
|
|
||||||
setVisualizerPosition({
|
setVisualizerPosition({
|
||||||
x: newX,
|
x: newX,
|
||||||
@ -123,10 +134,14 @@ export function PhoneSimulator({
|
|||||||
if (isDragging) {
|
if (isDragging) {
|
||||||
window.addEventListener("mouseup", handleDragEnd);
|
window.addEventListener("mouseup", handleDragEnd);
|
||||||
window.addEventListener("mousemove", handleDragMove);
|
window.addEventListener("mousemove", handleDragMove);
|
||||||
|
window.addEventListener("touchend", handleDragEnd);
|
||||||
|
window.addEventListener("touchmove", handleDragMove, { passive: false });
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("mouseup", handleDragEnd);
|
window.removeEventListener("mouseup", handleDragEnd);
|
||||||
window.removeEventListener("mousemove", handleDragMove);
|
window.removeEventListener("mousemove", handleDragMove);
|
||||||
|
window.removeEventListener("touchend", handleDragEnd);
|
||||||
|
window.removeEventListener("touchmove", handleDragMove);
|
||||||
};
|
};
|
||||||
}, [isDragging]);
|
}, [isDragging]);
|
||||||
|
|
||||||
@ -792,7 +807,7 @@ export function PhoneSimulator({
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-auto max-w-full h-full aspect-[9/19.5] max-h-full bg-black rounded-[40px] border-[12px] border-gray-900 overflow-hidden relative shadow-2xl flex flex-col shrink-0">
|
<div className="absolute inset-0 w-full h-full bg-black rounded-none border-0 overflow-hidden flex flex-col shrink-0 md:relative md:w-auto md:max-w-full md:h-full md:aspect-[9/19.5] md:max-h-full md:rounded-[40px] md:border-[12px] md:border-gray-900 md:shadow-2xl">
|
||||||
<style jsx global>{`
|
<style jsx global>{`
|
||||||
.mirror-video video {
|
.mirror-video video {
|
||||||
transform: scaleX(-1);
|
transform: scaleX(-1);
|
||||||
@ -824,7 +839,12 @@ export function PhoneSimulator({
|
|||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
{/* Status Bar */}
|
{/* Status Bar */}
|
||||||
<div className="h-12 w-full bg-black/20 backdrop-blur-sm absolute top-0 left-0 z-50 flex items-center justify-between px-6 text-white text-xs font-medium">
|
<div className="hidden md:flex w-full bg-black/20 backdrop-blur-sm absolute top-0 left-0 z-50 items-center justify-between px-6 text-white text-xs font-medium"
|
||||||
|
style={{
|
||||||
|
paddingTop: 'max(env(safe-area-inset-top, 0px), 0.5rem)',
|
||||||
|
paddingBottom: '0.75rem',
|
||||||
|
minHeight: '3rem',
|
||||||
|
}}>
|
||||||
<span>{currentTime}</span>
|
<span>{currentTime}</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<WifiIcon className="w-4 h-4" />
|
<WifiIcon className="w-4 h-4" />
|
||||||
@ -833,7 +853,10 @@ export function PhoneSimulator({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div ref={phoneContainerRef} className="flex-grow relative bg-gray-950 w-full h-full overflow-hidden">
|
<div ref={phoneContainerRef} className="flex-grow relative bg-gray-950 w-full h-full overflow-hidden"
|
||||||
|
style={{
|
||||||
|
paddingBottom: 'env(safe-area-inset-bottom, 0px)',
|
||||||
|
}}>
|
||||||
<div className={`h-full w-full transition-all duration-500 ease-in-out transform ${
|
<div className={`h-full w-full transition-all duration-500 ease-in-out transform ${
|
||||||
phoneMode === "hand_off" && roomState === ConnectionState.Connected
|
phoneMode === "hand_off" && roomState === ConnectionState.Connected
|
||||||
? "blur-md scale-105"
|
? "blur-md scale-105"
|
||||||
@ -960,12 +983,13 @@ export function PhoneSimulator({
|
|||||||
{roomState === ConnectionState.Connected && voiceAssistant.audioTrack && phoneMode !== "hand_off" && (
|
{roomState === ConnectionState.Connected && voiceAssistant.audioTrack && phoneMode !== "hand_off" && (
|
||||||
<div
|
<div
|
||||||
ref={visualizerRef}
|
ref={visualizerRef}
|
||||||
className="absolute z-50 p-2 bg-black/40 backdrop-blur-md rounded-lg border border-white/10 shadow-lg cursor-move select-none"
|
className="absolute z-50 p-2 bg-black/40 backdrop-blur-md rounded-lg border border-white/10 shadow-lg cursor-move select-none touch-none"
|
||||||
style={{
|
style={{
|
||||||
left: visualizerPosition.x,
|
left: visualizerPosition.x,
|
||||||
top: visualizerPosition.y,
|
top: visualizerPosition.y,
|
||||||
}}
|
}}
|
||||||
onMouseDown={handleDragStart}
|
onMouseDown={handleDragStart}
|
||||||
|
onTouchStart={handleDragStart}
|
||||||
>
|
>
|
||||||
<div className="h-8 w-24 flex items-center justify-center [--lk-va-bar-width:3px] [--lk-va-bar-gap:2px] [--lk-fg:white]">
|
<div className="h-8 w-24 flex items-center justify-center [--lk-va-bar-width:3px] [--lk-va-bar-gap:2px] [--lk-fg:white]">
|
||||||
<BarVisualizer
|
<BarVisualizer
|
||||||
@ -981,7 +1005,10 @@ export function PhoneSimulator({
|
|||||||
{/* Call Controls Overlay */}
|
{/* Call Controls Overlay */}
|
||||||
{roomState === ConnectionState.Connected && (
|
{roomState === ConnectionState.Connected && (
|
||||||
phoneMode === "capture" ? (
|
phoneMode === "capture" ? (
|
||||||
<div className="absolute top-0 left-0 w-full h-full flex flex-col justify-end pb-[5%] px-[8%] z-40">
|
<div className="absolute top-0 left-0 w-full h-full flex flex-col justify-end px-[8%] z-40"
|
||||||
|
style={{
|
||||||
|
paddingBottom: 'calc(5% + env(safe-area-inset-bottom, 0px))',
|
||||||
|
}}>
|
||||||
{/* Camera Controls Row */}
|
{/* Camera Controls Row */}
|
||||||
<div className="w-full flex items-center justify-evenly mb-8">
|
<div className="w-full flex items-center justify-evenly mb-8">
|
||||||
{/* Left: Upload */}
|
{/* Left: Upload */}
|
||||||
@ -1058,7 +1085,11 @@ export function PhoneSimulator({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="absolute bottom-[5%] left-0 w-full px-[8%] z-40">
|
<div className="absolute bottom-[5%] left-0 w-full px-[8%] z-40"
|
||||||
|
style={{
|
||||||
|
paddingBottom: 'max(env(safe-area-inset-bottom, 0px), 0px)',
|
||||||
|
bottom: 'calc(5% + env(safe-area-inset-bottom, 0px))',
|
||||||
|
}}>
|
||||||
<div className="w-full flex flex-col items-center justify-center gap-4">
|
<div className="w-full flex flex-col items-center justify-center gap-4">
|
||||||
{/* Mode Toggle Switch */}
|
{/* Mode Toggle Switch */}
|
||||||
{phoneMode !== "important_message" && phoneMode !== "hand_off" && voiceAssistant.agent && (
|
{phoneMode !== "important_message" && phoneMode !== "hand_off" && voiceAssistant.agent && (
|
||||||
@ -1126,7 +1157,7 @@ export function PhoneSimulator({
|
|||||||
{phoneMode !== "important_message" && (
|
{phoneMode !== "important_message" && (
|
||||||
<button
|
<button
|
||||||
ref={pushToTalkButtonRef}
|
ref={pushToTalkButtonRef}
|
||||||
className={`w-24 h-24 rounded-full backdrop-blur-md transition-all flex flex-col items-center justify-center gap-2 aspect-square ${
|
className={`w-24 h-24 rounded-full backdrop-blur-md transition-all flex flex-col items-center justify-center gap-2 aspect-square select-none ${
|
||||||
interruptRejected
|
interruptRejected
|
||||||
? "bg-red-500/70 text-white"
|
? "bg-red-500/70 text-white"
|
||||||
: isPushToTalkActive
|
: isPushToTalkActive
|
||||||
|
|||||||
@ -408,15 +408,15 @@ export default function Playground({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof document !== "undefined") {
|
if (typeof document !== "undefined") {
|
||||||
document.body.style.setProperty(
|
document.body.style.setProperty(
|
||||||
"--lk-theme-color",
|
"--lk-theme-color",
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
tailwindTheme.colors[config.settings.theme_color]["500"],
|
tailwindTheme.colors[config.settings.theme_color]["500"],
|
||||||
);
|
);
|
||||||
document.body.style.setProperty(
|
document.body.style.setProperty(
|
||||||
"--lk-drop-shadow",
|
"--lk-drop-shadow",
|
||||||
`var(--lk-theme-color) 0px 0px 18px`,
|
`var(--lk-theme-color) 0px 0px 18px`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [config.settings.theme_color]);
|
}, [config.settings.theme_color]);
|
||||||
|
|
||||||
@ -489,21 +489,21 @@ export default function Playground({
|
|||||||
|
|
||||||
const instructionsContent = (
|
const instructionsContent = (
|
||||||
<>
|
<>
|
||||||
<ConfigurationPanelItem title="Instructions">
|
<ConfigurationPanelItem title="Instructions">
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full bg-gray-950 text-white text-sm p-3 rounded-md border border-gray-800 focus:border-gray-600 focus:outline-none transition-colors resize-none disabled:opacity-50 disabled:cursor-not-allowed"
|
className="w-full bg-gray-950 text-white text-sm p-3 rounded-md border border-gray-800 focus:border-gray-600 focus:outline-none transition-colors resize-none disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
style={{ minHeight: "80px" }}
|
style={{ minHeight: "80px" }}
|
||||||
rows={3}
|
rows={3}
|
||||||
placeholder="Enter system instructions for the agent..."
|
placeholder="Enter system instructions for the agent..."
|
||||||
value={config.settings.instructions}
|
value={config.settings.instructions}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newSettings = { ...config.settings };
|
const newSettings = { ...config.settings };
|
||||||
newSettings.instructions = e.target.value;
|
newSettings.instructions = e.target.value;
|
||||||
setUserSettings(newSettings);
|
setUserSettings(newSettings);
|
||||||
}}
|
}}
|
||||||
disabled={roomState !== ConnectionState.Disconnected}
|
disabled={roomState !== ConnectionState.Disconnected}
|
||||||
/>
|
/>
|
||||||
</ConfigurationPanelItem>
|
</ConfigurationPanelItem>
|
||||||
<ConfigurationPanelItem title="Color">
|
<ConfigurationPanelItem title="Color">
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
colors={themeColors}
|
colors={themeColors}
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export const PlaygroundTile: React.FC<PlaygroundTileProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col items-center grow w-full ${childrenClassName}`}
|
className={`flex flex-col items-center grow w-full relative ${childrenClassName}`}
|
||||||
style={{
|
style={{
|
||||||
height: `calc(100% - ${title ? titleHeight + "px" : "0px"})`,
|
height: `calc(100% - ${title ? titleHeight + "px" : "0px"})`,
|
||||||
padding: `${contentPadding * 4}px`,
|
padding: `${contentPadding * 4}px`,
|
||||||
@ -74,7 +74,7 @@ export const PlaygroundTabbedTile: React.FC<PlaygroundTabbedTileProps> = ({
|
|||||||
className={`flex flex-col h-full border rounded-sm border-gray-800 text-gray-500 bg-${backgroundColor} ${className}`}
|
className={`flex flex-col h-full border rounded-sm border-gray-800 text-gray-500 bg-${backgroundColor} ${className}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="flex items-center justify-start text-xs uppercase border-b border-b-gray-800 tracking-wider"
|
className="flex items-center justify-start text-xs uppercase border-b border-b-gray-800 tracking-wider relative z-[100] bg-gray-950"
|
||||||
style={{
|
style={{
|
||||||
height: `${titleHeight}px`,
|
height: `${titleHeight}px`,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -51,3 +51,12 @@ body {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide Next.js floating dev indicator */
|
||||||
|
nextjs-portal,
|
||||||
|
#__next-build-watcher,
|
||||||
|
[data-nextjs-dialog],
|
||||||
|
[data-nextjs-toast],
|
||||||
|
div[style*="position: fixed"][style*="bottom"][style*="right"] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user