diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx
index d083769..9747528 100644
--- a/src/renderer/src/App.tsx
+++ b/src/renderer/src/App.tsx
@@ -22,6 +22,7 @@ import TitleBar from './components/electron/title-bar';
import { Live2DModelProvider } from './context/live2d-model-context';
import { InputSubtitle } from './components/electron/input-subtitle';
import { ProactiveSpeakProvider } from './context/proactive-speak-context';
+import { ScreenCaptureProvider } from './context/screen-capture-context';
function App(): JSX.Element {
const [showSidebar, setShowSidebar] = useState(true);
@@ -69,66 +70,68 @@ function App(): JSX.Element {
-
-
-
-
-
-
-
-
-
-
- {mode === 'window' ? (
- <>
- {isElectron && }
-
-
- setShowSidebar(!showSidebar)}
- />
-
-
- {/* */}
-
- {/* */}
- {/* */}
+
+
+
+
+
+
+
+
+
+
+
+ {mode === 'window' ? (
+ <>
+ {isElectron && }
+
-
-
-
- >
- ) : (
- <>
-
- {mode === 'pet' && (
-
- )}
- >
- )}
-
-
-
-
-
-
-
-
-
+
+ {/* */}
+
+ {/* */}
+ {/* */}
+
+
+
+
+ >
+ ) : (
+ <>
+
+ {mode === 'pet' && (
+
+ )}
+ >
+ )}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/src/components/sidebar/bottom-tab.tsx b/src/renderer/src/components/sidebar/bottom-tab.tsx
new file mode 100644
index 0000000..c44b13e
--- /dev/null
+++ b/src/renderer/src/components/sidebar/bottom-tab.tsx
@@ -0,0 +1,37 @@
+/* eslint-disable */
+import { Tabs } from '@chakra-ui/react'
+import { FiCamera, FiMonitor } from 'react-icons/fi'
+import { sidebarStyles } from './sidebar-styles'
+import CameraPanel from './camera-panel'
+import ScreenPanel from './screen-panel'
+
+function BottomTab(): JSX.Element {
+ return (
+
+
+
+
+ Camera
+
+
+
+ Screen
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default BottomTab
diff --git a/src/renderer/src/components/sidebar/camera-panel.tsx b/src/renderer/src/components/sidebar/camera-panel.tsx
index a7f312e..8762a52 100644
--- a/src/renderer/src/components/sidebar/camera-panel.tsx
+++ b/src/renderer/src/components/sidebar/camera-panel.tsx
@@ -73,7 +73,6 @@ function CameraPanel(): JSX.Element {
return (
- Camera
{isStreaming && }
diff --git a/src/renderer/src/components/sidebar/screen-panel.tsx b/src/renderer/src/components/sidebar/screen-panel.tsx
new file mode 100644
index 0000000..d0a74b6
--- /dev/null
+++ b/src/renderer/src/components/sidebar/screen-panel.tsx
@@ -0,0 +1,113 @@
+/* eslint-disable */
+import { Box, Text } from "@chakra-ui/react";
+import { FiMonitor } from "react-icons/fi";
+import { Tooltip } from "@/components/ui/tooltip";
+import { sidebarStyles } from "./sidebar-styles";
+import { useCaptureScreen } from "@/hooks/sidebar/use-capture-screen";
+
+// Reusable components
+function ScreenIndicator() {
+ return (
+
+
+ Screen
+
+ );
+}
+
+function ScreenPlaceholder() {
+ return (
+
+
+
+ Click to start screen capture
+
+
+ );
+}
+
+function VideoStream({
+ videoRef,
+ isStreaming,
+}: {
+ videoRef: React.RefObject;
+ isStreaming: boolean;
+}) {
+ return (
+
+ );
+}
+
+function ScreenPanel(): JSX.Element {
+ const {
+ videoRef,
+ error,
+ isHovering,
+ isStreaming,
+ toggleCapture,
+ handleMouseEnter,
+ handleMouseLeave,
+ } = useCaptureScreen();
+
+ return (
+
+
+ {isStreaming && }
+
+
+
+
+ {error ? (
+
+ {error}
+
+ ) : (
+ <>
+
+ {!isStreaming && }
+ >
+ )}
+
+
+
+ );
+}
+
+export default ScreenPanel;
diff --git a/src/renderer/src/components/sidebar/sidebar-styles.tsx b/src/renderer/src/components/sidebar/sidebar-styles.tsx
index 78586f3..89d2631 100644
--- a/src/renderer/src/components/sidebar/sidebar-styles.tsx
+++ b/src/renderer/src/components/sidebar/sidebar-styles.tsx
@@ -267,4 +267,71 @@ export const sidebarStyles = {
display: 'block',
} as const,
},
+
+ screenPanel: {
+ container: {
+ width: '97%',
+ overflow: 'hidden',
+ px: 4,
+ minH: '240px',
+ },
+ header: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ mb: 4,
+ },
+ title: commonStyles.title,
+ screenContainer: {
+ ...commonStyles.panel,
+ width: '100%',
+ height: '240px',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ overflow: 'hidden',
+ transition: 'all 0.2s',
+ },
+ video: {
+ width: '100%',
+ height: '100%',
+ objectFit: 'cover' as const,
+ borderRadius: '8px',
+ display: 'block',
+ } as const,
+ },
+
+ bottomTab: {
+ container: {
+ width: '100%',
+ px: 4,
+ },
+ tabs: {
+ width: '100%',
+ bg: 'whiteAlpha.50',
+ borderRadius: 'lg',
+ p: '1',
+ },
+ list: {
+ borderBottom: 'none',
+ gap: '2',
+ },
+ trigger: {
+ color: 'whiteAlpha.700',
+ display: 'flex',
+ alignItems: 'center',
+ gap: 2,
+ px: 3,
+ py: 2,
+ borderRadius: 'md',
+ _hover: {
+ color: 'white',
+ bg: 'whiteAlpha.50',
+ },
+ _selected: {
+ color: 'white',
+ bg: 'whiteAlpha.200',
+ },
+ },
+ },
};
diff --git a/src/renderer/src/components/sidebar/sidebar.tsx b/src/renderer/src/components/sidebar/sidebar.tsx
index 4afabfc..5353365 100644
--- a/src/renderer/src/components/sidebar/sidebar.tsx
+++ b/src/renderer/src/components/sidebar/sidebar.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
import { Box, Button } from '@chakra-ui/react';
import {
FiSettings, FiClock, FiPlus, FiChevronLeft,
@@ -6,7 +7,7 @@ import { memo } from 'react';
import { sidebarStyles } from './sidebar-styles';
import SettingUI from './setting/setting-ui';
import ChatHistoryPanel from './chat-history-panel';
-import CameraPanel from './camera-panel';
+import BottomTab from './bottom-tab';
import HistoryDrawer from './history-drawer';
import { useSidebar } from '@/hooks/sidebar/use-sidebar';
@@ -68,7 +69,7 @@ const SidebarContent = memo(({ onSettingsOpen, onNewHistory }: HeaderButtonsProp
/>
-
+
));
diff --git a/src/renderer/src/context/live2d-config-context.tsx b/src/renderer/src/context/live2d-config-context.tsx
index d498852..49cc2a4 100644
--- a/src/renderer/src/context/live2d-config-context.tsx
+++ b/src/renderer/src/context/live2d-config-context.tsx
@@ -51,9 +51,6 @@ export interface ModelInfo {
/** Initial Y position shift */
initialYshift: number | string;
- /** X-axis offset coefficient */
- kXOffset?: number | string;
-
/** Idle motion group name */
idleMotionGroupName?: string;
diff --git a/src/renderer/src/context/screen-capture-context.tsx b/src/renderer/src/context/screen-capture-context.tsx
new file mode 100644
index 0000000..fdebcf8
--- /dev/null
+++ b/src/renderer/src/context/screen-capture-context.tsx
@@ -0,0 +1,63 @@
+import { createContext, useContext, useState, ReactNode } from 'react';
+
+interface ScreenCaptureContextType {
+ stream: MediaStream | null;
+ isStreaming: boolean;
+ error: string;
+ startCapture: () => Promise;
+ stopCapture: () => void;
+}
+
+const ScreenCaptureContext = createContext(undefined);
+
+export function ScreenCaptureProvider({ children }: { children: ReactNode }) {
+ const [stream, setStream] = useState(null);
+ const [isStreaming, setIsStreaming] = useState(false);
+ const [error, setError] = useState('');
+
+ const startCapture = async () => {
+ try {
+ const displayMediaOptions: DisplayMediaStreamOptions = {
+ video: true,
+ audio: false,
+ };
+ const mediaStream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
+ setStream(mediaStream);
+ setIsStreaming(true);
+ setError('');
+ } catch (err) {
+ setError('Failed to start screen capture');
+ console.error(err);
+ }
+ };
+
+ const stopCapture = () => {
+ if (stream) {
+ stream.getTracks().forEach((track) => track.stop());
+ setStream(null);
+ setIsStreaming(false);
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export const useScreenCaptureContext = () => {
+ const context = useContext(ScreenCaptureContext);
+ if (context === undefined) {
+ throw new Error('useScreenCaptureContext must be used within a ScreenCaptureProvider');
+ }
+ return context;
+};
diff --git a/src/renderer/src/hooks/sidebar/setting/use-live2d-settings.ts b/src/renderer/src/hooks/sidebar/setting/use-live2d-settings.ts
index 899cd74..1e3a677 100644
--- a/src/renderer/src/hooks/sidebar/setting/use-live2d-settings.ts
+++ b/src/renderer/src/hooks/sidebar/setting/use-live2d-settings.ts
@@ -9,7 +9,6 @@ export const useLive2dSettings = () => {
kScale: 0.000625,
initialXshift: 0,
initialYshift: 0,
- kXOffset: 1150,
emotionMap: {},
scrollToResize: true,
};
diff --git a/src/renderer/src/hooks/sidebar/use-capture-screen.ts b/src/renderer/src/hooks/sidebar/use-capture-screen.ts
new file mode 100644
index 0000000..5967c9a
--- /dev/null
+++ b/src/renderer/src/hooks/sidebar/use-capture-screen.ts
@@ -0,0 +1,36 @@
+import { useRef, useState, useEffect } from 'react';
+import { useScreenCaptureContext } from '@/context/screen-capture-context';
+
+export function useCaptureScreen() {
+ const videoRef = useRef(null);
+ const [isHovering, setIsHovering] = useState(false);
+ const { stream, isStreaming, error, startCapture, stopCapture } = useScreenCaptureContext();
+
+ const toggleCapture = () => {
+ if (isStreaming) {
+ stopCapture();
+ } else {
+ startCapture();
+ }
+ };
+
+ const handleMouseEnter = () => setIsHovering(true);
+ const handleMouseLeave = () => setIsHovering(false);
+
+ useEffect(() => {
+ if (videoRef.current) {
+ videoRef.current.srcObject = stream;
+ }
+ }, [stream]);
+
+ return {
+ videoRef,
+ error,
+ isHovering,
+ isStreaming,
+ stream,
+ toggleCapture,
+ handleMouseEnter,
+ handleMouseLeave,
+ };
+}