fixed reasoning
This commit is contained in:
+6
-1
@@ -2,7 +2,12 @@
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content"
|
||||
/>
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<title>Home AI Assistant</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
+37
-1
@@ -1,7 +1,9 @@
|
||||
.app {
|
||||
min-height: 100vh;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
@@ -39,4 +41,38 @@
|
||||
.app-main {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.app-header {
|
||||
padding: 0.55rem 0.75rem;
|
||||
gap: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.app-header h1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-header nav {
|
||||
flex: 1;
|
||||
overflow-x: auto;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.35rem;
|
||||
padding-bottom: 0.1rem;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.app-header nav::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-header nav a {
|
||||
padding: 0.4rem 0.65rem;
|
||||
font-size: 0.85rem;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NavLink, Route, Routes } from "react-router-dom";
|
||||
import PomodoroWidget from "./components/PomodoroWidget";
|
||||
import { PomodoroProvider } from "./context/PomodoroContext";
|
||||
import { useVisualViewportHeight } from "./hooks/useVisualViewport";
|
||||
import Character from "./pages/Character";
|
||||
import Chat from "./pages/Chat";
|
||||
import Fitness from "./pages/Fitness";
|
||||
@@ -10,6 +11,8 @@ import Pomodoro from "./pages/Pomodoro";
|
||||
import "./App.css";
|
||||
|
||||
export default function App() {
|
||||
useVisualViewportHeight();
|
||||
|
||||
return (
|
||||
<PomodoroProvider>
|
||||
<div className="app">
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function useVisualViewportHeight() {
|
||||
useEffect(() => {
|
||||
const update = () => {
|
||||
const height = window.visualViewport?.height ?? window.innerHeight;
|
||||
document.documentElement.style.setProperty("--app-height", `${height}px`);
|
||||
};
|
||||
|
||||
update();
|
||||
window.visualViewport?.addEventListener("resize", update);
|
||||
window.visualViewport?.addEventListener("scroll", update);
|
||||
window.addEventListener("resize", update);
|
||||
|
||||
return () => {
|
||||
window.visualViewport?.removeEventListener("resize", update);
|
||||
window.visualViewport?.removeEventListener("scroll", update);
|
||||
window.removeEventListener("resize", update);
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
+12
-1
@@ -11,9 +11,20 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
min-height: 100%;
|
||||
height: var(--app-height, 100dvh);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
button,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
.chat-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
height: calc(100vh - 65px);
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-sidebar {
|
||||
@@ -12,6 +14,7 @@
|
||||
gap: 1rem;
|
||||
background: #12151c;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
@@ -30,6 +33,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.session-list li {
|
||||
@@ -66,6 +70,13 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #0f1115;
|
||||
}
|
||||
|
||||
.chat-mobile-bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat-empty {
|
||||
@@ -75,11 +86,21 @@
|
||||
|
||||
.messages {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: 1.5rem;
|
||||
overflow-x: hidden;
|
||||
padding: 1rem 1rem 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
touch-action: pan-y;
|
||||
}
|
||||
|
||||
.messages-bottom-anchor {
|
||||
flex-shrink: 0;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.message {
|
||||
@@ -176,27 +197,33 @@
|
||||
.chat-input {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem 1.5rem;
|
||||
flex-shrink: 0;
|
||||
padding: 0.75rem 1rem;
|
||||
padding-bottom: max(0.75rem, env(safe-area-inset-bottom));
|
||||
border-top: 1px solid #2a2f3a;
|
||||
background: #0f1115;
|
||||
}
|
||||
|
||||
.chat-input textarea {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
resize: none;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #2f3748;
|
||||
background: #12151c;
|
||||
color: inherit;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.chat-input button {
|
||||
align-self: flex-end;
|
||||
flex-shrink: 0;
|
||||
background: #4f7cff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 0.65rem 1.2rem;
|
||||
padding: 0.65rem 1rem;
|
||||
}
|
||||
|
||||
.chat-input button:disabled {
|
||||
@@ -212,4 +239,53 @@
|
||||
.chat-sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat-mobile-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
padding: 0.55rem 0.75rem;
|
||||
border-bottom: 1px solid #2a2f3a;
|
||||
background: #12151c;
|
||||
}
|
||||
|
||||
.chat-session-select {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #2f3748;
|
||||
background: #0f1115;
|
||||
color: inherit;
|
||||
padding: 0.5rem 0.65rem;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.chat-mobile-new {
|
||||
flex-shrink: 0;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: #4f7cff;
|
||||
color: #fff;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.messages {
|
||||
padding: 0.75rem 0.75rem 0.35rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 92%;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
padding: 0.65rem 0.75rem;
|
||||
padding-bottom: max(0.65rem, env(safe-area-inset-bottom));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.chat-input button {
|
||||
padding: 0.65rem 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FormEvent, useEffect, useRef, useState } from "react";
|
||||
import { FormEvent, useCallback, useEffect, useRef, useState } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { api, ChatMessage, ChatSession } from "../api/client";
|
||||
import PomodoroWidget from "../components/PomodoroWidget";
|
||||
@@ -42,7 +42,9 @@ export default function Chat() {
|
||||
"thinking",
|
||||
);
|
||||
const [liveNotices, setLiveNotices] = useState<string[]>([]);
|
||||
const bottomRef = useRef<HTMLDivElement>(null);
|
||||
const messagesRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const scrollRafRef = useRef<number | null>(null);
|
||||
const { status: pomodoroStatus, refresh: refreshPomodoro } = usePomodoro();
|
||||
const [lastNotifySeq, setLastNotifySeq] = useState(0);
|
||||
|
||||
@@ -69,9 +71,33 @@ export default function Chat() {
|
||||
}
|
||||
}, [activeId]);
|
||||
|
||||
const scrollToBottom = useCallback((smooth = false) => {
|
||||
const container = messagesRef.current;
|
||||
if (!container) return;
|
||||
container.scrollTo({
|
||||
top: container.scrollHeight,
|
||||
behavior: smooth ? "smooth" : "auto",
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
}, [messages, streaming, liveNotices, loading]);
|
||||
if (scrollRafRef.current !== null) {
|
||||
cancelAnimationFrame(scrollRafRef.current);
|
||||
}
|
||||
scrollRafRef.current = requestAnimationFrame(() => {
|
||||
scrollToBottom(!streaming);
|
||||
scrollRafRef.current = null;
|
||||
});
|
||||
return () => {
|
||||
if (scrollRafRef.current !== null) {
|
||||
cancelAnimationFrame(scrollRafRef.current);
|
||||
}
|
||||
};
|
||||
}, [messages, streaming, liveNotices, loading, scrollToBottom]);
|
||||
|
||||
const dismissKeyboard = useCallback(() => {
|
||||
inputRef.current?.blur();
|
||||
}, []);
|
||||
|
||||
const waitingForStream = loading && !streaming;
|
||||
const pendingLabel =
|
||||
@@ -119,6 +145,7 @@ export default function Chat() {
|
||||
|
||||
const text = input.trim();
|
||||
setInput("");
|
||||
dismissKeyboard();
|
||||
setLoading(true);
|
||||
setStreaming("");
|
||||
setPendingPhase("thinking");
|
||||
@@ -215,7 +242,29 @@ export default function Chat() {
|
||||
<div className="chat-empty">Создайте новый чат, чтобы начать</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="messages">
|
||||
<div className="chat-mobile-bar">
|
||||
<select
|
||||
className="chat-session-select"
|
||||
value={activeId}
|
||||
onChange={(e) => setActiveId(Number(e.target.value))}
|
||||
aria-label="Выбор чата"
|
||||
>
|
||||
{sessions.map((session) => (
|
||||
<option key={session.id} value={session.id}>
|
||||
{session.title}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button type="button" className="chat-mobile-new" onClick={handleNewChat}>
|
||||
+ Новый
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="messages"
|
||||
ref={messagesRef}
|
||||
onClick={dismissKeyboard}
|
||||
>
|
||||
{visibleMessages.map((msg) => (
|
||||
<div key={msg.id} className={`message message-${msg.role}`}>
|
||||
<div className="message-role">{roleLabel(msg.role, msg.content)}</div>
|
||||
@@ -260,15 +309,18 @@ export default function Chat() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div ref={bottomRef} />
|
||||
<div className="messages-bottom-anchor" aria-hidden="true" />
|
||||
</div>
|
||||
|
||||
<form className="chat-input" onSubmit={handleSubmit}>
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder="Напишите сообщение..."
|
||||
rows={2}
|
||||
enterKeyHint="send"
|
||||
autoComplete="off"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
display: grid;
|
||||
grid-template-columns: 240px 1fr;
|
||||
gap: 1rem;
|
||||
height: calc(100vh - 80px);
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user