#!/bin/bash
# Create the full directory structure
mkdir -p my-tauri-app/src/chat/{components,lib,providers,styles,types}
mkdir -p my-tauri-app/src/chat/components/{chat,projector,selector,ui}
mkdir -p my-tauri-app/src/styles
# Create all files with React+HTML equivalents
cat > my-tauri-app/src/chat/components/chat/chat-header.tsx << 'EOL'
import React from 'react';
import { useChat } from '../../providers/chat-provider';
import '../../styles/chat.css';
export function ChatHeader() {
const { instance } = useChat();
return (
{instance?.name || 'Chat'}
Online
);
}
EOL
cat > my-tauri-app/src/chat/components/chat/chat-input.tsx << 'EOL'
import React from 'react';
import { EmojiPicker } from '../ui/emoji-picker';
import { useChat } from '../../providers/chat-provider';
import { useSound } from '../../providers/sound-provider';
import '../../styles/chat.css';
export function ChatInput() {
const [message, setMessage] = React.useState('');
const [showEmoji, setShowEmoji] = React.useState(false);
const { sendActivity } = useChat();
const { playSound } = useSound();
const handleSend = () => {
if (!message.trim()) return;
playSound('send');
sendActivity({
type: 'message',
text: message.trim(),
});
setMessage('');
};
const handleEmojiSelect = (emoji: string) => {
playSound('click');
setMessage(prev => prev + emoji);
};
return (
<>
{
playSound('click');
setShowEmoji(false);
}}
onEmojiSelect={handleEmojiSelect}
/>
>
);
}
EOL
cat > my-tauri-app/src/chat/components/chat/chat-window.tsx << 'EOL'
import React from 'react';
import { MessageList } from './message-list';
import { ChatInput } from './chat-input';
import { ChatHeader } from './chat-header';
import { useChat } from '../../providers/chat-provider';
import { Message } from '../../types';
import '../../styles/chat.css';
export function ChatWindow() {
const { line } = useChat();
const [messages, setMessages] = React.useState([]);
React.useEffect(() => {
if (!line) return;
const subscription = line.activity$.subscribe((activity: any) => {
if (activity.type === 'message') {
setMessages(prev => [...prev, activity as Message]);
}
});
return () => subscription.unsubscribe();
}, [line]);
return (
);
}
EOL
cat > my-tauri-app/src/chat/components/chat/message-list.tsx << 'EOL'
import React from 'react';
import { useChat } from '../../providers/chat-provider';
import { useSound } from '../../providers/sound-provider';
import { Message } from '../../types';
import '../../styles/chat.css';
interface MessageListProps {
messages: Message[];
}
export function MessageList({ messages }: MessageListProps) {
const scrollRef = React.useRef(null);
const { user } = useChat();
const { playSound } = useSound();
const prevMessagesLength = React.useRef(messages.length);
React.useEffect(() => {
if (messages.length > prevMessagesLength.current) {
const lastMessage = messages[messages.length - 1];
if (lastMessage.from.id !== user.id) {
playSound('receive');
}
scrollRef.current?.scrollIntoView({ behavior: 'smooth' });
}
prevMessagesLength.current = messages.length;
}, [messages]);
return (
{messages.map((message, index) => (
{message.text}
{new Date(message.timestamp).toLocaleTimeString()}
))}
);
}
EOL
cat > my-tauri-app/src/chat/components/chat-layout.tsx << 'EOL'
import React from 'react';
import { PersonSelector } from './selector/person-selector';
import { ProjectorView } from './projector/projector-view';
import { ChatWindow } from './chat/chat-window';
import '../../styles/layout.css';
export function ChatLayout() {
return (
);
}
EOL
cat > my-tauri-app/src/chat/components/projector/image-viewer.tsx << 'EOL'
import React from 'react';
import '../../styles/projector.css';
interface ImageViewerProps {
url: string;
}
export function ImageViewer({ url }: ImageViewerProps) {
return (
);
}
EOL
cat > my-tauri-app/src/chat/components/projector/markdown-viewer.tsx << 'EOL'
import React from 'react';
import ReactMarkdown from 'react-markdown';
import '../../styles/projector.css';
interface MarkdownViewerProps {
content: string;
}
export function MarkdownViewer({ content }: MarkdownViewerProps) {
return (
{content}
);
}
EOL
cat > my-tauri-app/src/chat/components/projector/projector-view.tsx << 'EOL'
import React from 'react';
import { VideoPlayer } from './video-player';
import { ImageViewer } from './image-viewer';
import { MarkdownViewer } from './markdown-viewer';
import { useChat } from '../../providers/chat-provider';
import '../../styles/projector.css';
export function ProjectorView() {
const { line } = useChat();
const [content, setContent] = React.useState(null);
React.useEffect(() => {
if (!line) return;
const subscription = line.activity$
.subscribe((activity: any) => {
if (activity.type === 'event' && activity.name === 'project') {
setContent(activity.value);
}
});
return () => subscription.unsubscribe();
}, [line]);
const renderContent = () => {
if (!content) return null;
switch (content.type) {
case 'video':
return ;
case 'image':
return ;
case 'markdown':
return ;
default:
return null;
}
};
return (
{renderContent()}
);
}
EOL
cat > my-tauri-app/src/chat/components/projector/video-player.tsx << 'EOL'
import React from 'react';
import '../../styles/projector.css';
interface VideoPlayerProps {
url: string;
}
export function VideoPlayer({ url }: VideoPlayerProps) {
return (
);
}
EOL
cat > my-tauri-app/src/chat/components/selector/person-selector.tsx << 'EOL'
import React from 'react';
import { useChat } from '../../providers/chat-provider';
import '../../styles/selector.css';
export function PersonSelector() {
const [search, setSearch] = React.useState('');
const { instance } = useChat();
return (
{instance?.logo && (

)}
{['FAQ', 'Support', 'Sales'].map((item) => (
{item[0]}
{item}
Start a conversation
))}
);
}
EOL
cat > my-tauri-app/src/chat/components/sound-initializer.tsx << 'EOL'
import React, { useEffect, useState } from 'react';
import { soundAssets } from '../../../public/sounds/manifest';
import { cacheAssets } from '../lib/asset-loader';
export function SoundInitializer({ children }: { children: React.ReactNode }) {
const [isReady, setIsReady] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const initializeSounds = async () => {
try {
await cacheAssets(Object.values(soundAssets));
setIsReady(true);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to initialize sounds');
}
};
initializeSounds();
}, []);
if (error) {
return (
);
}
if (!isReady) {
return (
);
}
return <>{children}>;
}
EOL
cat > my-tauri-app/src/chat/components/ui/emoji-picker.tsx << 'EOL'
import React from 'react';
import '../../styles/ui.css';
const EMOJI_CATEGORIES = {
"đ đŽ": ["đ", "đ", "đ¤", "đž", "đŽ", "â¨", "đ", "đĢ"],
"đ đĢ": ["â", "đ", "đĢ", "â¨", "âĄ", "đĨ", "đĨ", "đ"],
"đ¤ đ¯": ["đ¤", "đ¯", "đ˛", "đŽ", "đšī¸", "đž", "đģ", "â¨ī¸"]
};
export function EmojiPicker({ visible, onClose, onEmojiSelect }: {
visible: boolean;
onClose: () => void;
onEmojiSelect: (emoji: string) => void;
}) {
if (!visible) return null;
return (
{Object.entries(EMOJI_CATEGORIES).map(([category, emojis]) => (
{category}
{emojis.map(emoji => (
))}
))}
);
}
EOL
cat > my-tauri-app/src/chat/components/ui/settings-panel.tsx << 'EOL'
import React from 'react';
import { useSound } from '../../providers/sound-provider';
import '../../styles/ui.css';
export function SettingsPanel() {
const [theme, setTheme] = React.useState('dark');
const [sound, setSound] = React.useState(true);
const [powerMode, setPowerMode] = React.useState(false);
const { setEnabled, playSound } = useSound();
const handleSoundToggle = (value: boolean) => {
setSound(value);
setEnabled(value);
if (value) {
playSound('success');
}
};
const handleThemeChange = () => {
playSound('click');
setTheme(theme === 'dark' ? 'light' : 'dark');
};
const handlePowerMode = (value: boolean) => {
playSound(value ? 'success' : 'click');
setPowerMode(value);
};
return (
{sound ? (
) : (
)}
Sound Effects
);
}
EOL
cat > my-tauri-app/src/chat/index.tsx << 'EOL'
import React from 'react';
import { ChatProvider } from './providers/chat-provider';
import { ChatLayout } from './components/chat-layout';
import { SoundInitializer } from './components/sound-initializer';
import { SoundProvider } from './providers/sound-provider';
export function Chat() {
return (
);
}
EOL
cat > my-tauri-app/src/chat/lib/utils.ts << 'EOL'
export function formatTimestamp(date: Date): string {
return new Intl.DateTimeFormat('en-US', {
hour: '2-digit',
minute: '2-digit',
}).format(date);
}
export function cn(...classes: string[]): string {
return classes.filter(Boolean).join(' ');
}
export function generateId(): string {
return Math.random().toString(36).slice(2);
}
EOL
cat > my-tauri-app/src/chat/providers/chat-provider.tsx << 'EOL'
import React, { createContext, useContext, useState, useEffect } from 'react';
import { invoke } from '@tauri-apps/api/tauri';
import { User, ChatInstance } from '../types';
interface ChatContextType {
line: any;
user: User;
instance: ChatInstance | null;
sendActivity: (activity: any) => void;
selectedVoice: any;
setVoice: (voice: any) => void;
}
const ChatContext = createContext(undefined);
export function ChatProvider({ children }: { children: React.ReactNode }) {
const [line, setLine] = useState(null);
const [instance, setInstance] = useState(null);
const [selectedVoice, setSelectedVoice] = useState(null);
const [user] = useState({
id: `user_${Math.random().toString(36).slice(2)}`,
name: 'You'
});
useEffect(() => {
const initializeChat = async () => {
try {
const botId = window.location.pathname.split('/')[1] || 'default';
const instanceData = await invoke('get_chat_instance', { botId });
setInstance(instanceData as ChatInstance);
// Initialize DirectLine or other chat service
const directLine = {
activity$: { subscribe: () => {} },
postActivity: () => ({ subscribe: () => {} })
};
setLine(directLine);
} catch (error) {
console.error('Failed to initialize chat:', error);
}
};
initializeChat();
}, []);
const sendActivity = async (activity: any) => {
try {
await invoke('send_chat_activity', {
activity: {
...activity,
from: user,
timestamp: new Date().toISOString()
}
});
line?.postActivity(activity).subscribe();
} catch (error) {
console.error('Failed to send activity:', error);
}
};
const setVoice = (voice: any) => {
setSelectedVoice(voice);
};
return (
{children}
);
}
export function useChat() {
const context = useContext(ChatContext);
if (!context) {
throw new Error('useChat must be used within ChatProvider');
}
return context;
}
EOL
cat > my-tauri-app/src/chat/providers/sound-provider.tsx << 'EOL'
import React, { createContext, useContext, useCallback } from 'react';
import { invoke } from '@tauri-apps/api/tauri';
interface SoundContextType {
playSound: (sound: string) => void;
setEnabled: (enabled: boolean) => void;
}
const SoundContext = createContext(undefined);
export function SoundProvider({ children }: { children: React.ReactNode }) {
const [enabled, setEnabled] = React.useState(true);
const playSound = useCallback(async (sound: string) => {
if (!enabled) return;
try {
await invoke('play_sound', { sound });
} catch (error) {
console.error('Failed to play sound:', error);
}
}, [enabled]);
return (
{children}
);
}
export function useSound() {
const context = React.useContext(SoundContext);
if (!context) {
throw new Error('useSound must be used within SoundProvider');
}
return context;
}
EOL
cat > my-tauri-app/src/chat/types/index.ts << 'EOL'
export interface Message {
id: string;
type: 'message' | 'event';
text?: string;
timestamp: string;
from: User;
attachments?: Attachment[];
}
export interface User {
id: string;
name: string;
avatar?: string;
}
export interface Attachment {
type: 'image' | 'video' | 'document';
url: string;
thumbnailUrl?: string;
}
export interface ChatInstance {
id: string;
name: string;
logo?: string;
botId: string;
webchatToken: string;
}
EOL
# Create CSS files
cat > my-tauri-app/src/styles/chat.css << 'EOL'
.chat-window {
display: flex;
flex-direction: column;
height: 100%;
background-color: #111;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: #111;
border-bottom: 1px solid #333;
}
.header-content {
display: flex;
flex-direction: column;
}
.header-title {
color: white;
font-size: 1.25rem;
font-weight: bold;
margin: 0;
}
.header-subtitle {
color: #00f3ff;
font-size: 0.875rem;
margin-top: 0.25rem;
}
.message-list {
flex: 1;
overflow-y: auto;
padding: 1rem;
}
.message-container {
max-width: 70%;
margin-bottom: 0.5rem;
padding: 0.75rem;
border-radius: 0.75rem;
}
.user-message {
align-self: flex-end;
background-color: rgba(0, 243, 255, 0.1);
border: 1px solid #00f3ff;
}
.bot-message {
align-self: flex-start;
background-color: rgba(191, 0, 255, 0.1);
border: 1px solid #bf00ff;
}
.message-text {
color: white;
margin: 0;
}
.message-time {
color: #666;
font-size: 0.75rem;
margin-top: 0.25rem;
}
.input-container {
display: flex;
align-items: center;
padding: 1rem;
border-top: 1px solid #333;
background-color: #111;
}
.chat-input {
flex: 1;
min-height: 2.5rem;
max-height: 6rem;
padding: 0.5rem 1rem;
margin: 0 0.5rem;
background-color: #1a1a1a;
color: white;
border: 1px solid #333;
border-radius: 1.25rem;
resize: none;
}
.icon-button {
background: none;
border: none;
color: #00f3ff;
cursor: pointer;
padding: 0.5rem;
}
.send-button {
background-color: rgba(0, 243, 255, 0.1);
border: 1px solid #00f3ff;
border-radius: 50%;
padding: 0.5rem;
cursor: pointer;
}
.icon {
width: 1.5rem;
height: 1.5rem;
fill: currentColor;
}
EOL
cat > my-tauri-app/src/styles/layout.css << 'EOL'
.chat-layout {
display: flex;
height: 100vh;
background-color: #111;
}
.sidebar {
width: 18rem;
border-right: 1px solid #333;
}
.main-content {
flex: 1;
display: flex;
}
.projector {
width: 40%;
border-right: 1px solid #333;
}
.chat-area {
flex: 1;
}
EOL
cat > my-tauri-app/src/styles/projector.css << 'EOL'
.projector-container {
height: 100%;
padding: 1rem;
background-color: #111;
}
.image-container, .video-container {
width: 100%;
height: 100%;
background-color: #111;
border-radius: 0.5rem;
overflow: hidden;
}
.projector-image, .projector-video {
width: 100%;
height: 100%;
object-fit: contain;
}
.markdown-container {
height: 100%;
padding: 1rem;
color: white;
background-color: #111;
border-radius: 0.5rem;
overflow-y: auto;
}
.markdown-container h1,
.markdown-container h2,
.markdown-container h3 {
color: #00f3ff;
}
.markdown-container a {
color: #00f3ff;
text-decoration: none;
}
.markdown-container pre {
background-color: #1a1a1a;
padding: 1rem;
border-radius: 0.25rem;
}
EOL
cat > my-tauri-app/src/styles/selector.css << 'EOL'
.selector-container {
height: 100%;
background-color: #111;
}
.selector-header {
padding: 1rem;
border-bottom: 1px solid #333;
}
.selector-logo {
height: 2.5rem;
}
.search-container {
display: flex;
align-items: center;
padding: 0.75rem;
border-bottom: 1px solid #333;
}
.search-icon {
width: 1.25rem;
height: 1.25rem;
margin-right: 0.5rem;
color: #00f3ff;
}
.search-input {
flex: 1;
background: none;
border: none;
color: white;
font-size: 1rem;
}
.search-input::placeholder {
color: #666;
}
.selector-list {
height: calc(100% - 7rem);
overflow-y: auto;
}
.selector-item {
display: flex;
padding: 1rem;
border-bottom: 1px solid #333;
cursor: pointer;
}
.selector-item:hover {
background-color: #1a1a1a;
}
.selector-avatar {
width: 3rem;
height: 3rem;
border-radius: 50%;
background-color: #1a1a1a;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #00f3ff;
color: #00f3ff;
font-size: 1.25rem;
font-weight: bold;
}
.item-content {
margin-left: 1rem;
flex: 1;
}
.item-title {
color: white;
font-size: 1rem;
font-weight: bold;
margin: 0;
}
.item-subtitle {
color: #666;
font-size: 0.875rem;
margin-top: 0.25rem;
}
EOL
cat > my-tauri-app/src/styles/ui.css << 'EOL'
.emoji-picker-modal {
position: fixed;
bottom: 5rem;
right: 2rem;
width: 20rem;
max-height: 25rem;
background-color: rgba(0, 0, 0, 0.95);
border: 1px solid #00f3ff;
border-radius: 0.75rem;
box-shadow: 0 0 1rem rgba(0, 243, 255, 0.5);
z-index: 1000;
overflow: hidden;
}
.emoji-picker-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid rgba(0, 243, 255, 0.2);
}
.emoji-picker-header h3 {
color: #00f3ff;
margin: 0;
}
.close-button {
background: none;
border: none;
color: #00f3ff;
cursor: pointer;
}
.emoji-picker-content {
padding: 1rem;
overflow-y: auto;
max-height: calc(25rem - 3.5rem);
}
.emoji-category {
margin-bottom: 1.5rem;
}
.category-title {
color: #bf00ff;
font-size: 0.875rem;
margin-bottom: 0.75rem;
}
.emoji-grid {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 0.5rem;
}
.emoji-button {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
padding: 0.25rem;
border-radius: 0.25rem;
}
.emoji-button:hover {
background-color: rgba(0, 243, 255, 0.1);
}
.settings-panel {
background-color: #111;
padding: 1rem;
border-radius: 0.5rem;
border: 1px solid #333;
}
.settings-option {
display: flex;
align-items: center;
padding: 0.75rem;
margin-bottom: 0.5rem;
background-color: #1a1a1a;
border-radius: 0.5rem;
border: 1px solid #333;
}
.option-text {
flex: 1;
color: white;
margin-left: 0.75rem;
}
.switch {
position: relative;
display: inline-block;
width: 3rem;
height: 1.5rem;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #333;
transition: .4s;
border-radius: 1.5rem;
}
.slider:before {
position: absolute;
content: "";
height: 1.1rem;
width: 1.1rem;
left: 0.2rem;
bottom: 0.2rem;
background-color: #666;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: rgba(0, 243, 255, 0.2);
}
input:checked + .slider:before {
transform: translateX(1.5rem);
background-color: #00f3ff;
}
EOL
echo "All files have been created with React + Tauri compatible versions!"