u
This commit is contained in:
@@ -630,15 +630,31 @@ const actions = {
|
|||||||
* Adds brain icon with text as chunks stream in.
|
* Adds brain icon with text as chunks stream in.
|
||||||
*/
|
*/
|
||||||
applyStreamReasoning(data) {
|
applyStreamReasoning(data) {
|
||||||
const text = typeof data?.text === 'string' ? data.text : (typeof data === 'string' ? data : '');
|
let text = '';
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
text = data;
|
||||||
|
} else if (data && typeof data === 'object') {
|
||||||
|
if (typeof data.plain === 'string') {
|
||||||
|
text = data.plain;
|
||||||
|
} else if (data.encrypted) {
|
||||||
|
text = '';
|
||||||
|
} else if (typeof data.text === 'string') {
|
||||||
|
text = data.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!text || !text.trim()) return;
|
if (!text || !text.trim()) return;
|
||||||
|
const trimmed = text.trim();
|
||||||
|
if (trimmed.startsWith('{') && (trimmed.includes('"encrypted"') || trimmed.includes('"plain"'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
chatAutomat.setState((prev) => {
|
chatAutomat.setState((prev) => {
|
||||||
const turns = prev.activeTurns ? [...prev.activeTurns] : [];
|
const turns = prev.activeTurns ? [...prev.activeTurns] : [];
|
||||||
if (turns.length === 0) {
|
if (turns.length === 0) {
|
||||||
turns.push({
|
turns.push({
|
||||||
id: `turn-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
id: `turn-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
||||||
handshake: 'green',
|
handshake: 'green',
|
||||||
reasonings: [{ id: `r-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`, text, sealed: false }],
|
reasonings: [{ id: `r-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`, text: trimmed, sealed: false }],
|
||||||
tools: [],
|
tools: [],
|
||||||
usage: null,
|
usage: null,
|
||||||
content: '',
|
content: '',
|
||||||
@@ -648,6 +664,7 @@ const actions = {
|
|||||||
}
|
}
|
||||||
const lastIdx = turns.length - 1;
|
const lastIdx = turns.length - 1;
|
||||||
const lastTurn = { ...turns[lastIdx] };
|
const lastTurn = { ...turns[lastIdx] };
|
||||||
|
lastTurn.tools = (lastTurn.tools || []).filter((tool) => Boolean(tool.name));
|
||||||
const rList = (lastTurn.reasonings || []).map((r) => ({ ...r }));
|
const rList = (lastTurn.reasonings || []).map((r) => ({ ...r }));
|
||||||
let unsealedIdx = -1;
|
let unsealedIdx = -1;
|
||||||
for (let i = rList.length - 1; i >= 0; i--) {
|
for (let i = rList.length - 1; i >= 0; i--) {
|
||||||
@@ -657,9 +674,9 @@ const actions = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (unsealedIdx >= 0) {
|
if (unsealedIdx >= 0) {
|
||||||
rList[unsealedIdx].text = text;
|
rList[unsealedIdx].text = trimmed;
|
||||||
} else {
|
} else {
|
||||||
rList.push({ id: `r-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`, text, sealed: false });
|
rList.push({ id: `r-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`, text: trimmed, sealed: false });
|
||||||
}
|
}
|
||||||
lastTurn.reasonings = rList;
|
lastTurn.reasonings = rList;
|
||||||
turns[lastIdx] = lastTurn;
|
turns[lastIdx] = lastTurn;
|
||||||
@@ -790,6 +807,7 @@ const actions = {
|
|||||||
lastTurn.reasonings = (lastTurn.reasonings || [])
|
lastTurn.reasonings = (lastTurn.reasonings || [])
|
||||||
.filter((r) => r.text && r.text.trim())
|
.filter((r) => r.text && r.text.trim())
|
||||||
.map((r) => ({ ...r, sealed: true }));
|
.map((r) => ({ ...r, sealed: true }));
|
||||||
|
lastTurn.tools = (lastTurn.tools || []).filter((tool) => Boolean(tool.name));
|
||||||
turns[lastIdx] = lastTurn;
|
turns[lastIdx] = lastTurn;
|
||||||
return { activeTurns: turns, streamPhase: 'working' };
|
return { activeTurns: turns, streamPhase: 'working' };
|
||||||
});
|
});
|
||||||
@@ -815,6 +833,7 @@ const actions = {
|
|||||||
}
|
}
|
||||||
const lastIdx = turns.length - 1;
|
const lastIdx = turns.length - 1;
|
||||||
const lastTurn = { ...turns[lastIdx] };
|
const lastTurn = { ...turns[lastIdx] };
|
||||||
|
lastTurn.tools = (lastTurn.tools || []).filter((tool) => Boolean(tool.name));
|
||||||
const currentText = typeof data.text === 'string'
|
const currentText = typeof data.text === 'string'
|
||||||
? data.text
|
? data.text
|
||||||
: ((lastTurn.content || '') + (data.delta || ''));
|
: ((lastTurn.content || '') + (data.delta || ''));
|
||||||
@@ -874,7 +893,9 @@ const actions = {
|
|||||||
reasonings: (t.reasonings || [])
|
reasonings: (t.reasonings || [])
|
||||||
.filter((r) => r.text && r.text.trim())
|
.filter((r) => r.text && r.text.trim())
|
||||||
.map((r) => ({ ...r, sealed: true })),
|
.map((r) => ({ ...r, sealed: true })),
|
||||||
tools: (t.tools || []).map((tool) => (tool.state === 'black' ? { ...tool, state: 'green' } : tool)),
|
tools: (t.tools || [])
|
||||||
|
.filter((tool) => Boolean(tool.name))
|
||||||
|
.map((tool) => (tool.state === 'black' ? { ...tool, state: 'green' } : tool)),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Gather full cumulative content from all turns
|
// Gather full cumulative content from all turns
|
||||||
@@ -1039,6 +1060,7 @@ const actions = {
|
|||||||
const payload = {
|
const payload = {
|
||||||
role: 'tool',
|
role: 'tool',
|
||||||
tool_call_id,
|
tool_call_id,
|
||||||
|
name: 'ask_user',
|
||||||
content: answer,
|
content: answer,
|
||||||
answer,
|
answer,
|
||||||
};
|
};
|
||||||
@@ -1062,16 +1084,60 @@ const actions = {
|
|||||||
sealed: false,
|
sealed: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
chatAutomat.setState((prev) => ({
|
chatAutomat.setState((prev) => {
|
||||||
messages: [...prev.messages, optimisticMsg],
|
const turns = prev.activeTurns ? [...prev.activeTurns] : [];
|
||||||
activeTurns: [initialTurn],
|
const sealedTurns = turns.map((t) => ({
|
||||||
conversationState: 'busy',
|
...t,
|
||||||
streamPhase: 'thinking',
|
sealed: true,
|
||||||
streamingText: '',
|
reasonings: (t.reasonings || [])
|
||||||
lastUsage: null,
|
.filter((r) => r.text && r.text.trim())
|
||||||
isSending: true,
|
.map((r) => ({ ...r, sealed: true })),
|
||||||
sendError: null,
|
tools: (t.tools || [])
|
||||||
}));
|
.filter((tool) => Boolean(tool.name))
|
||||||
|
.map((tool) => (tool.state === 'black' ? { ...tool, state: 'green' } : tool)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const hasActiveTurns = sealedTurns.some(
|
||||||
|
(t) => (t.reasonings && t.reasonings.length > 0) || (t.tools && t.tools.length > 0) || t.content || t.usage
|
||||||
|
);
|
||||||
|
|
||||||
|
let foundExisting = false;
|
||||||
|
const nextMessages = prev.messages.map((m) => {
|
||||||
|
if (m.role === 'assistant' && !m.turns) {
|
||||||
|
try {
|
||||||
|
const p = typeof m.message === 'string' ? JSON.parse(m.message) : m.message;
|
||||||
|
if (p?.tool_calls?.some((tc) => tc.id === tool_call_id || tc.function?.name === 'ask_user')) {
|
||||||
|
foundExisting = true;
|
||||||
|
return { ...m, turns: sealedTurns };
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!foundExisting && hasActiveTurns) {
|
||||||
|
nextMessages.push({
|
||||||
|
id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
||||||
|
conversation_id: selectedConversationId,
|
||||||
|
role: 'assistant',
|
||||||
|
message: '',
|
||||||
|
turns: sealedTurns,
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
nextMessages.push(optimisticMsg);
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: nextMessages,
|
||||||
|
activeTurns: [initialTurn],
|
||||||
|
conversationState: 'busy',
|
||||||
|
streamPhase: 'thinking',
|
||||||
|
streamingText: '',
|
||||||
|
lastUsage: null,
|
||||||
|
isSending: true,
|
||||||
|
sendError: null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await modelconversationApi.addMessage(selectedConversationId, payload);
|
const res = await modelconversationApi.addMessage(selectedConversationId, payload);
|
||||||
|
|||||||
@@ -10,6 +10,17 @@ import SearchRoundedIcon from '@mui/icons-material/SearchRounded';
|
|||||||
import ComputerRoundedIcon from '@mui/icons-material/ComputerRounded';
|
import ComputerRoundedIcon from '@mui/icons-material/ComputerRounded';
|
||||||
import ImageSearchRoundedIcon from '@mui/icons-material/ImageSearchRounded';
|
import ImageSearchRoundedIcon from '@mui/icons-material/ImageSearchRounded';
|
||||||
import CompareRoundedIcon from '@mui/icons-material/CompareRounded';
|
import CompareRoundedIcon from '@mui/icons-material/CompareRounded';
|
||||||
|
import SupportIcon from '@mui/icons-material/Support';
|
||||||
|
import RecordVoiceOverIcon from '@mui/icons-material/RecordVoiceOver';
|
||||||
|
import PhonelinkIcon from '@mui/icons-material/Phonelink';
|
||||||
|
import SaveAsIcon from '@mui/icons-material/SaveAs';
|
||||||
|
import DescriptionIcon from '@mui/icons-material/Description';
|
||||||
|
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
import GoogleIcon from '@mui/icons-material/Google';
|
||||||
|
import WebIcon from '@mui/icons-material/Web';
|
||||||
|
import EscalatorWarningIcon from '@mui/icons-material/EscalatorWarning';
|
||||||
|
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||||
import HealingMarkdown from './HealingMarkdown';
|
import HealingMarkdown from './HealingMarkdown';
|
||||||
import {
|
import {
|
||||||
CHROMA,
|
CHROMA,
|
||||||
@@ -23,6 +34,17 @@ const TOOL_ICON_MAP = {
|
|||||||
javascript_execute: ComputerRoundedIcon,
|
javascript_execute: ComputerRoundedIcon,
|
||||||
search_images: ImageSearchRoundedIcon,
|
search_images: ImageSearchRoundedIcon,
|
||||||
analyze_image: CompareRoundedIcon,
|
analyze_image: CompareRoundedIcon,
|
||||||
|
apisuggestionsorissues: SupportIcon,
|
||||||
|
ask_user: RecordVoiceOverIcon,
|
||||||
|
dashback_request: PhonelinkIcon,
|
||||||
|
patch: SaveAsIcon,
|
||||||
|
read_file: DescriptionIcon,
|
||||||
|
reset_vfs: DeleteForeverIcon,
|
||||||
|
rm: DeleteIcon,
|
||||||
|
web_search: GoogleIcon,
|
||||||
|
url_get: WebIcon,
|
||||||
|
sub_agent: EscalatorWarningIcon,
|
||||||
|
pdf_read: PictureAsPdfIcon,
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatMessageTime(isoString) {
|
function formatMessageTime(isoString) {
|
||||||
@@ -173,27 +195,108 @@ function HandshakeIconView({ state }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractReasoningText(msg, payload) {
|
||||||
|
if (payload && typeof payload === 'object') {
|
||||||
|
if (payload.plain !== undefined) {
|
||||||
|
if (typeof payload.plain === 'string' && payload.plain.trim()) {
|
||||||
|
return payload.plain.trim();
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (payload.encrypted) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const candidate = payload.text || payload.content || payload.thinking || payload.reasoning;
|
||||||
|
if (candidate && typeof candidate === 'string') {
|
||||||
|
const trimmed = candidate.trim();
|
||||||
|
if (trimmed.startsWith('{') && (trimmed.includes('"encrypted"') || trimmed.includes('"plain"'))) {
|
||||||
|
try {
|
||||||
|
const inner = JSON.parse(trimmed);
|
||||||
|
if (inner.plain && typeof inner.plain === 'string' && inner.plain.trim()) {
|
||||||
|
return inner.plain.trim();
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof msg?.message === 'string') {
|
||||||
|
const raw = msg.message.trim();
|
||||||
|
if (raw.startsWith('{') && (raw.includes('"encrypted"') || raw.includes('"plain"'))) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
if (parsed.plain && typeof parsed.plain === 'string' && parsed.plain.trim()) {
|
||||||
|
return parsed.plain.trim();
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (!raw.startsWith('{')) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReasoningTooltipContent extends PureComponent {
|
||||||
|
boxRef = createRef();
|
||||||
|
|
||||||
|
scrollToBottom = () => {
|
||||||
|
const el = this.boxRef.current;
|
||||||
|
if (el) {
|
||||||
|
el.scrollTop = el.scrollHeight;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (this.boxRef.current) {
|
||||||
|
this.boxRef.current.scrollTop = this.boxRef.current.scrollHeight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.text !== prevProps.text) {
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { text } = this.props;
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
ref={this.boxRef}
|
||||||
|
sx={{
|
||||||
|
p: 0.5,
|
||||||
|
maxWidth: 440,
|
||||||
|
maxHeight: 280,
|
||||||
|
overflowY: 'auto',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
lineHeight: 1.45,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function BrainIconView({ reasoning }) {
|
function BrainIconView({ reasoning }) {
|
||||||
const tooltipContent = reasoning?.text?.trim() || '';
|
const tooltipContent = reasoning?.text?.trim() || '';
|
||||||
if (!tooltipContent) return null;
|
if (!tooltipContent) return null;
|
||||||
|
if (tooltipContent.startsWith('{') && (tooltipContent.includes('"encrypted"') || tooltipContent.includes('"plain"'))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClickTooltip
|
<ClickTooltip
|
||||||
title={
|
title={<ReasoningTooltipContent text={tooltipContent} />}
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
p: 0.5,
|
|
||||||
maxWidth: 440,
|
|
||||||
maxHeight: 280,
|
|
||||||
overflowY: 'auto',
|
|
||||||
whiteSpace: 'pre-wrap',
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
lineHeight: 1.45,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tooltipContent}
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -216,10 +319,11 @@ function BrainIconView({ reasoning }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function WorkIconView({ tool }) {
|
function WorkIconView({ tool }) {
|
||||||
|
if (!tool || !tool.name) return null;
|
||||||
const isGreen = tool?.state === 'green';
|
const isGreen = tool?.state === 'green';
|
||||||
const ToolNameIcon = tool?.name ? TOOL_ICON_MAP[tool.name] : null;
|
const ToolNameIcon = tool?.name ? TOOL_ICON_MAP[tool.name] : null;
|
||||||
const prefix = ToolNameIcon ? tool?.name : null;
|
const prefix = ToolNameIcon ? tool?.name : null;
|
||||||
const tooltipText = formatToolTooltip(tool?.content, tool?.result, tool?.parameters, prefix);
|
const tooltipText = formatToolTooltip(tool?.content, tool?.result, tool?.parameters || tool?.arguments, prefix);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClickTooltip
|
<ClickTooltip
|
||||||
@@ -424,6 +528,19 @@ export function normalizeChatMessages(rawMessages = []) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let turnSeq = 0;
|
let turnSeq = 0;
|
||||||
|
const findExistingTool = (toolCallId) => {
|
||||||
|
if (!toolCallId) return null;
|
||||||
|
if (currentTurn?.tools) {
|
||||||
|
const t = currentTurn.tools.find((tool) => tool.id === toolCallId);
|
||||||
|
if (t) return t;
|
||||||
|
}
|
||||||
|
for (let i = currentAssistantTurns.length - 1; i >= 0; i--) {
|
||||||
|
const t = currentAssistantTurns[i].tools?.find((tool) => tool.id === toolCallId);
|
||||||
|
if (t) return t;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const getOrCreateTurn = (msg) => {
|
const getOrCreateTurn = (msg) => {
|
||||||
const shouldStartNew =
|
const shouldStartNew =
|
||||||
!currentTurn ||
|
!currentTurn ||
|
||||||
@@ -481,29 +598,39 @@ export function normalizeChatMessages(rawMessages = []) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (msg.role === 'reasoning') {
|
if (msg.role === 'reasoning') {
|
||||||
const turn = getOrCreateTurn(msg);
|
const text = extractReasoningText(msg, payload);
|
||||||
const text =
|
if (text) {
|
||||||
payload.plain ||
|
const turn = getOrCreateTurn(msg);
|
||||||
payload.text ||
|
turn.reasonings.push({
|
||||||
payload.content ||
|
id: msg.id || `r-${turn.reasonings.length}`,
|
||||||
payload.thinking ||
|
text,
|
||||||
payload.reasoning ||
|
sealed: true,
|
||||||
(typeof msg.message === 'string' ? msg.message : '');
|
});
|
||||||
turn.reasonings.push({
|
}
|
||||||
id: msg.id || `r-${turn.reasonings.length}`,
|
|
||||||
text,
|
|
||||||
sealed: true,
|
|
||||||
});
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.role === 'tool') {
|
if (msg.role === 'tool') {
|
||||||
|
const existingTool = payload.tool_call_id ? findExistingTool(payload.tool_call_id) : null;
|
||||||
|
const isAskUser = payload.name === 'ask_user' || existingTool?.name === 'ask_user';
|
||||||
|
|
||||||
|
if (!isAskUser && existingTool) {
|
||||||
|
existingTool.result = payload.result !== undefined ? payload.result : (payload.content !== undefined ? payload.content : '');
|
||||||
|
if (payload.parameters) {
|
||||||
|
existingTool.parameters = payload.parameters;
|
||||||
|
}
|
||||||
|
if (payload.name && !existingTool.name) {
|
||||||
|
existingTool.name = payload.name;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const turn = getOrCreateTurn(msg);
|
const turn = getOrCreateTurn(msg);
|
||||||
turn.tools.push({
|
turn.tools.push({
|
||||||
id: payload.tool_call_id || msg.id,
|
id: payload.tool_call_id || msg.id,
|
||||||
state: 'green',
|
state: 'green',
|
||||||
name: payload.name || '',
|
name: isAskUser ? 'ask_user' : (payload.name || ''),
|
||||||
content: payload.content !== undefined ? payload.content : (payload.parameters || ''),
|
content: payload.content !== undefined ? payload.content : (payload.answer || payload.parameters || ''),
|
||||||
parameters: payload.parameters,
|
parameters: payload.parameters,
|
||||||
result: payload.result,
|
result: payload.result,
|
||||||
});
|
});
|
||||||
@@ -526,13 +653,22 @@ export function normalizeChatMessages(rawMessages = []) {
|
|||||||
try {
|
try {
|
||||||
args = typeof tc.function?.arguments === 'string' ? JSON.parse(tc.function.arguments) : (tc.function?.arguments || {});
|
args = typeof tc.function?.arguments === 'string' ? JSON.parse(tc.function.arguments) : (tc.function?.arguments || {});
|
||||||
} catch {}
|
} catch {}
|
||||||
turn.tools.push({
|
|
||||||
id: tc.id || `tc-${Date.now()}`,
|
const isAskUser = tc.function?.name === 'ask_user';
|
||||||
state: 'green',
|
const existing = !isAskUser && tc.id ? findExistingTool(tc.id) : null;
|
||||||
name: tc.function?.name || 'tool',
|
if (existing) {
|
||||||
content: JSON.stringify(args, null, 2),
|
existing.parameters = tc.function?.arguments || args;
|
||||||
result: null,
|
if (tc.function?.name && !existing.name) existing.name = tc.function.name;
|
||||||
});
|
} else {
|
||||||
|
turn.tools.push({
|
||||||
|
id: tc.id || `tc-${Date.now()}`,
|
||||||
|
state: 'green',
|
||||||
|
name: tc.function?.name || 'tool',
|
||||||
|
parameters: tc.function?.arguments || args,
|
||||||
|
content: null,
|
||||||
|
result: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { defineConfig, searchForWorkspaceRoot } from 'vite';
|
import { execSync } from 'node:child_process';
|
||||||
|
import { defineConfig, searchForWorkspaceRoot, createLogger } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { VitePWA } from 'vite-plugin-pwa';
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
|
import pc from 'picocolors';
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const useHttps = process.env.VITE_HTTPS === '1';
|
const useHttps = process.env.VITE_HTTPS === '1';
|
||||||
@@ -209,7 +211,76 @@ function nginxHeadersPreviewPlugin() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getListeningProcess(port) {
|
||||||
|
try {
|
||||||
|
const stdout = execSync(`lsof -nP -ti :${port} -sTCP:LISTEN`, {
|
||||||
|
encoding: 'utf8',
|
||||||
|
stdio: ['pipe', 'pipe', 'ignore'],
|
||||||
|
}).trim();
|
||||||
|
const pid = stdout.split('\n')[0];
|
||||||
|
if (!pid) return null;
|
||||||
|
let cmd = '';
|
||||||
|
try {
|
||||||
|
cmd = execSync(`ps -p ${pid} -o comm=`, {
|
||||||
|
encoding: 'utf8',
|
||||||
|
stdio: ['pipe', 'pipe', 'ignore'],
|
||||||
|
}).trim();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return { pid, cmd };
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPidLogger() {
|
||||||
|
const logger = createLogger();
|
||||||
|
const originalInfo = logger.info.bind(logger);
|
||||||
|
logger.info = (msg, options) => {
|
||||||
|
if (typeof msg === 'string') {
|
||||||
|
const match = msg.match(/Port (\d+) is in use/);
|
||||||
|
if (match) {
|
||||||
|
const port = match[1];
|
||||||
|
const owner = getListeningProcess(port);
|
||||||
|
if (owner) {
|
||||||
|
const detail = owner.cmd ? ` (PID ${owner.pid}: ${owner.cmd})` : ` (PID ${owner.pid})`;
|
||||||
|
msg = msg.replace(`Port ${port} is in use`, `Port ${port} is in use${pc.yellow(detail)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
originalInfo(msg, options);
|
||||||
|
};
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pidReporterPlugin() {
|
||||||
|
const patchPrintUrls = (server) => {
|
||||||
|
if (!server || server._pidReporterPatched) return;
|
||||||
|
server._pidReporterPatched = true;
|
||||||
|
const origPrintUrls = server.printUrls.bind(server);
|
||||||
|
server.printUrls = () => {
|
||||||
|
origPrintUrls();
|
||||||
|
const pmId = process.env.pm_id ? pc.dim(` (PM2 #${process.env.pm_id})`) : '';
|
||||||
|
server.config.logger.info(
|
||||||
|
` ${pc.green('➜')} ${pc.bold('PID')}: ${pc.cyan(process.pid)}${pmId}`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'pid-reporter',
|
||||||
|
configureServer(server) {
|
||||||
|
patchPrintUrls(server);
|
||||||
|
},
|
||||||
|
configurePreviewServer(server) {
|
||||||
|
patchPrintUrls(server);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default defineConfig(({ command }) => ({
|
export default defineConfig(({ command }) => ({
|
||||||
|
customLogger: createPidLogger(),
|
||||||
resolve: sharedResolve,
|
resolve: sharedResolve,
|
||||||
build: {
|
build: {
|
||||||
emptyOutDir: false,
|
emptyOutDir: false,
|
||||||
@@ -298,6 +369,7 @@ export default defineConfig(({ command }) => ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
pidReporterPlugin(),
|
||||||
react(),
|
react(),
|
||||||
nginxHeadersPreviewPlugin(),
|
nginxHeadersPreviewPlugin(),
|
||||||
...(command === 'build'
|
...(command === 'build'
|
||||||
|
|||||||
Reference in New Issue
Block a user