quantumkv commited on
Commit
e3a84af
·
verified ·
1 Parent(s): ff9a51c

Hi Design Team,

Browse files

I need to provide extremely comprehensive structural specifications for our upcoming Claude AI chat interface. This document covers every functional element, user flow, and technical requirement—please treat this as your primary blueprint for what to design (not how it should look).
1.0 PROJECT ARCHITECTURE OVERVIEW
We're building a single-page chat application that operates entirely client-side. The app will be delivered as one HTML file containing embedded CSS and JavaScript, with zero external dependencies except the Puter.js script tag. Users access multiple Claude AI models without API keys, authentication, or backend servers.
Core User Journey:
Land on page → See empty chat with onboarding message
Select AI model → Choose from 5 Claude variants
Input prompt → Type or click example button
Choose interaction mode → Standard (instant) or Stream (real-time)
Receive response → View formatted message in conversation thread
Continue conversation → Build context-aware chat history
2.0 HEADER SECTION (Fixed Position)
2.1 Left Zone: Application Identity
App Name: "Claude AI Chat" (text, not logo)
Tagline/Subtitle: "Powered by Puter.js" (optional)
Function: Clicking app name resets conversation (clear history)
2.2 Right Zone: Model Selection
Component: Dropdown selector (default: "claude-sonnet-4-5")
Options:
claude-sonnet-4-5 (label: "Claude Sonnet 4.5")
claude-sonnet-4 (label: "Claude Sonnet 4")
claude-opus-4-1 (label: "Claude Opus 4.1")
claude-opus-4 (label: "Claude Opus 4")
claude-haiku-4-5 (label: "Claude Haiku 4.5")
Behavior:
Changing model mid-conversation appends system message: "Model changed to [Model Name]"
Dropdown disabled during active requests
On mobile: Convert to bottom sheet or full-screen selector
2.3 Status Indicator Area
Location: Top-right corner, overlaying content
States:
Hidden (default)
Success: Green bar, auto-dismiss after 3 seconds
Error: Red bar, auto-dismiss after 5 seconds, contains "Retry" button
Loading: Persistent spinner with text "Connecting..."
3.0 MAIN CHAT CONTAINER (Scrollable Region)
3.1 Message Thread Structure
Container: Flex column, reverse chronological order (newest at bottom)
Scroll Behavior: Auto-scroll to bottom on new messages, unless user manually scrolled up
Message Components (per message):
Header: Role label ("You" or "Assistant") + timestamp (optional)
Body: Text content with markdown support
Metadata: Character count (optional, expandable)
Message Types:
User Message: Right-aligned, contains only plain text
Assistant Message: Left-aligned, contains:
Plain text
Code blocks (monospaced, syntax highlighting)
Inline code (backticks)
Lists (bulleted/numbered)
Links (clickable, open new tab)
3.2 Typing Indicator
Trigger: Appears immediately after user sends message
Structure: Three animated dots + "Claude is typing..." text
Position: Above assistant message, disappears when response starts
Stream Mode: Hidden, response appears directly
3.3 Empty State
Condition: Zero messages in thread
Content: Welcome message from assistant explaining capabilities
Actions: Prompt user to select example or type message
3.4 Message History Management
Persistence: Store last 50 messages in localStorage
Page Refresh: Reload history from localStorage
Reset: "New Chat" button clears history and container
Overflow: Auto-prune messages older than 50, show "Load older messages" button
4.0 EXAMPLE PROMPTS SECTION (Dynamic)
4.1 Container
Location: Between header and chat container
Visibility: Always visible until first user message, then collapsible
Structure: Horizontal flex wrap of button elements
4.2 Button Specifications
Default Prompts:
"Explain quantum computing in simple terms"
"Write a detailed essay on AI impact"
"Write a short poem about coding"
"Generate a JavaScript function to sort an array"
Behavior: Clicking injects text into textarea and focuses input
Customization: Allow users to edit prompt before sending
Dynamic Addition: Future feature: users can add custom example prompts
4.3 Responsive Behavior
Desktop: 4 buttons in row
Tablet: 2x2 grid
Mobile: Vertical stack, full-width buttons
5.0 INPUT AREA (Fixed Bottom)
5.1 Text Area Component
Type: Multi-line textarea (not single-line input)
Auto-resize: Grows vertically up to 150px max-height
Placeholder: "Type your message here... (Shift+Enter for new line)"
Keyboard Shortcuts:
Enter → Send message
Shift + Enter → New line
Cmd/Ctrl + K → Clear input
Cmd/Ctrl + N → New chat
Character Limit: 10,000 characters (show counter when >80%)
Disabled States: During API requests, show "Waiting for response..."
Paste Behavior: Accept text, code, and markdown; strip images/files
5.2 Button Group
Primary Button (Left): "Send"
Sends standard request, waits for complete response
Disabled when textarea empty or during active request
Icon: Paper plane or arrow right
Secondary Button (Right): "Stream"
Sends streaming request, shows real-time text generation
Best for long responses (essays, code generation)
Disabled when textarea empty or during active request
Icon: Lightning bolt or pulse waves
Tertiary Button (Optional): "New Chat"
Resets conversation, clears history
Place in header or as floating action button
5.3 Input Validation
Empty Input: Disable buttons, show tooltip on click attempt
Rate Limiting: Queue messages if user spams, show "Please wait..." message
Network Error: If Puter.js fails to load, show persistent error bar: "Failed to connect to AI service. Refresh page."
6.0 INTERACTION FLOWS (Detailed)
6.1 Standard Message Flow
User types message → Clicks "Send"
Input locked, buttons disabled, typing indicator appears
API request sent via puter.ai.chat() (non-streaming)
On success:
Typing indicator hidden
Assistant message rendered in full
Message saved to history
Input unlocked
On error:
Show error status: "Request failed. [Error message]"
Input unlocked, message preserved for retry
6.2 Streaming Message Flow
User types message → Clicks "Stream"
Input locked, buttons disabled, empty assistant message appears
API request sent with {stream: true} parameter
Response chunks arrive → Append to message in real-time
On completion: Input unlocked, message saved
On error: Show error bar, preserve partial response with "Streaming interrupted"
6.3 Model Switching Flow
User opens dropdown → Selects new model
If conversation empty: Silent switch
If conversation active:
Append system message to chat: "Model changed to [New Model] — previous context retained"
Dropdown shows checkmark briefly
Selected model becomes default for new chats
6.4 Error Recovery Flow
Network Error: Show "Retry" button in error status, clicking resends last message
Rate Limit: Show "Too many requests. Wait 30 seconds." with countdown
Model Unavailable: Auto-suggest next best model, show prompt: "[Model] unavailable. Use [Alternative] instead?"
7.0 TECHNICAL INTEGRATION POINTS
7.1 Puter.js API Calls
Host Script: <script src="https://js.puter.com/v2/"></script> in <head>
Standard Call: puter.ai.chat(message, {model: selectedModel})
Streaming Call: puter.ai.chat(message, {model: selectedModel, stream: true})
Response Handling:
Standard: response.message.content[0].text
Streaming: Async iterator for await (const part of response)
7.2 State Management
Global Variables:
conversationHistory (array of message objects)
selectedModel (string)
isProcessing (boolean)
isStreaming (boolean)
localStorage Keys:
claudeChat_history (JSON string)
claudeChat_selectedModel (string)
claudeChat_timestamp (for cache invalidation)
7.3 Performance Requirements
First Paint: < 1 second
Time to Interactive: < 2 seconds (including Puter.js load)
Message Render: < 50ms for new message insertion
Stream Latency: < 100ms between chunks
Memory: < 50MB total footprint
8.0 RESPONSIVE BREAKPOINTS & ADAPTIVE BEHAVIOR
8.1 Desktop (≥1024px)
Three-column layout: Header, Examples, Chat, Input
Chat container max-width: 1200px, centered
Buttons side-by-side
Model selector as dropdown
8.2 Tablet (768px – 1023px)
Two-column layout: Header full-width, Examples 2x2 grid, Chat, Input
Chat container: 90% width
Buttons slightly larger touch targets (44px min)
8.3 Mobile (≤767px)
Single-column stacked layout
Header: App name and model selector on same line, smaller font
Examples: Vertical stack, full-width buttons
Chat: Full-width, 1rem padding
Input: Textarea + buttons in flex column, buttons full-width stacked
Mobile-Specific: Add floating "Scroll to bottom" button when user scrolls up
9.0 ACCESSIBILITY (WCAG 2.1 AA Compliance)
Keyboard Navigation: All interactive elements reachable via Tab
Screen Readers: ARIA labels for dynamic content (role="status" for messages)
Focus Management: Return focus to textarea after sending
Color Contrast: Minimum 4.5:1 ratio for all text
Motion: Respect prefers-reduced-motion (disable animations)
Alt Text: Icons must have aria-hidden or descriptive labels
10.0 FUTURE-PROOFING STRUCTURE
Design components to support upcoming features:
**File Upload:**预留 drag-drop zone above textarea
Multi-turn Conversations: Message threading indentation
User Accounts: Login button space in header
Settings Panel: Gear icon in header → modal/sheet
Export Chat: Download button per message or entire thread
Voice Input: Mic button next to textarea
Please design the interface structure based on these specifications. Focus on layout, component hierarchy, interaction patterns, and responsive behavior. Visual styling (colors, fonts, effects) is your creative domain—just ensure the underlying UX skeleton matches this document exactly.

Files changed (4) hide show
  1. README.md +7 -4
  2. index.html +122 -19
  3. script.js +397 -0
  4. style.css +71 -18
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Claude Ai Chat
3
- emoji: 📊
4
- colorFrom: pink
5
  colorTo: blue
 
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: Claude AI Chat 🤖
3
+ colorFrom: purple
 
4
  colorTo: blue
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://huggingface.co/deepsite).
index.html CHANGED
@@ -1,19 +1,122 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Claude AI Chat</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://unpkg.com/feather-icons"></script>
10
+ <script src="https://js.puter.com/v2/"></script>
11
+ </head>
12
+ <body class="bg-gray-50 h-screen flex flex-col">
13
+ <!-- Header -->
14
+ <header class="bg-white shadow-sm py-4 px-6 flex justify-between items-center z-10">
15
+ <div class="flex items-center space-x-2">
16
+ <h1 id="app-title" class="text-xl font-bold text-gray-800 cursor-pointer">Claude AI Chat</h1>
17
+ <span class="text-xs text-gray-500 hidden sm:block">Powered by Puter.js</span>
18
+ </div>
19
+ <div class="flex items-center space-x-4">
20
+ <div class="relative">
21
+ <select id="model-selector" class="appearance-none bg-white border border-gray-300 rounded-md py-2 pl-3 pr-10 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
22
+ <option value="claude-sonnet-4-5">Claude Sonnet 4.5</option>
23
+ <option value="claude-sonnet-4">Claude Sonnet 4</option>
24
+ <option value="claude-opus-4-1">Claude Opus 4.1</option>
25
+ <option value="claude-opus-4">Claude Opus 4</option>
26
+ <option value="claude-haiku-4-5">Claude Haiku 4.5</option>
27
+ </select>
28
+ <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
29
+ <i data-feather="chevron-down" class="w-4 h-4"></i>
30
+ </div>
31
+ </div>
32
+ <button id="new-chat-btn" class="p-2 rounded-md hover:bg-gray-100">
33
+ <i data-feather="plus-circle" class="w-5 h-5 text-gray-600"></i>
34
+ </button>
35
+ </div>
36
+ </header>
37
+
38
+ <!-- Status Indicator -->
39
+ <div id="status-bar" class="hidden px-6 py-3 text-sm text-white">
40
+ <div class="flex items-center justify-between">
41
+ <span id="status-message"></span>
42
+ <button id="retry-btn" class="hidden underline">Retry</button>
43
+ </div>
44
+ </div>
45
+
46
+ <!-- Example Prompts -->
47
+ <section id="example-prompts" class="py-4 px-6 bg-white border-b">
48
+ <div class="max-w-4xl mx-auto grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
49
+ <button class="example-btn bg-blue-50 hover:bg-blue-100 text-blue-700 py-3 px-4 rounded-lg text-sm font-medium transition-colors">Explain quantum computing in simple terms</button>
50
+ <button class="example-btn bg-purple-50 hover:bg-purple-100 text-purple-700 py-3 px-4 rounded-lg text-sm font-medium transition-colors">Write a detailed essay on AI impact</button>
51
+ <button class="example-btn bg-green-50 hover:bg-green-100 text-green-700 py-3 px-4 rounded-lg text-sm font-medium transition-colors">Write a short poem about coding</button>
52
+ <button class="example-btn bg-yellow-50 hover:bg-yellow-100 text-yellow-700 py-3 px-4 rounded-lg text-sm font-medium transition-colors">Generate a JavaScript function to sort an array</button>
53
+ </div>
54
+ </section>
55
+
56
+ <!-- Chat Container -->
57
+ <main id="chat-container" class="flex-1 overflow-y-auto p-6 pb-24">
58
+ <div id="messages" class="max-w-4xl mx-auto space-y-6">
59
+ <!-- Messages will be inserted here -->
60
+ <div class="assistant-message bg-white p-4 rounded-lg shadow-sm">
61
+ <div class="flex items-center mb-2">
62
+ <div class="w-6 h-6 rounded-full bg-purple-500 flex items-center justify-center mr-2">
63
+ <span class="text-white text-xs">A</span>
64
+ </div>
65
+ <span class="font-semibold text-gray-700">Assistant</span>
66
+ </div>
67
+ <div class="text-gray-700">
68
+ <p>Hello! I'm Claude, an AI assistant. How can I help you today?</p>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </main>
73
+
74
+ <!-- Typing Indicator -->
75
+ <div id="typing-indicator" class="hidden px-6 py-3">
76
+ <div class="max-w-4xl mx-auto flex items-center">
77
+ <div class="bg-gray-100 rounded-lg p-4">
78
+ <div class="flex items-center">
79
+ <div class="flex space-x-1">
80
+ <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
81
+ <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.2s;"></div>
82
+ <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.4s;"></div>
83
+ </div>
84
+ <span class="ml-2 text-gray-500">Claude is typing...</span>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ <!-- Input Area -->
91
+ <footer class="bg-white border-t py-4 px-6 fixed bottom-0 w-full">
92
+ <div class="max-w-4xl mx-auto">
93
+ <div class="flex flex-col sm:flex-row gap-3">
94
+ <textarea
95
+ id="message-input"
96
+ class="flex-1 border border-gray-300 rounded-lg py-3 px-4 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
97
+ placeholder="Type your message here... (Shift+Enter for new line)"
98
+ rows="1"
99
+ ></textarea>
100
+ <div class="flex gap-2">
101
+ <button id="stream-btn" class="bg-purple-600 hover:bg-purple-700 text-white py-3 px-5 rounded-lg font-medium flex items-center disabled:opacity-50">
102
+ <i data-feather="zap" class="w-4 h-4 mr-2"></i> Stream
103
+ </button>
104
+ <button id="send-btn" class="bg-blue-600 hover:bg-blue-700 text-white py-3 px-5 rounded-lg font-medium flex items-center disabled:opacity-50">
105
+ <i data-feather="send" class="w-4 h-4 mr-2"></i> Send
106
+ </button>
107
+ </div>
108
+ </div>
109
+ <div class="flex justify-between mt-2 text-xs text-gray-500">
110
+ <span id="char-count"></span>
111
+ <span>Press Enter to send, Shift+Enter for new line</span>
112
+ </div>
113
+ </div>
114
+ </footer>
115
+
116
+ <script src="script.js"></script>
117
+ <script>
118
+ feather.replace();
119
+ </script>
120
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
121
+ </body>
122
+ </html>
script.js ADDED
@@ -0,0 +1,397 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Global state
2
+ let conversationHistory = JSON.parse(localStorage.getItem('claudeChat_history')) || [];
3
+ let selectedModel = localStorage.getItem('claudeChat_selectedModel') || 'claude-sonnet-4-5';
4
+ let isProcessing = false;
5
+ let isStreaming = false;
6
+
7
+ // DOM Elements
8
+ const modelSelector = document.getElementById('model-selector');
9
+ const messageInput = document.getElementById('message-input');
10
+ const sendBtn = document.getElementById('send-btn');
11
+ const streamBtn = document.getElementById('stream-btn');
12
+ const chatContainer = document.getElementById('chat-container');
13
+ const messagesContainer = document.getElementById('messages');
14
+ const examplePrompts = document.getElementById('example-prompts');
15
+ const exampleButtons = document.querySelectorAll('.example-btn');
16
+ const statusBar = document.getElementById('status-bar');
17
+ const statusMessage = document.getElementById('status-message');
18
+ const retryBtn = document.getElementById('retry-btn');
19
+ const typingIndicator = document.getElementById('typing-indicator');
20
+ const charCount = document.getElementById('char-count');
21
+ const newChatBtn = document.getElementById('new-chat-btn');
22
+ const appTitle = document.getElementById('app-title');
23
+
24
+ // Initialize
25
+ document.addEventListener('DOMContentLoaded', () => {
26
+ // Set initial model
27
+ modelSelector.value = selectedModel;
28
+
29
+ // Load conversation history
30
+ renderConversationHistory();
31
+
32
+ // Set up event listeners
33
+ setupEventListeners();
34
+
35
+ // Update character count
36
+ updateCharCount();
37
+ });
38
+
39
+ // Set up event listeners
40
+ function setupEventListeners() {
41
+ // Model selection
42
+ modelSelector.addEventListener('change', handleModelChange);
43
+
44
+ // Message input
45
+ messageInput.addEventListener('input', updateCharCount);
46
+ messageInput.addEventListener('keydown', handleKeyDown);
47
+
48
+ // Send buttons
49
+ sendBtn.addEventListener('click', () => sendMessage(false));
50
+ streamBtn.addEventListener('click', () => sendMessage(true));
51
+
52
+ // Example prompts
53
+ exampleButtons.forEach(button => {
54
+ button.addEventListener('click', () => {
55
+ messageInput.value = button.textContent;
56
+ messageInput.focus();
57
+ updateCharCount();
58
+ });
59
+ });
60
+
61
+ // Status bar
62
+ retryBtn.addEventListener('click', retryLastMessage);
63
+
64
+ // New chat
65
+ newChatBtn.addEventListener('click', resetConversation);
66
+ appTitle.addEventListener('click', resetConversation);
67
+
68
+ // Auto-scroll
69
+ chatContainer.addEventListener('scroll', handleScroll);
70
+ }
71
+
72
+ // Handle model change
73
+ function handleModelChange() {
74
+ const newModel = modelSelector.value;
75
+ const previousModel = selectedModel;
76
+ selectedModel = newModel;
77
+ localStorage.setItem('claudeChat_selectedModel', selectedModel);
78
+
79
+ // If conversation exists, add system message
80
+ if (conversationHistory.length > 0) {
81
+ const modelName = modelSelector.options[modelSelector.selectedIndex].text;
82
+ addSystemMessage(`Model changed to ${modelName} — previous context retained`);
83
+ }
84
+ }
85
+
86
+ // Handle keyboard shortcuts
87
+ function handleKeyDown(e) {
88
+ if (e.key === 'Enter' && !e.shiftKey) {
89
+ e.preventDefault();
90
+ if (!isProcessing && messageInput.value.trim()) {
91
+ sendMessage(false);
92
+ }
93
+ }
94
+
95
+ // Clear input shortcut
96
+ if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
97
+ e.preventDefault();
98
+ messageInput.value = '';
99
+ updateCharCount();
100
+ }
101
+
102
+ // New chat shortcut
103
+ if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
104
+ e.preventDefault();
105
+ resetConversation();
106
+ }
107
+ }
108
+
109
+ // Update character count
110
+ function updateCharCount() {
111
+ const count = messageInput.value.length;
112
+ charCount.textContent = `${count}/10000`;
113
+
114
+ if (count > 8000) {
115
+ charCount.classList.add('text-red-500');
116
+ } else {
117
+ charCount.classList.remove('text-red-500');
118
+ }
119
+
120
+ // Enable/disable buttons
121
+ const hasText = count > 0;
122
+ sendBtn.disabled = !hasText || isProcessing;
123
+ streamBtn.disabled = !hasText || isProcessing;
124
+ }
125
+
126
+ // Send message
127
+ async function sendMessage(stream = false) {
128
+ const message = messageInput.value.trim();
129
+ if (!message || isProcessing) return;
130
+
131
+ isProcessing = true;
132
+ isStreaming = stream;
133
+ updateUIState();
134
+
135
+ // Add user message to UI
136
+ addUserMessage(message);
137
+
138
+ // Clear input
139
+ messageInput.value = '';
140
+ updateCharCount();
141
+
142
+ // Show typing indicator for non-streaming
143
+ if (!stream) {
144
+ typingIndicator.classList.remove('hidden');
145
+ chatContainer.scrollTop = chatContainer.scrollHeight;
146
+ }
147
+
148
+ try {
149
+ // Add to history
150
+ conversationHistory.push({ role: 'user', content: message });
151
+ saveConversation();
152
+
153
+ // Call API
154
+ if (stream) {
155
+ await streamResponse(message);
156
+ } else {
157
+ await getResponse(message);
158
+ }
159
+ } catch (error) {
160
+ handleError(error);
161
+ } finally {
162
+ isProcessing = false;
163
+ isStreaming = false;
164
+ updateUIState();
165
+ typingIndicator.classList.add('hidden');
166
+ }
167
+ }
168
+
169
+ // Get response from API (non-streaming)
170
+ async function getResponse(userMessage) {
171
+ try {
172
+ const response = await puter.ai.chat(userMessage, {
173
+ model: selectedModel,
174
+ conversationHistory: conversationHistory
175
+ });
176
+
177
+ const assistantMessage = response.message?.content?.[0]?.text || "Sorry, I couldn't process that.";
178
+
179
+ // Add to history
180
+ conversationHistory.push({ role: 'assistant', content: assistantMessage });
181
+ saveConversation();
182
+
183
+ // Add to UI
184
+ addAssistantMessage(assistantMessage);
185
+ } catch (error) {
186
+ throw error;
187
+ }
188
+ }
189
+
190
+ // Stream response from API
191
+ async function streamResponse(userMessage) {
192
+ try {
193
+ const response = await puter.ai.chat(userMessage, {
194
+ model: selectedModel,
195
+ conversationHistory: conversationHistory,
196
+ stream: true
197
+ });
198
+
199
+ // Create assistant message element
200
+ const messageElement = document.createElement('div');
201
+ messageElement.className = 'assistant-message bg-white p-4 rounded-lg shadow-sm';
202
+ messageElement.innerHTML = `
203
+ <div class="flex items-center mb-2">
204
+ <div class="w-6 h-6 rounded-full bg-purple-500 flex items-center justify-center mr-2">
205
+ <span class="text-white text-xs">A</span>
206
+ </div>
207
+ <span class="font-semibold text-gray-700">Assistant</span>
208
+ </div>
209
+ <div class="text-gray-700"></div>
210
+ `;
211
+
212
+ const contentElement = messageElement.querySelector('.text-gray-700');
213
+ messagesContainer.appendChild(messageElement);
214
+ chatContainer.scrollTop = chatContainer.scrollHeight;
215
+
216
+ let fullResponse = '';
217
+
218
+ // Process stream
219
+ for await (const chunk of response) {
220
+ const text = chunk?.message?.content?.[0]?.text || '';
221
+ fullResponse += text;
222
+ contentElement.textContent = fullResponse;
223
+
224
+ // Scroll to bottom
225
+ chatContainer.scrollTop = chatContainer.scrollHeight;
226
+ }
227
+
228
+ // Add to history
229
+ conversationHistory.push({ role: 'assistant', content: fullResponse });
230
+ saveConversation();
231
+ } catch (error) {
232
+ throw error;
233
+ }
234
+ }
235
+
236
+ // Add user message to UI
237
+ function addUserMessage(content) {
238
+ const messageElement = document.createElement('div');
239
+ messageElement.className = 'user-message bg-blue-500 text-white p-4 rounded-lg shadow-sm ml-auto';
240
+ messageElement.innerHTML = `
241
+ <div class="flex items-center mb-2">
242
+ <div class="w-6 h-6 rounded-full bg-blue-300 flex items-center justify-center mr-2">
243
+ <span class="text-blue-800 text-xs">Y</span>
244
+ </div>
245
+ <span class="font-semibold">You</span>
246
+ </div>
247
+ <div class="text-white">${content}</div>
248
+ `;
249
+
250
+ messagesContainer.appendChild(messageElement);
251
+ chatContainer.scrollTop = chatContainer.scrollHeight;
252
+
253
+ // Hide example prompts after first message
254
+ if (conversationHistory.length === 0) {
255
+ examplePrompts.classList.add('hidden');
256
+ }
257
+ }
258
+
259
+ // Add assistant message to UI
260
+ function addAssistantMessage(content) {
261
+ const messageElement = document.createElement('div');
262
+ messageElement.className = 'assistant-message bg-white p-4 rounded-lg shadow-sm';
263
+ messageElement.innerHTML = `
264
+ <div class="flex items-center mb-2">
265
+ <div class="w-6 h-6 rounded-full bg-purple-500 flex items-center justify-center mr-2">
266
+ <span class="text-white text-xs">A</span>
267
+ </div>
268
+ <span class="font-semibold text-gray-700">Assistant</span>
269
+ </div>
270
+ <div class="text-gray-700">${content}</div>
271
+ `;
272
+
273
+ messagesContainer.appendChild(messageElement);
274
+ chatContainer.scrollTop = chatContainer.scrollHeight;
275
+ }
276
+
277
+ // Add system message to UI
278
+ function addSystemMessage(content) {
279
+ const messageElement = document.createElement('div');
280
+ messageElement.className = 'system-message bg-gray-100 text-gray-600 p-3 rounded-lg text-center text-sm my-2';
281
+ messageElement.textContent = content;
282
+
283
+ messagesContainer.appendChild(messageElement);
284
+ chatContainer.scrollTop = chatContainer.scrollHeight;
285
+ }
286
+
287
+ // Handle errors
288
+ function handleError(error) {
289
+ console.error('Error:', error);
290
+ showStatus(`Request failed: ${error.message}`, 'error');
291
+ retryBtn.classList.remove('hidden');
292
+ }
293
+
294
+ // Retry last message
295
+ function retryLastMessage() {
296
+ if (conversationHistory.length >= 2) {
297
+ const lastUserMessage = conversationHistory[conversationHistory.length - 2].content;
298
+ messageInput.value = lastUserMessage;
299
+ updateCharCount();
300
+ retryBtn.classList.add('hidden');
301
+ sendMessage(isStreaming);
302
+ }
303
+ }
304
+
305
+ // Reset conversation
306
+ function resetConversation() {
307
+ conversationHistory = [];
308
+ localStorage.removeItem('claudeChat_history');
309
+ messagesContainer.innerHTML = `
310
+ <div class="assistant-message bg-white p-4 rounded-lg shadow-sm">
311
+ <div class="flex items-center mb-2">
312
+ <div class="w-6 h-6 rounded-full bg-purple-500 flex items-center justify-center mr-2">
313
+ <span class="text-white text-xs">A</span>
314
+ </div>
315
+ <span class="font-semibold text-gray-700">Assistant</span>
316
+ </div>
317
+ <div class="text-gray-700">
318
+ <p>Hello! I'm Claude, an AI assistant. How can I help you today?</p>
319
+ </div>
320
+ </div>
321
+ `;
322
+ examplePrompts.classList.remove('hidden');
323
+ hideStatus();
324
+ }
325
+
326
+ // Save conversation to localStorage
327
+ function saveConversation() {
328
+ localStorage.setItem('claudeChat_history', JSON.stringify(conversationHistory));
329
+ localStorage.setItem('claudeChat_timestamp', Date.now());
330
+ }
331
+
332
+ // Render conversation history
333
+ function renderConversationHistory() {
334
+ if (conversationHistory.length === 0) return;
335
+
336
+ messagesContainer.innerHTML = '';
337
+ examplePrompts.classList.add('hidden');
338
+
339
+ conversationHistory.forEach(msg => {
340
+ if (msg.role === 'user') {
341
+ addUserMessage(msg.content);
342
+ } else if (msg.role === 'assistant') {
343
+ addAssistantMessage(msg.content);
344
+ }
345
+ });
346
+ }
347
+
348
+ // Update UI state based on processing status
349
+ function updateUIState() {
350
+ if (isProcessing) {
351
+ messageInput.disabled = true;
352
+ sendBtn.disabled = true;
353
+ streamBtn.disabled = true;
354
+ modelSelector.disabled = true;
355
+ messageInput.placeholder = "Waiting for response...";
356
+ } else {
357
+ messageInput.disabled = false;
358
+ updateCharCount();
359
+ modelSelector.disabled = false;
360
+ messageInput.placeholder = "Type your message here... (Shift+Enter for new line)";
361
+ }
362
+ }
363
+
364
+ // Show status message
365
+ function showStatus(message, type) {
366
+ statusMessage.textContent = message;
367
+ statusBar.className = `px-6 py-3 text-sm text-white ${type}`;
368
+ statusBar.classList.remove('hidden');
369
+
370
+ // Auto-hide success messages
371
+ if (type === 'success') {
372
+ setTimeout(hideStatus, 3000);
373
+ }
374
+
375
+ // Auto-hide error messages (but keep retry button)
376
+ if (type === 'error') {
377
+ setTimeout(() => {
378
+ if (!retryBtn.classList.contains('hidden')) return;
379
+ hideStatus();
380
+ }, 5000);
381
+ }
382
+ }
383
+
384
+ // Hide status message
385
+ function hideStatus() {
386
+ statusBar.classList.add('hidden');
387
+ retryBtn.classList.add('hidden');
388
+ }
389
+
390
+ // Handle scroll for auto-scroll detection
391
+ function handleScroll() {
392
+ const threshold = 50;
393
+ const atBottom = chatContainer.scrollTop + chatContainer.clientHeight >= chatContainer.scrollHeight - threshold;
394
+
395
+ // In a real implementation, you might add a "scroll to bottom" button here
396
+ // when the user scrolls up and new messages arrive
397
+ }
style.css CHANGED
@@ -1,28 +1,81 @@
 
 
 
 
 
1
  body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
 
 
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
28
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ /* Custom Styles */
6
  body {
7
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
8
+ }
9
+
10
+ /* Scrollbar styling */
11
+ ::-webkit-scrollbar {
12
+ width: 8px;
13
+ }
14
+
15
+ ::-webkit-scrollbar-track {
16
+ background: #f1f1f1;
17
+ }
18
+
19
+ ::-webkit-scrollbar-thumb {
20
+ background: #c5c5c5;
21
+ border-radius: 4px;
22
+ }
23
+
24
+ ::-webkit-scrollbar-thumb:hover {
25
+ background: #a8a8a8;
26
+ }
27
+
28
+ /* Message styling */
29
+ .user-message {
30
+ @apply ml-auto max-w-[85%] sm:max-w-[75%];
31
+ }
32
+
33
+ .assistant-message {
34
+ @apply max-w-[85%] sm:max-w-[75%];
35
+ }
36
+
37
+ /* Status bar colors */
38
+ #status-bar.success {
39
+ @apply bg-green-500;
40
  }
41
 
42
+ #status-bar.error {
43
+ @apply bg-red-500;
 
44
  }
45
 
46
+ #status-bar.loading {
47
+ @apply bg-blue-500;
 
 
 
48
  }
49
 
50
+ /* Animation for typing indicator */
51
+ @keyframes bounce {
52
+ 0%, 100% {
53
+ transform: translateY(0);
54
+ }
55
+ 50% {
56
+ transform: translateY(-5px);
57
+ }
58
  }
59
 
60
+ .animate-bounce {
61
+ animation: bounce 1s infinite;
62
  }
63
+
64
+ /* Responsive adjustments */
65
+ @media (max-width: 640px) {
66
+ .user-message, .assistant-message {
67
+ @apply max-w-full;
68
+ }
69
+
70
+ #example-prompts .grid {
71
+ @apply grid-cols-1;
72
+ }
73
+
74
+ footer .flex-col {
75
+ @apply gap-2;
76
+ }
77
+
78
+ footer button {
79
+ @apply w-full justify-center;
80
+ }
81
+ }