added jenkins
This commit is contained in:
Vendored
+2
-2
@@ -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(
|
||||||
|
|||||||
@@ -292,10 +292,14 @@ 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"])
|
||||||
yield self._sse("token", {"content": event["content"]})
|
if stream_live:
|
||||||
|
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
|
||||||
if event.get("reasoning_details"):
|
if event.get("reasoning_details"):
|
||||||
@@ -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()
|
||||||
|
|||||||
+38
-24
@@ -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,23 +104,38 @@ 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"
|
: "Думаю…";
|
||||||
? "Генерирую ответ…"
|
|
||||||
: "Думаю…";
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const seq = pomodoroStatus?.cycle?.chat_notify_seq ?? 0;
|
const seq = pomodoroStatus?.cycle?.chat_notify_seq ?? 0;
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user