Chat / app.py
Aleksmorshen's picture
Update app.py
f0af5f7 verified
import os
import io
import json
from flask import Flask, request, jsonify, Response
from PIL import Image
import google.generativeai as genai
app = Flask(__name__)
API_KEY_INTERNAL = "AIzaSyArKidc4o0MwbaCFKStlb2q2AwNg6Pnqns"
html_template = """
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>SYNKRIS AI</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--light-bg: #F9F9F9;
--light-chat-bg: #FFFFFF;
--light-user-bubble: #007AFF;
--light-user-text: #FFFFFF;
--light-ai-bubble: #EFEFF4;
--light-ai-text: #000000;
--light-text-primary: #000000;
--light-text-secondary: #6D6D72;
--light-border: #E5E5EA;
--light-input-bg: #F0F0F0;
--dark-bg: #000000;
--dark-chat-bg: #1C1C1E;
--dark-user-bubble: #0A84FF;
--dark-user-text: #FFFFFF;
--dark-ai-bubble: #2C2C2E;
--dark-ai-text: #FFFFFF;
--dark-text-primary: #FFFFFF;
--dark-text-secondary: #8E8E93;
--dark-border: #3A3A3C;
--dark-input-bg: #2C2C2E;
--bg-color: var(--light-bg);
--chat-bg-color: var(--light-chat-bg);
--user-bubble-color: var(--light-user-bubble);
--user-text-color: var(--light-user-text);
--ai-bubble-color: var(--light-ai-bubble);
--ai-text-color: var(--light-ai-text);
--text-primary: var(--light-text-primary);
--text-secondary: var(--light-text-secondary);
--border-color: var(--light-border);
--input-bg-color: var(--light-input-bg);
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: var(--dark-bg);
--chat-bg-color: var(--dark-chat-bg);
--user-bubble-color: var(--dark-user-bubble);
--user-text-color: var(--dark-user-text);
--ai-bubble-color: var(--dark-ai-bubble);
--ai-text-color: var(--dark-ai-text);
--text-primary: var(--dark-text-primary);
--text-secondary: var(--dark-text-secondary);
--border-color: var(--dark-border);
--input-bg-color: var(--dark-input-bg);
}
}
html {
height: -webkit-fill-available;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
margin: 0;
background-color: var(--bg-color);
color: var(--text-primary);
display: flex;
flex-direction: column;
height: 100vh;
height: -webkit-fill-available;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.chat-container {
display: flex;
flex-direction: column;
height: 100%;
max-width: 800px;
width: 100%;
margin: 0 auto;
background-color: var(--chat-bg-color);
box-shadow: 0 0 20px rgba(0,0,0,0.05);
}
.chat-header {
padding: 15px 20px;
border-bottom: 1px solid var(--border-color);
text-align: center;
flex-shrink: 0;
}
.chat-header h1 {
font-size: 20px;
font-weight: 600;
margin: 0;
color: var(--text-primary);
}
.chat-header span {
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
}
.chat-messages {
flex-grow: 1;
overflow-y: auto;
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
}
.message-bubble {
display: flex;
flex-direction: column;
max-width: 80%;
word-wrap: break-word;
}
.message-content {
padding: 12px 18px;
border-radius: 22px;
line-height: 1.5;
font-size: 16px;
}
.user {
align-self: flex-end;
align-items: flex-end;
}
.user .message-content {
background-color: var(--user-bubble-color);
color: var(--user-text-color);
border-bottom-right-radius: 6px;
}
.ai {
align-self: flex-start;
align-items: flex-start;
}
.ai .message-content {
background-color: var(--ai-bubble-color);
color: var(--ai-text-color);
border-bottom-left-radius: 6px;
}
.error .message-content {
background-color: #FF3B301A;
color: #FF453A;
border: 1px solid #FF3B3080;
}
.typing-indicator {
display: flex;
align-items: center;
gap: 5px;
padding: 12px 18px;
}
.typing-indicator span {
height: 8px;
width: 8px;
background-color: var(--text-secondary);
border-radius: 50%;
opacity: 0.4;
animation: bounce 1.4s infinite ease-in-out both;
}
.typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
.typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1.0); }
}
.chat-input-area {
flex-shrink: 0;
padding: 15px 20px;
padding-bottom: calc(15px + env(safe-area-inset-bottom));
border-top: 1px solid var(--border-color);
background-color: var(--chat-bg-color);
}
.chat-input-form {
display: flex;
align-items: flex-end;
gap: 10px;
}
#message-input {
flex-grow: 1;
border: none;
padding: 14px 18px;
border-radius: 22px;
background-color: var(--input-bg-color);
color: var(--text-primary);
font-family: inherit;
font-size: 16px;
line-height: 1.4;
resize: none;
max-height: 150px;
outline: none;
transition: box-shadow 0.2s;
}
#message-input:focus {
box-shadow: 0 0 0 3px color-mix(in srgb, var(--user-bubble-color) 25%, transparent);
}
#send-button {
border: none;
background-color: var(--user-bubble-color);
color: white;
width: 44px;
height: 44px;
border-radius: 50%;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
transition: background-color 0.2s, transform 0.1s;
}
#send-button svg {
width: 22px;
height: 22px;
transition: transform 0.2s ease-in-out;
}
#send-button:hover {
background-color: color-mix(in srgb, var(--user-bubble-color) 90%, #000);
}
#send-button:active {
transform: scale(0.9);
}
#send-button:disabled {
background-color: var(--text-secondary);
cursor: not-allowed;
}
#send-button:not(:disabled) svg {
transform: translateX(1px);
}
@media (max-width: 800px) {
.chat-container {
border-radius: 0;
box-shadow: none;
}
}
@media (max-width: 600px) {
.message-content {
font-size: 15px;
padding: 10px 15px;
}
#message-input {
font-size: 15px;
padding: 12px 16px;
}
#send-button {
width: 40px;
height: 40px;
}
#send-button svg {
width: 20px;
height: 20px;
}
}
</style>
</head>
<body>
<div class="chat-container">
<header class="chat-header">
<h1>SYNKRIS AI</h1>
<span>by Morshen Group</span>
</header>
<div class="chat-messages" id="chat-messages">
<div class="message-bubble ai" id="initial-message">
<div class="message-content">
Здравствуйте! Я — SYNKRIS AI 2.0. Чем могу помочь?
</div>
</div>
</div>
<footer class="chat-input-area">
<form class="chat-input-form" id="chat-form">
<textarea id="message-input" placeholder="Введите ваше сообщение..." rows="1" required></textarea>
<button type="submit" id="send-button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="24" height="24"><path d="M3.478 2.405a.75.75 0 00-.926.94l2.432 7.905H13.5a.75.75 0 010 1.5H4.984l-2.432 7.905a.75.75 0 00.926.94 60.519 60.519 0 0018.445-8.986.75.75 0 000-1.218A60.517 60.517 0 003.478 2.405z" /></svg>
</button>
</form>
</footer>
</div>
<script>
const chatForm = document.getElementById('chat-form');
const messageInput = document.getElementById('message-input');
const sendButton = document.getElementById('send-button');
const chatMessages = document.getElementById('chat-messages');
let conversationHistory = [];
const addMessageToUI = (sender, message, type = 'text') => {
const messageBubble = document.createElement('div');
messageBubble.classList.add('message-bubble', sender);
const messageContent = document.createElement('div');
if (type === 'error') {
messageBubble.classList.add('error');
messageContent.textContent = `Ошибка: ${message}`;
} else if (type === 'loading') {
messageContent.classList.add('typing-indicator');
messageContent.innerHTML = '<span></span><span></span><span></span>';
messageBubble.id = 'loading-indicator';
} else {
messageContent.classList.add('message-content');
messageContent.textContent = message;
}
messageBubble.appendChild(messageContent);
chatMessages.appendChild(messageBubble);
chatMessages.scrollTop = chatMessages.scrollHeight;
};
const autoResizeTextarea = () => {
messageInput.style.height = 'auto';
let scrollHeight = messageInput.scrollHeight;
let maxHeight = parseInt(window.getComputedStyle(messageInput).maxHeight);
if (scrollHeight > maxHeight) {
messageInput.style.height = maxHeight + 'px';
messageInput.style.overflowY = 'auto';
} else {
messageInput.style.height = scrollHeight + 'px';
messageInput.style.overflowY = 'hidden';
}
};
messageInput.addEventListener('input', autoResizeTextarea);
messageInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
chatForm.requestSubmit();
}
});
chatForm.addEventListener('submit', async (e) => {
e.preventDefault();
const userMessage = messageInput.value.trim();
if (!userMessage) return;
addMessageToUI('user', userMessage);
conversationHistory.push({ role: 'user', parts: [{ text: userMessage }] });
messageInput.value = '';
autoResizeTextarea();
sendButton.disabled = true;
addMessageToUI('ai', '', 'loading');
try {
const response = await fetch('/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ history: conversationHistory })
});
const result = await response.json();
const loadingIndicator = document.getElementById('loading-indicator');
if (loadingIndicator) loadingIndicator.remove();
if (!response.ok) {
throw new Error(result.error || `Ошибка сервера: ${response.status}`);
}
const aiMessage = result.text;
addMessageToUI('ai', aiMessage);
conversationHistory.push({ role: 'model', parts: [{ text: aiMessage }] });
} catch (error) {
console.error("Fetch Error:", error);
const loadingIndicator = document.getElementById('loading-indicator');
if (loadingIndicator) loadingIndicator.remove();
addMessageToUI('ai', error.message, 'error');
} finally {
sendButton.disabled = false;
messageInput.focus();
}
});
</script>
</body>
</html>
"""
@app.route('/')
def index():
return Response(html_template, mimetype='text/html')
@app.route('/chat', methods=['POST'])
def handle_chat():
try:
data = request.get_json()
if not data or 'history' not in data:
return jsonify({"error": "Некорректный запрос. Отсутствует история диалога."}), 400
history = data['history']
genai.configure(api_key=API_KEY_INTERNAL)
system_instruction = {
"role": "user",
"parts": [{
"text": "Ты — SYNKRIS AI 2.0, большая языковая модель, разработанная AI лабораторией Synkris, принадлежащей компании Morshen Group. Веди диалог вежливо, будь полезным и отвечай на вопросы пользователя точно и по существу. Всегда отвечай на том же языке, на котором к тебе обратился пользователь."
}]
}
model_response_instruction = {
"role": "model",
"parts": [{
"text": "Здравствуйте! Я — SYNKRIS AI 2.0. Я готов помочь вам. На каком языке вы предпочитаете общаться?"
}]
}
full_history = [system_instruction, model_response_instruction] + history
model = genai.GenerativeModel('gemma-2-27b-it')
response = model.generate_content(full_history)
if not hasattr(response, 'text') or not response.text:
if response.prompt_feedback and response.prompt_feedback.block_reason:
reason = response.prompt_feedback.block_reason
return jsonify({"error": f"Ответ заблокирован из-за политики безопасности (Причина: {reason})."}), 400
else:
return jsonify({"error": "Модель не сгенерировала ответ. Попробуйте переформулировать запрос."}), 500
return jsonify({"text": response.text})
except Exception as e:
error_message = str(e)
if "API key not valid" in error_message:
return jsonify({"error": "Неверный или неактивный ключ API. Проверьте конфигурацию на сервере."}), 500
elif "resource has been exhausted" in error_message:
return jsonify({"error": "Квота запросов к API исчерпана. Пожалуйста, попробуйте позже."}), 429
else:
return jsonify({"error": f"Произошла внутренняя ошибка сервера: {error_message}"}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=7860, debug=False)