added jenkins

This commit is contained in:
2026-06-11 11:35:46 +03:00
parent b70ac1899c
commit 1aa39fc4b2
3 changed files with 50 additions and 28 deletions
Vendored
+2 -2
View File
@@ -10,7 +10,7 @@
pipeline { pipeline {
agent { agent {
label 'Мастер' label 'мастер'
} }
options { options {
@@ -33,7 +33,7 @@ pipeline {
) )
string( string(
name: 'BACKEND_HEALTH_URL', name: 'BACKEND_HEALTH_URL',
defaultValue: 'http://127.0.0.1:8080/api/v1/health', defaultValue: 'http://127.0.0.1:8202/api/v1/health',
description: 'Проверка после деплоя' description: 'Проверка после деплоя'
) )
booleanParam( booleanParam(
+9 -1
View File
@@ -292,9 +292,13 @@ class ChatService:
reasoning_details: list[Any] | None = None reasoning_details: list[Any] | None = None
finish_reason = "" finish_reason = ""
# После tool-раунда стримим вживую; до tools — буфер (иначе текст «переписывает» notice).
stream_live = tools_executed > 0
async for event in self.llm.stream_chat(messages, tools=TOOL_DEFINITIONS): async for event in self.llm.stream_chat(messages, tools=TOOL_DEFINITIONS):
if event["type"] == "content": if event["type"] == "content":
content_parts.append(event["content"]) content_parts.append(event["content"])
if stream_live:
yield self._sse("token", {"content": event["content"]}) yield self._sse("token", {"content": event["content"]})
elif event["type"] == "reasoning": elif event["type"] == "reasoning":
reasoning = event.get("reasoning", "") or reasoning reasoning = event.get("reasoning", "") or reasoning
@@ -390,8 +394,12 @@ class ChatService:
continue continue
if content_parts and not stream_live:
for part in content_parts:
yield self._sse("token", {"content": part})
final_content = "".join(content_parts).strip() final_content = "".join(content_parts).strip()
if not final_content and streamed_reply_parts: if not final_content and streamed_reply_parts and tools_executed == 0:
final_content = "".join(streamed_reply_parts).strip() final_content = "".join(streamed_reply_parts).strip()
if not final_content and reasoning: if not final_content and reasoning:
final_content = reasoning.strip() final_content = reasoning.strip()
+33 -19
View File
@@ -48,8 +48,8 @@ export default function Chat() {
const [pendingPhase, setPendingPhase] = useState< const [pendingPhase, setPendingPhase] = useState<
"thinking" | "preparing" | "generating" | "tools" "thinking" | "preparing" | "generating" | "tools"
>("thinking"); >("thinking");
const [liveNotices, setLiveNotices] = useState<string[]>([]);
const [chatError, setChatError] = useState<string | null>(null); const [chatError, setChatError] = useState<string | null>(null);
const tempMessageId = useRef(0);
const messagesRef = useRef<HTMLDivElement>(null); const messagesRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null); const inputRef = useRef<HTMLTextAreaElement>(null);
const scrollRafRef = useRef<number | null>(null); const scrollRafRef = useRef<number | null>(null);
@@ -104,18 +104,33 @@ export default function Chat() {
cancelAnimationFrame(scrollRafRef.current); cancelAnimationFrame(scrollRafRef.current);
} }
}; };
}, [messages, streaming, liveNotices, loading, chatError, scrollToBottom]); }, [messages, streaming, loading, chatError, scrollToBottom]);
const dismissKeyboard = useCallback(() => { const dismissKeyboard = useCallback(() => {
inputRef.current?.blur(); inputRef.current?.blur();
}, []); }, []);
const waitingForStream = loading && !streaming; const waitingForStream = loading && !streaming;
const nextTempId = () => {
tempMessageId.current -= 1;
return tempMessageId.current;
};
const appendNotice = useCallback((content: string) => {
setMessages((prev) => [
...prev,
{
id: nextTempId(),
role: "notice",
content,
created_at: new Date().toISOString(),
},
]);
}, []);
const pendingLabel = const pendingLabel =
pendingPhase === "tools" pendingPhase === "tools"
? "Выполняю команды…" ? "Выполняю команды…"
: liveNotices.length > 0
? "Обрабатываю…"
: pendingPhase === "preparing" : pendingPhase === "preparing"
? "Собираю контекст…" ? "Собираю контекст…"
: pendingPhase === "generating" : pendingPhase === "generating"
@@ -177,7 +192,6 @@ export default function Chat() {
await loadSessions(); await loadSessions();
setActiveId(session.id); setActiveId(session.id);
setMessages([]); setMessages([]);
setLiveNotices([]);
}; };
const handleDelete = async (id: number) => { const handleDelete = async (id: number) => {
@@ -187,7 +201,6 @@ export default function Chat() {
if (activeId === id) { if (activeId === id) {
setActiveId(data[0]?.id ?? null); setActiveId(data[0]?.id ?? null);
setMessages([]); setMessages([]);
setLiveNotices([]);
} }
}; };
@@ -201,11 +214,10 @@ export default function Chat() {
setLoading(true); setLoading(true);
setStreaming(""); setStreaming("");
setPendingPhase("thinking"); setPendingPhase("thinking");
setLiveNotices([]);
setChatError(null); setChatError(null);
const tempUser: ChatMessage = { const tempUser: ChatMessage = {
id: Date.now(), id: nextTempId(),
role: "user", role: "user",
content: text, content: text,
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
@@ -234,7 +246,7 @@ export default function Chat() {
setStreaming(assistantText); setStreaming(assistantText);
} }
if (chunk.event === "notice") { if (chunk.event === "notice") {
setLiveNotices((prev) => [...prev, chunk.data.content]); appendNotice(chunk.data.content);
if (String(chunk.data.content).startsWith("⏱")) { if (String(chunk.data.content).startsWith("⏱")) {
refreshPomodoro(); refreshPomodoro();
} }
@@ -243,8 +255,19 @@ export default function Chat() {
refreshPomodoro(); refreshPomodoro();
} }
if (chunk.event === "done") { if (chunk.event === "done") {
const tail = assistantText.trim();
if (tail) {
setMessages((prev) => [
...prev,
{
id: nextTempId(),
role: "assistant",
content: tail,
created_at: new Date().toISOString(),
},
]);
}
setStreaming(""); setStreaming("");
setLiveNotices([]);
setChatError(null); setChatError(null);
await loadMessages(activeId); await loadMessages(activeId);
await loadSessions(); await loadSessions();
@@ -334,15 +357,6 @@ export default function Chat() {
</div> </div>
))} ))}
{liveNotices.map((notice, idx) => (
<div key={`notice-${idx}`} className="message message-notice">
<div className="message-role">{noticeLabel(notice)}</div>
<div className="message-content">
<ReactMarkdown>{notice}</ReactMarkdown>
</div>
</div>
))}
{waitingForStream && ( {waitingForStream && (
<div className="message message-assistant message-pending" aria-live="polite"> <div className="message message-assistant message-pending" aria-live="polite">
<div className="message-role">assistant</div> <div className="message-role">assistant</div>