Skip to content
Merged
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,7 @@ dist
.yarn/install-state.gz
.pnp.*
1097d3adbfbe8745c7109358ddb3635d08fb7c47

# Documentation files (temporary setup guides)
API_SETUP.md
QUOTA_SOLUTION.md
4 changes: 4 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export default [
HTMLTextAreaElement: 'readonly',
MouseEvent: 'readonly',
Node: 'readonly',
process: 'readonly',
fetch: 'readonly',
RequestInit: 'readonly',
Response: 'readonly',
},
},
plugins: {
Expand Down
2 changes: 1 addition & 1 deletion src/MainLayout/NavBar/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const NavBar: React.FC = () => {
};

const handleLogout = () => {
// 这里可以添加登出逻辑
// Add logout logic here
// TODO: Implement logout logic
};

Expand Down
2 changes: 1 addition & 1 deletion src/MainLayout/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ const Sidebar: React.FC = () => {
</div>
</div>
)}
{/* 搜索弹窗 */}
{/* Search modal */}
{showSearch && (
<div className={styles.searchModalOverlay} onClick={() => setShowSearch(false)}>
<div className={styles.searchModal} onClick={(e) => e.stopPropagation()}>
Expand Down
22 changes: 22 additions & 0 deletions src/Pages/Chat/Chat.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,28 @@
font-weight: 400;
}

.apiWarning {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-top: 16px;
padding: 12px 16px;
background: #fef3c7;
border: 1px solid #f59e0b;
border-radius: 8px;
color: #92400e;
font-size: 0.875rem;
max-width: 500px;
margin-left: auto;
margin-right: auto;
}

.apiWarning svg {
flex-shrink: 0;
color: #f59e0b;
}

.chatInputContainer {
margin-bottom: 40px;
}
Expand Down
92 changes: 85 additions & 7 deletions src/Pages/Chat/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useState, useRef, useEffect } from 'react';
import { Send, Mic, Paperclip, Smile, Sparkles, Plus } from 'lucide-react';
import { Send, Mic, Paperclip, Smile, Sparkles, Plus, AlertCircle } from 'lucide-react';
import styles from './Chat.module.css';
import { useChatStore, useCurrentMessages, Message } from '../../stores/chatStore';
import { useUserStore } from '../../stores/userStore';
import openAIService from '../../services/openaiService';

const Chat: React.FC = () => {
const [inputValue, setInputValue] = useState('');
Expand All @@ -14,7 +15,9 @@ const Chat: React.FC = () => {

// translation function
const t = (key: string) => {
const translations: { [key: string]: { [key: string]: string } } = {
const translations: {
[key: string]: { [key: string]: string };
} = {
'en-US': {
'chat.placeholder': 'Message ThinkML...',
'chat.welcome.title': 'ThinkML',
Expand All @@ -25,6 +28,11 @@ const Chat: React.FC = () => {
'chat.welcome.suggestion3': 'How to optimize website performance?',
'chat.welcome.suggestion4': 'Write a simple todo app',
'nav.newChat': 'New Chat',
'chat.error.api': 'API Error',
'chat.error.noApiKey':
'OpenAI API key not configured. Please set REACT_APP_OPENAI_API_KEY in your environment variables.',
'chat.error.network': 'Network error. Please check your connection.',
'chat.error.unknown': 'An unknown error occurred.',
},
'zh-CN': {
'chat.placeholder': '发送消息给 ThinkML...',
Expand All @@ -36,6 +44,11 @@ const Chat: React.FC = () => {
'chat.welcome.suggestion3': '如何优化网站性能?',
'chat.welcome.suggestion4': '写一个简单的待办应用',
'nav.newChat': '新建聊天',
'chat.error.api': 'API 错误',
'chat.error.noApiKey':
'OpenAI API 密钥未配置。请在环境变量中设置 REACT_APP_OPENAI_API_KEY。',
'chat.error.network': '网络错误。请检查您的连接。',
'chat.error.unknown': '发生未知错误。',
},
'ja-JP': {
'chat.placeholder': 'ThinkMLにメッセージを送信...',
Expand All @@ -47,6 +60,11 @@ const Chat: React.FC = () => {
'chat.welcome.suggestion3': 'ウェブサイトのパフォーマンスを最適化する方法は?',
'chat.welcome.suggestion4': 'シンプルなTodoアプリを作成して',
'nav.newChat': '新しいチャット',
'chat.error.api': 'API エラー',
'chat.error.noApiKey':
'OpenAI API キーが設定されていません。環境変数で REACT_APP_OPENAI_API_KEY を設定してください。',
'chat.error.network': 'ネットワークエラー。接続を確認してください。',
'chat.error.unknown': '不明なエラーが発生しました。',
},
};

Expand Down Expand Up @@ -83,17 +101,66 @@ const Chat: React.FC = () => {
setInputValue('');
setLoading(true);

// Simulate AI response
setTimeout(() => {
try {
// check api key
if (!openAIService.isConfigured()) {
throw new Error(t('chat.error.noApiKey'));
}

// api messages
const apiMessages = messages.map((msg) => ({
role: msg.role as 'user' | 'assistant',
content: msg.content,
}));

// add current user message
apiMessages.push({
role: 'user',
content: userMessage.content,
});

// call ChatGPT API
const response = await openAIService.chatCompletion(apiMessages, {
model: 'gpt-3.5-turbo',
maxTokens: 1000,
temperature: 0.7,
});

const assistantMessage: Message = {
id: (Date.now() + 1).toString(),
content: `I received your message: "${userMessage.content}". This is a simulated response. In a real application, this would call an AI API.`,
content: response,
role: 'assistant',
timestamp: new Date(),
};
addMessage(assistantMessage);
} catch (error) {
// eslint-disable-next-line no-console
console.error('Chat API Error:', error);

let errorMessage = t('chat.error.unknown');
if (error instanceof Error) {
if (error.message.includes('API key')) {
errorMessage = t('chat.error.noApiKey');
} else if (error.message.includes('fetch') || error.message.includes('network')) {
errorMessage = t('chat.error.network');
} else if (error.message.includes('quota')) {
errorMessage = 'API 配额已用完,已切换到模拟模式。请检查你的 OpenAI 账户余额。';
} else {
errorMessage = error.message;
}
}

// add error message to chat
const errorResponse: Message = {
id: (Date.now() + 1).toString(),
content: `❌ ${errorMessage}`,
role: 'assistant',
timestamp: new Date(),
};
addMessage(errorResponse);
} finally {
setLoading(false);
}, 1000);
}
};

const handleKeyPress = (e: React.KeyboardEvent) => {
Expand All @@ -104,13 +171,16 @@ const Chat: React.FC = () => {
};

const handleNewChat = () => {
// 由Sidebar控制新建会话
// new chat by sidebar
setInputValue('');
setTimeout(() => {
inputRef.current?.focus();
}, 100);
};

// check API configuration status
const apiConfigStatus = openAIService.getConfigurationStatus();

return (
<div className={styles.chatContainer}>
{!hasStartedChat ? (
Expand All @@ -123,6 +193,14 @@ const Chat: React.FC = () => {
</div>
<h1 className={styles.welcomeTitle}>{t('chat.welcome.title')}</h1>
<p className={styles.welcomeSubtitle}>{t('chat.welcome.subtitle')}</p>

{/* API configuration status hint */}
{!apiConfigStatus.configured && (
<div className={styles.apiWarning}>
<AlertCircle size={16} />
<span>{apiConfigStatus.message}</span>
</div>
)}
</div>

<div className={styles.chatInputContainer}>
Expand Down
Loading