Add automated session logging system
Some checks failed
Build and Deploy Docs / build-and-deploy (push) Has been cancelled

- Created logs/ structure (sessions, operations, incidents)
- Added session-start/log/end scripts
- Installed Git hooks for auto-logging commits/pushes
- Added shell integration for zsh
- Created CHANGELOG.md
- Documented today's session (2026-01-10)
This commit is contained in:
Apple
2026-01-10 04:53:17 -08:00
parent e67882fd15
commit 744c149300
260 changed files with 6364 additions and 68 deletions

View File

@@ -289,3 +289,12 @@ export function ChatDemoPage() {

268
src/pages/SofiaChatPage.tsx Normal file
View File

@@ -0,0 +1,268 @@
import React, { useState, useRef, useEffect } from 'react';
import { Bot, Send, Loader2, Sparkles, Trash2, FileText } from 'lucide-react';
interface Message {
id: string;
role: 'user' | 'assistant';
content: string;
timestamp: Date;
tokens?: number;
}
export const SofiaChatPage: React.FC = () => {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
// Sofia backend URL
const sofiaUrl = 'http://localhost:8899'; // Потім запустимо FastAPI бекенд
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const sendMessage = async () => {
if (!input.trim() || isLoading) return;
const userMessage: Message = {
id: Date.now().toString(),
role: 'user',
content: input,
timestamp: new Date(),
};
setMessages(prev => [...prev, userMessage]);
setInput('');
setIsLoading(true);
setError(null);
try {
const response = await fetch(`${sofiaUrl}/chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: input,
}),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
const assistantMessage: Message = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: data.response || data.message || 'No response',
timestamp: new Date(),
tokens: data.tokens,
};
setMessages(prev => [...prev, assistantMessage]);
} catch (err: any) {
console.error('Error sending message:', err);
setError(err.message || 'Failed to send message');
// Fallback: show error as assistant message
const errorMessage: Message = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: `❌ Помилка: ${err.message}. Переконайтесь, що Sofia backend запущено.`,
timestamp: new Date(),
};
setMessages(prev => [...prev, errorMessage]);
} finally {
setIsLoading(false);
}
};
const clearChat = () => {
setMessages([]);
setError(null);
};
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-purple-50 via-white to-pink-50">
{/* Header */}
<div className="bg-white border-b border-purple-200 shadow-sm">
<div className="max-w-5xl mx-auto px-4 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-gradient-to-br from-purple-500 to-pink-500 rounded-xl flex items-center justify-center shadow-lg">
<Sparkles className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="text-2xl font-bold bg-gradient-to-r from-purple-600 to-pink-600 bg-clip-text text-transparent">
Sofia
</h1>
<p className="text-sm text-gray-600">
Chief AI Engineer & R&D Orchestrator
</p>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={clearChat}
className="px-3 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors flex items-center gap-2"
title="Очистити чат"
>
<Trash2 className="w-4 h-4" />
<span className="text-sm">Очистити</span>
</button>
<div className="px-3 py-1 bg-purple-100 text-purple-700 rounded-lg text-xs font-medium">
qwen2.5-coder:32b
</div>
</div>
</div>
</div>
</div>
{/* Chat Area */}
<div className="max-w-5xl mx-auto px-4 py-6">
<div className="bg-white rounded-2xl shadow-lg border border-purple-100 overflow-hidden">
{/* Messages */}
<div className="h-[calc(100vh-280px)] overflow-y-auto p-6 space-y-4">
{messages.length === 0 && (
<div className="flex flex-col items-center justify-center h-full text-center">
<div className="w-20 h-20 bg-gradient-to-br from-purple-100 to-pink-100 rounded-full flex items-center justify-center mb-4">
<Sparkles className="w-10 h-10 text-purple-500" />
</div>
<h3 className="text-xl font-semibold text-gray-800 mb-2">
Привіт! Я Sofia 👋
</h3>
<p className="text-gray-600 max-w-md mb-6">
Chief AI Engineer в екосистемі DAARION.city. Допомагаю з дослідженнями AI/ML,
архітектурою агентських систем та технічними рішеннями.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 max-w-2xl">
<button
onClick={() => setInput('Розкажи про свою роль в DAARION')}
className="px-4 py-3 bg-purple-50 hover:bg-purple-100 text-purple-700 rounded-lg text-sm text-left transition-colors"
>
💼 Розкажи про свою роль
</button>
<button
onClick={() => setInput('Які AI моделі найкращі для NLP задач?')}
className="px-4 py-3 bg-purple-50 hover:bg-purple-100 text-purple-700 rounded-lg text-sm text-left transition-colors"
>
🤖 Які моделі для NLP?
</button>
<button
onClick={() => setInput('Як побудувати multi-agent систему?')}
className="px-4 py-3 bg-purple-50 hover:bg-purple-100 text-purple-700 rounded-lg text-sm text-left transition-colors"
>
🏗 Multi-agent архітектура
</button>
<button
onClick={() => setInput('Поясни різницю між RAG та fine-tuning')}
className="px-4 py-3 bg-purple-50 hover:bg-purple-100 text-purple-700 rounded-lg text-sm text-left transition-colors"
>
📚 RAG vs Fine-tuning
</button>
</div>
</div>
)}
{messages.map((message) => (
<div
key={message.id}
className={`flex gap-3 ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
{message.role === 'assistant' && (
<div className="w-8 h-8 bg-gradient-to-br from-purple-500 to-pink-500 rounded-lg flex items-center justify-center flex-shrink-0">
<Bot className="w-5 h-5 text-white" />
</div>
)}
<div
className={`max-w-[70%] px-4 py-3 rounded-2xl ${
message.role === 'user'
? 'bg-gradient-to-br from-purple-500 to-pink-500 text-white'
: 'bg-gray-50 text-gray-900'
}`}
>
<div className="whitespace-pre-wrap break-words">{message.content}</div>
<div className="mt-2 flex items-center gap-2 text-xs opacity-70">
<span>{message.timestamp.toLocaleTimeString('uk-UA')}</span>
{message.tokens && (
<span className="flex items-center gap-1">
{message.tokens} tokens
</span>
)}
</div>
</div>
{message.role === 'user' && (
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-cyan-500 rounded-lg flex items-center justify-center flex-shrink-0">
<span className="text-white text-sm">👤</span>
</div>
)}
</div>
))}
{isLoading && (
<div className="flex gap-3 justify-start">
<div className="w-8 h-8 bg-gradient-to-br from-purple-500 to-pink-500 rounded-lg flex items-center justify-center flex-shrink-0">
<Bot className="w-5 h-5 text-white" />
</div>
<div className="bg-gray-50 px-4 py-3 rounded-2xl">
<Loader2 className="w-5 h-5 animate-spin text-purple-500" />
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
{/* Input Area */}
<div className="border-t border-gray-100 p-4 bg-gray-50">
{error && (
<div className="mb-3 px-4 py-2 bg-red-50 border border-red-200 text-red-700 rounded-lg text-sm">
{error}
</div>
)}
<div className="flex gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="Напишіть повідомлення..."
className="flex-1 px-4 py-3 bg-white border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent"
disabled={isLoading}
/>
<button
onClick={sendMessage}
disabled={!input.trim() || isLoading}
className="px-6 py-3 bg-gradient-to-br from-purple-500 to-pink-500 text-white rounded-xl hover:from-purple-600 hover:to-pink-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all flex items-center gap-2 shadow-lg hover:shadow-xl"
>
{isLoading ? (
<Loader2 className="w-5 h-5 animate-spin" />
) : (
<Send className="w-5 h-5" />
)}
</button>
</div>
<div className="mt-2 text-xs text-gray-500 text-center">
Sofia працює з локальним Ollama qwen2.5-coder:32b
</div>
</div>
</div>
</div>
</div>
);
};