feat: update openclaw workspace integration
This commit is contained in:
@@ -13,6 +13,9 @@
|
||||
"preview:host": "vite preview --host"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dicebear/collection": "^9.4.2",
|
||||
"@dicebear/core": "^9.4.2",
|
||||
"@lobehub/icons": "^5.0.1",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
|
||||
@@ -234,6 +234,26 @@ export default function LiveTradingApp() {
|
||||
workspaceFilesByAgent,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSocketReady || !clientRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
AGENTS.forEach((agent) => {
|
||||
if (!agent?.id) {
|
||||
return;
|
||||
}
|
||||
if (!agentProfilesByAgent[agent.id]) {
|
||||
requestAgentProfile(agent.id);
|
||||
}
|
||||
});
|
||||
}, [
|
||||
agentProfilesByAgent,
|
||||
clientRef,
|
||||
isSocketReady,
|
||||
requestAgentProfile,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const symbols = runtimeControls.displayTickers
|
||||
.map((ticker) => ticker.symbol)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { ASSETS } from '../config/constants';
|
||||
import { getModelIcon, getShortModelName } from '../utils/modelIcons';
|
||||
import LobeModelLogo from './LobeModelLogo.jsx';
|
||||
|
||||
/**
|
||||
* Get rank medal/trophy
|
||||
@@ -207,14 +208,18 @@ export default function AgentCard({ agent, onClose, isClosing }) {
|
||||
justifyContent: 'center',
|
||||
marginBottom: 4
|
||||
}}>
|
||||
{modelInfo.logoPath ? (
|
||||
<img
|
||||
src={modelInfo.logoPath}
|
||||
{agent.modelName || modelInfo.logoPath ? (
|
||||
<LobeModelLogo
|
||||
model={agent.modelName}
|
||||
provider={agent.modelProvider}
|
||||
fallbackSrc={modelInfo.logoPath}
|
||||
alt={modelInfo.provider}
|
||||
size={36}
|
||||
type="color"
|
||||
shape="square"
|
||||
style={{
|
||||
maxHeight: '100%',
|
||||
maxWidth: '100%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -3,6 +3,7 @@ import { formatTime } from '../utils/formatters';
|
||||
import { MESSAGE_COLORS, getAgentColors, AGENTS, ASSETS } from '../config/constants';
|
||||
import { getModelIcon } from '../utils/modelIcons';
|
||||
import MarkdownModal from './MarkdownModal';
|
||||
import LobeModelLogo from './LobeModelLogo.jsx';
|
||||
|
||||
const isAnalyst = (agentId, agentName) => {
|
||||
if (agentId && agentId.includes('analyst')) return true;
|
||||
@@ -167,11 +168,11 @@ const AgentFeed = forwardRef(({ feed, leaderboard, agentProfilesByAgent }, ref)
|
||||
// Get current selection display info
|
||||
const getCurrentSelectionInfo = () => {
|
||||
if (selectedAgent === 'all') {
|
||||
return { label: '全部角色', modelInfo: null };
|
||||
return { label: '全部角色', modelInfo: null, agentInfo: null };
|
||||
}
|
||||
const agentInfo = getAgentInfoByName(selectedAgent);
|
||||
const modelInfo = agentInfo ? getModelIcon(agentInfo.modelName, agentInfo.modelProvider) : null;
|
||||
return { label: selectedAgent, modelInfo };
|
||||
return { label: selectedAgent, modelInfo, agentInfo };
|
||||
};
|
||||
|
||||
const currentSelection = getCurrentSelectionInfo();
|
||||
@@ -189,11 +190,16 @@ const AgentFeed = forwardRef(({ feed, leaderboard, agentProfilesByAgent }, ref)
|
||||
onBlur={() => setTimeout(() => setDropdownOpen(false), 200)}
|
||||
>
|
||||
<div className="custom-select-value">
|
||||
{currentSelection.modelInfo?.logoPath && (
|
||||
<img
|
||||
src={currentSelection.modelInfo.logoPath}
|
||||
alt={currentSelection.modelInfo.provider}
|
||||
{(currentSelection.agentInfo?.modelName || currentSelection.modelInfo?.logoPath) && (
|
||||
<LobeModelLogo
|
||||
model={currentSelection.agentInfo?.modelName}
|
||||
provider={currentSelection.agentInfo?.modelProvider}
|
||||
fallbackSrc={currentSelection.modelInfo?.logoPath}
|
||||
alt={currentSelection.modelInfo?.provider}
|
||||
size={18}
|
||||
className="select-model-icon"
|
||||
shape="square"
|
||||
type="color"
|
||||
/>
|
||||
)}
|
||||
<span>{currentSelection.label}</span>
|
||||
@@ -223,11 +229,16 @@ const AgentFeed = forwardRef(({ feed, leaderboard, agentProfilesByAgent }, ref)
|
||||
setDropdownOpen(false);
|
||||
}}
|
||||
>
|
||||
{modelInfo?.logoPath && (
|
||||
<img
|
||||
src={modelInfo.logoPath}
|
||||
alt={modelInfo.provider}
|
||||
{(agentInfo?.modelName || modelInfo?.logoPath) && (
|
||||
<LobeModelLogo
|
||||
model={agentInfo?.modelName}
|
||||
provider={agentInfo?.modelProvider}
|
||||
fallbackSrc={modelInfo?.logoPath}
|
||||
alt={modelInfo?.provider}
|
||||
size={18}
|
||||
className="select-model-icon"
|
||||
shape="square"
|
||||
type="color"
|
||||
/>
|
||||
)}
|
||||
<span>{agent}</span>
|
||||
@@ -363,16 +374,16 @@ function ConferenceMessage({ message, getAgentModelInfo }) {
|
||||
return (
|
||||
<div className="conf-message-item">
|
||||
<div className="conf-agent-name" style={{ color: agentColors.text, display: 'flex', alignItems: 'center', gap: '6px', fontSize: '12px' }}>
|
||||
{modelInfo.logoPath && (
|
||||
<img
|
||||
src={modelInfo.logoPath}
|
||||
{(agentModelData.modelName || modelInfo.logoPath) && (
|
||||
<LobeModelLogo
|
||||
model={agentModelData.modelName}
|
||||
provider={agentModelData.modelProvider}
|
||||
fallbackSrc={modelInfo.logoPath}
|
||||
alt={modelInfo.provider}
|
||||
style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
size={20}
|
||||
shape="circle"
|
||||
type="color"
|
||||
style={{ borderRadius: '50%' }}
|
||||
/>
|
||||
)}
|
||||
{message.agent}
|
||||
@@ -591,16 +602,16 @@ function MessageItem({ message, itemId, isHighlighted, getAgentModelInfo }) {
|
||||
>
|
||||
<div className="feed-item-header">
|
||||
<span className="feed-item-title" style={{ color: colors.text, display: 'flex', alignItems: 'center', gap: '6px', fontSize: '12px' }}>
|
||||
{modelInfo.logoPath && message.agent !== 'Memory' && (
|
||||
<img
|
||||
src={modelInfo.logoPath}
|
||||
{message.agent !== 'Memory' && (agentModelData.modelName || modelInfo.logoPath) && (
|
||||
<LobeModelLogo
|
||||
model={agentModelData.modelName}
|
||||
provider={agentModelData.modelProvider}
|
||||
fallbackSrc={modelInfo.logoPath}
|
||||
alt={modelInfo.provider}
|
||||
style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
size={20}
|
||||
shape="circle"
|
||||
type="color"
|
||||
style={{ borderRadius: '50%' }}
|
||||
/>
|
||||
)}
|
||||
{title}
|
||||
|
||||
78
frontend/src/components/LobeModelLogo.jsx
Normal file
78
frontend/src/components/LobeModelLogo.jsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import ModelIcon from '@lobehub/icons/es/features/ModelIcon';
|
||||
import ProviderIcon from '@lobehub/icons/es/features/ProviderIcon';
|
||||
|
||||
export default function LobeModelLogo({
|
||||
model,
|
||||
provider,
|
||||
fallbackSrc = null,
|
||||
alt = '',
|
||||
size = 28,
|
||||
shape = 'square',
|
||||
type = 'color',
|
||||
style = {},
|
||||
className = '',
|
||||
}) {
|
||||
const hasModel = typeof model === 'string' && model.trim().length > 0;
|
||||
const hasProvider = typeof provider === 'string' && provider.trim().length > 0;
|
||||
|
||||
try {
|
||||
if (hasModel) {
|
||||
return (
|
||||
<ModelIcon
|
||||
model={model}
|
||||
size={size}
|
||||
shape={shape}
|
||||
type={type}
|
||||
className={className}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (hasProvider) {
|
||||
return (
|
||||
<ProviderIcon
|
||||
provider={provider.toLowerCase()}
|
||||
size={size}
|
||||
shape={shape}
|
||||
type={type}
|
||||
className={className}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// Fall through to local fallback asset.
|
||||
}
|
||||
|
||||
if (fallbackSrc) {
|
||||
return (
|
||||
<img
|
||||
src={fallbackSrc}
|
||||
alt={alt}
|
||||
className={className}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
objectFit: 'contain',
|
||||
...style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: shape === 'circle' ? '50%' : 8,
|
||||
background: '#F3F4F6',
|
||||
border: '1px solid #D1D5DB',
|
||||
...style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,5 @@
|
||||
import { OpenClawStatus } from './OpenClawStatus';
|
||||
|
||||
export default function OpenClawView() {
|
||||
return (
|
||||
<div style={{
|
||||
height: '100%',
|
||||
overflow: 'auto',
|
||||
padding: '16px',
|
||||
background: '#F3F4F6',
|
||||
}}>
|
||||
<OpenClawStatus />
|
||||
</div>
|
||||
);
|
||||
return <OpenClawStatus />;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react'
|
||||
import { ASSETS, SCENE_NATIVE, AGENT_SEATS, AGENTS } from '../config/constants';
|
||||
import AgentCard from './AgentCard';
|
||||
import { getModelIcon } from '../utils/modelIcons';
|
||||
import LobeModelLogo from './LobeModelLogo.jsx';
|
||||
|
||||
/**
|
||||
* Custom hook to load an image
|
||||
@@ -518,21 +519,23 @@ export default function RoomView({ bubbles, bubbleFor, leaderboard, agentProfile
|
||||
{medal}
|
||||
</span>
|
||||
)}
|
||||
{modelInfo.logoPath && (
|
||||
<img
|
||||
src={modelInfo.logoPath}
|
||||
{(agentData?.modelName || modelInfo.logoPath) && (
|
||||
<LobeModelLogo
|
||||
model={agentData?.modelName}
|
||||
provider={agentData?.modelProvider}
|
||||
fallbackSrc={modelInfo.logoPath}
|
||||
alt={modelInfo.provider}
|
||||
size={25}
|
||||
shape="circle"
|
||||
type="color"
|
||||
className="agent-model-badge"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: -12,
|
||||
right: -12,
|
||||
width: 25,
|
||||
height: 25,
|
||||
borderRadius: '50%',
|
||||
border: '2px solid #ffffff',
|
||||
background: '#ffffff',
|
||||
objectFit: 'contain',
|
||||
padding: 2,
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||
pointerEvents: 'none'
|
||||
@@ -642,10 +645,15 @@ export default function RoomView({ bubbles, bubbleFor, leaderboard, agentProfile
|
||||
|
||||
{/* Agent header with model icon */}
|
||||
<div className="room-bubble-header">
|
||||
{modelInfo.logoPath && (
|
||||
<img
|
||||
src={modelInfo.logoPath}
|
||||
{(agentData?.modelName || modelInfo.logoPath) && (
|
||||
<LobeModelLogo
|
||||
model={agentData?.modelName}
|
||||
provider={agentData?.modelProvider}
|
||||
fallbackSrc={modelInfo.logoPath}
|
||||
alt={modelInfo.provider}
|
||||
size={18}
|
||||
shape="circle"
|
||||
type="color"
|
||||
className="bubble-model-icon"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import JSZip from 'jszip';
|
||||
import { getModelIcon, getShortModelName } from '../utils/modelIcons';
|
||||
import LobeModelLogo from './LobeModelLogo.jsx';
|
||||
|
||||
export default function TraderView({
|
||||
agents,
|
||||
@@ -249,13 +250,16 @@ export default function TraderView({
|
||||
alignItems: 'center',
|
||||
gap: 10
|
||||
}}>
|
||||
{modelInfo.logoPath && (
|
||||
<img
|
||||
src={modelInfo.logoPath}
|
||||
alt={modelInfo.provider}
|
||||
style={{ width: 26, height: 26, borderRadius: 999 }}
|
||||
/>
|
||||
)}
|
||||
<LobeModelLogo
|
||||
model={profile.model_name}
|
||||
provider={profile.model_provider}
|
||||
fallbackSrc={modelInfo.logoPath}
|
||||
alt={modelInfo.provider}
|
||||
size={26}
|
||||
shape="circle"
|
||||
type="color"
|
||||
style={{ borderRadius: 999 }}
|
||||
/>
|
||||
<div style={{ display: 'grid', gap: 2 }}>
|
||||
<div style={{ fontSize: 11, color: '#4B5563', fontWeight: 700 }}>模型</div>
|
||||
<div style={{ fontSize: 12, color: '#111111', fontWeight: 800 }}>
|
||||
|
||||
@@ -58,6 +58,92 @@ export function useOpenClawPanel() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const resolveSession = useCallback(({ agentId, label = null, channel = null, includeGlobal = true }) => {
|
||||
const store = getStore();
|
||||
const client = store.clientRef?.current;
|
||||
if (!client) return;
|
||||
store.setChatError?.(null);
|
||||
sendWithRetry({ current: client }, {
|
||||
type: "openclaw_resolve_session",
|
||||
agent_id: agentId,
|
||||
label,
|
||||
channel,
|
||||
include_global: includeGlobal,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const createSession = useCallback(({ agentId, label = null, model = null, initialMessage = null }) => {
|
||||
const store = getStore();
|
||||
const client = store.clientRef?.current;
|
||||
if (!client || !agentId) return;
|
||||
store.setChatError?.(null);
|
||||
sendWithRetry({ current: client }, {
|
||||
type: "openclaw_create_session",
|
||||
agent_id: agentId,
|
||||
label,
|
||||
model,
|
||||
initial_message: initialMessage,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const subscribeSession = useCallback((sessionKey) => {
|
||||
const client = getStore().clientRef?.current;
|
||||
if (!client || !sessionKey) return;
|
||||
sendWithRetry({ current: client }, {
|
||||
type: "openclaw_subscribe_session",
|
||||
session_key: sessionKey,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const unsubscribeSession = useCallback((sessionKey) => {
|
||||
const client = getStore().clientRef?.current;
|
||||
if (!client || !sessionKey) return;
|
||||
sendWithRetry({ current: client }, {
|
||||
type: "openclaw_unsubscribe_session",
|
||||
session_key: sessionKey,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const resetSession = useCallback((sessionKey) => {
|
||||
const store = getStore();
|
||||
const client = store.clientRef?.current;
|
||||
if (!client || !sessionKey) return;
|
||||
store.setChatError?.(null);
|
||||
sendWithRetry({ current: client }, {
|
||||
type: "openclaw_reset_session",
|
||||
session_key: sessionKey,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const deleteSession = useCallback((sessionKey) => {
|
||||
const store = getStore();
|
||||
const client = store.clientRef?.current;
|
||||
if (!client || !sessionKey) return;
|
||||
store.setChatError?.(null);
|
||||
sendWithRetry({ current: client }, {
|
||||
type: "openclaw_delete_session",
|
||||
session_key: sessionKey,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const sendSessionMessage = useCallback((sessionKey, message, thinking = null) => {
|
||||
const store = getStore();
|
||||
const client = store.clientRef?.current;
|
||||
if (!client || !sessionKey || !message?.trim()) return;
|
||||
sendWithRetry({ current: client }, {
|
||||
type: "openclaw_subscribe_session",
|
||||
session_key: sessionKey,
|
||||
});
|
||||
store.setOpenclawChatSendingForSession?.(sessionKey, true);
|
||||
store.setChatError?.(null);
|
||||
sendWithRetry({ current: client }, {
|
||||
type: "openclaw_send_message",
|
||||
session_key: sessionKey,
|
||||
message: message.trim(),
|
||||
thinking,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const requestCron = useCallback(() => {
|
||||
const store = getStore();
|
||||
const client = store.clientRef?.current;
|
||||
@@ -91,13 +177,13 @@ export function useOpenClawPanel() {
|
||||
sendWithRetry({ current: client }, { type: "get_openclaw_agents_presence" });
|
||||
}, []);
|
||||
|
||||
const requestSkills = useCallback(() => {
|
||||
const requestSkills = useCallback((agentId = null) => {
|
||||
const store = getStore();
|
||||
const client = store.clientRef?.current;
|
||||
if (!client) return;
|
||||
store.setSkillsLoading(true);
|
||||
store.setSkillsError(null);
|
||||
sendWithRetry({ current: client }, { type: "get_openclaw_skills" });
|
||||
sendWithRetry({ current: client }, { type: "get_openclaw_skills", agent_id: agentId });
|
||||
}, []);
|
||||
|
||||
const requestModels = useCallback(() => {
|
||||
@@ -239,6 +325,13 @@ export function useOpenClawPanel() {
|
||||
requestSessions,
|
||||
requestSessionDetail,
|
||||
requestSessionHistory,
|
||||
resolveSession,
|
||||
createSession,
|
||||
subscribeSession,
|
||||
unsubscribeSession,
|
||||
resetSession,
|
||||
deleteSession,
|
||||
sendSessionMessage,
|
||||
requestCron,
|
||||
requestApprovals,
|
||||
requestAgents,
|
||||
|
||||
@@ -66,6 +66,306 @@ function buildTickersFromSymbols(symbols, previousTickers = []) {
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeOpenClawHistoryItems(history) {
|
||||
if (!Array.isArray(history)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return history
|
||||
.map((item, index) => {
|
||||
const role = item?.role || item?.senderRole || item?.kind || item?.type || 'event';
|
||||
const isFinal = hasOpenClawFinalTag(item);
|
||||
const text = extractOpenClawText(item);
|
||||
if (!shouldKeepOpenClawMessage(item)) {
|
||||
return null;
|
||||
}
|
||||
const timestamp = item?.timestamp || item?.ts || item?.createdAt || item?.time || null;
|
||||
const nestedMeta = item?.message?.__openclaw || item?.__openclaw || null;
|
||||
const seq = item?.messageSeq ?? item?.seq ?? nestedMeta?.seq ?? null;
|
||||
const messageId = item?.messageId ?? item?.id ?? nestedMeta?.id ?? null;
|
||||
|
||||
return {
|
||||
id: messageId || (seq !== null ? `seq:${seq}` : `${timestamp || 'history'}:${index}`),
|
||||
role,
|
||||
text: String(text || ''),
|
||||
timestamp,
|
||||
seq,
|
||||
messageId,
|
||||
isFinal,
|
||||
raw: item,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function unwrapOpenClawFinal(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return null;
|
||||
}
|
||||
const match = value.match(/<final>([\s\S]*?)<\/final>/i);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return match[1].trim();
|
||||
}
|
||||
|
||||
function stripOpenClawFinalTags(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return value ? String(value) : '';
|
||||
}
|
||||
return value.replace(/<\/?final>/gi, '').trim();
|
||||
}
|
||||
|
||||
function shouldHideOpenClawMessage({ role, text }) {
|
||||
const normalizedRole = String(role || '').toLowerCase();
|
||||
const normalizedText = String(text || '').trim();
|
||||
|
||||
if (normalizedRole === 'system') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (normalizedRole === 'user') {
|
||||
if (normalizedText.startsWith('Sender (untrusted metadata):')) {
|
||||
return true;
|
||||
}
|
||||
if (normalizedText.startsWith('[Fri ') || normalizedText.startsWith('[Sat ') || normalizedText.startsWith('[Sun ')
|
||||
|| normalizedText.startsWith('[Mon ') || normalizedText.startsWith('[Tue ') || normalizedText.startsWith('[Wed ')
|
||||
|| normalizedText.startsWith('[Thu ')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function shouldKeepOpenClawMessage(item) {
|
||||
const role = item?.role || item?.senderRole || item?.kind || item?.type || 'event';
|
||||
const text = extractOpenClawText(item);
|
||||
const isFinal = hasOpenClawFinalTag(item);
|
||||
|
||||
if (shouldHideOpenClawMessage({ role, text })) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const normalizedRole = String(role || '').toLowerCase();
|
||||
if (normalizedRole === 'assistant') {
|
||||
return isFinal;
|
||||
}
|
||||
|
||||
if (!normalizedRole || normalizedRole === 'event') {
|
||||
return isFinal;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function hasOpenClawFinalTag(item) {
|
||||
if (typeof item === 'string') {
|
||||
return /<final>[\s\S]*?<\/final>/i.test(item);
|
||||
}
|
||||
|
||||
if (!item || typeof item !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const candidates = [];
|
||||
if (typeof item.text === 'string') candidates.push(item.text);
|
||||
if (typeof item.message === 'string') candidates.push(item.message);
|
||||
if (typeof item.content === 'string') candidates.push(item.content);
|
||||
|
||||
const nestedMessage = item.message && typeof item.message === 'object' ? item.message : null;
|
||||
if (nestedMessage) {
|
||||
if (typeof nestedMessage.content === 'string') candidates.push(nestedMessage.content);
|
||||
if (Array.isArray(nestedMessage.content)) {
|
||||
nestedMessage.content.forEach((entry) => {
|
||||
if (typeof entry === 'string') candidates.push(entry);
|
||||
if (entry?.type === 'text' && typeof entry?.text === 'string') candidates.push(entry.text);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(item.content)) {
|
||||
item.content.forEach((entry) => {
|
||||
if (typeof entry === 'string') candidates.push(entry);
|
||||
if (entry?.type === 'text' && typeof entry?.text === 'string') candidates.push(entry.text);
|
||||
});
|
||||
}
|
||||
|
||||
return candidates.some((value) => /<final>[\s\S]*?<\/final>/i.test(value));
|
||||
}
|
||||
|
||||
function extractOpenClawText(item) {
|
||||
if (typeof item === 'string') {
|
||||
return unwrapOpenClawFinal(item) || stripOpenClawFinalTags(item);
|
||||
}
|
||||
|
||||
if (!item || typeof item !== 'object') {
|
||||
return item ? String(item) : '';
|
||||
}
|
||||
|
||||
if (typeof item.text === 'string' && item.text.trim()) {
|
||||
return unwrapOpenClawFinal(item.text) || stripOpenClawFinalTags(item.text);
|
||||
}
|
||||
if (typeof item.message === 'string' && item.message.trim()) {
|
||||
return unwrapOpenClawFinal(item.message) || stripOpenClawFinalTags(item.message);
|
||||
}
|
||||
if (typeof item.content === 'string' && item.content.trim()) {
|
||||
return unwrapOpenClawFinal(item.content) || stripOpenClawFinalTags(item.content);
|
||||
}
|
||||
|
||||
const nestedMessage = item.message && typeof item.message === 'object' ? item.message : null;
|
||||
if (nestedMessage) {
|
||||
if (typeof nestedMessage.content === 'string' && nestedMessage.content.trim()) {
|
||||
return unwrapOpenClawFinal(nestedMessage.content) || stripOpenClawFinalTags(nestedMessage.content);
|
||||
}
|
||||
if (Array.isArray(nestedMessage.content)) {
|
||||
const textBlock = nestedMessage.content.find((entry) => entry?.type === 'text' && typeof entry?.text === 'string');
|
||||
if (textBlock?.text) {
|
||||
return unwrapOpenClawFinal(textBlock.text) || stripOpenClawFinalTags(textBlock.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(item.content)) {
|
||||
const textParts = item.content
|
||||
.map((entry) => {
|
||||
if (typeof entry === 'string') return entry;
|
||||
if (entry?.type === 'text' && typeof entry?.text === 'string') return entry.text;
|
||||
return '';
|
||||
})
|
||||
.filter(Boolean);
|
||||
if (textParts.length > 0) {
|
||||
const merged = textParts.join('\n');
|
||||
return unwrapOpenClawFinal(merged) || stripOpenClawFinalTags(merged);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof item.summary === 'string' && item.summary.trim()) {
|
||||
return item.summary;
|
||||
}
|
||||
if (typeof item.value === 'string' && item.value.trim()) {
|
||||
return item.value;
|
||||
}
|
||||
|
||||
return JSON.stringify(item);
|
||||
}
|
||||
|
||||
function normalizeOpenClawLiveEvent(evt) {
|
||||
const payload = evt?.payload || {};
|
||||
const nestedMessage = payload?.message && typeof payload.message === 'object' ? payload.message : null;
|
||||
const nestedMeta = nestedMessage?.__openclaw || payload?.__openclaw || null;
|
||||
const isFinal = hasOpenClawFinalTag(payload);
|
||||
const text = extractOpenClawText(payload) || evt?.event || '';
|
||||
const role =
|
||||
payload.role
|
||||
|| nestedMessage?.role
|
||||
|| payload.senderRole
|
||||
|| payload.kind
|
||||
|| evt?.event
|
||||
|| 'event';
|
||||
const seq = payload.messageSeq ?? payload.seq ?? nestedMeta?.seq ?? null;
|
||||
const messageId = payload.messageId ?? payload.id ?? nestedMeta?.id ?? null;
|
||||
|
||||
return {
|
||||
id: messageId || (seq !== null ? `seq:${seq}` : `${evt?.event || 'event'}:${Date.now()}`),
|
||||
role,
|
||||
text: String(text),
|
||||
timestamp: payload.timestamp || payload.ts || new Date().toISOString(),
|
||||
seq,
|
||||
messageId,
|
||||
isFinal,
|
||||
raw: payload,
|
||||
};
|
||||
}
|
||||
|
||||
function shouldAppendOpenClawLiveEvent(evt) {
|
||||
const name = String(evt?.event || '');
|
||||
const payload = evt?.payload || {};
|
||||
if (name === 'session.message') {
|
||||
return shouldKeepOpenClawMessage(payload);
|
||||
}
|
||||
return Boolean(payload.text || payload.message || payload.content);
|
||||
}
|
||||
|
||||
function requestOpenClawSessionHistory(clientRef, sessionKey, limit = 30) {
|
||||
const client = clientRef?.current;
|
||||
if (!client || !sessionKey) {
|
||||
return false;
|
||||
}
|
||||
return client.send(JSON.stringify({
|
||||
type: 'get_openclaw_session_history',
|
||||
session_key: sessionKey,
|
||||
limit,
|
||||
}));
|
||||
}
|
||||
|
||||
function normalizeOpenClawAgents(agents, presence, sessionsPayload = null) {
|
||||
const normalizedAgents = Array.isArray(agents) ? agents : [];
|
||||
const presenceAgents = presence?.agents || presence || {};
|
||||
const sessionDefaults = sessionsPayload?.defaults || {};
|
||||
const sessions = Array.isArray(sessionsPayload?.sessions) ? sessionsPayload.sessions : [];
|
||||
|
||||
const sessionModelByAgent = new Map();
|
||||
sessions.forEach((session) => {
|
||||
if (!session || typeof session !== 'object') return;
|
||||
let agentId = String(session.agentId || session.agent_id || '').trim();
|
||||
if (!agentId) {
|
||||
const key = String(session.key || session.sessionKey || '').trim();
|
||||
const parts = key.split(':');
|
||||
if (parts.length >= 3 && parts[0] === 'agent') {
|
||||
agentId = parts[1];
|
||||
}
|
||||
}
|
||||
const modelValue =
|
||||
session.model ||
|
||||
session.modelName ||
|
||||
session.model_name ||
|
||||
session.resolvedModel ||
|
||||
session.resolved_model ||
|
||||
null;
|
||||
if (agentId && modelValue && !sessionModelByAgent.has(agentId)) {
|
||||
sessionModelByAgent.set(agentId, modelValue);
|
||||
}
|
||||
});
|
||||
|
||||
return normalizedAgents.map((agent) => {
|
||||
if (!agent || typeof agent !== 'object') {
|
||||
return agent;
|
||||
}
|
||||
|
||||
const agentId = String(agent.id || agent.agentId || '').trim();
|
||||
const presenceEntry = agentId ? presenceAgents?.[agentId] : null;
|
||||
const presenceSessions = Array.isArray(presenceEntry?.sessions) ? presenceEntry.sessions : [];
|
||||
const firstPresenceSession = presenceSessions.find((session) => {
|
||||
const value = session?.model || session?.modelName || session?.model_name || session?.resolvedModel;
|
||||
return typeof value === 'string' && value.trim();
|
||||
});
|
||||
|
||||
const model =
|
||||
agent.model ||
|
||||
agent.modelName ||
|
||||
agent.model_name ||
|
||||
agent.resolvedModel ||
|
||||
agent.resolved_model ||
|
||||
agent.defaultModel ||
|
||||
agent.default_model ||
|
||||
sessionModelByAgent.get(agentId) ||
|
||||
sessionDefaults.model ||
|
||||
sessionDefaults.modelName ||
|
||||
sessionDefaults.model_name ||
|
||||
firstPresenceSession?.model ||
|
||||
firstPresenceSession?.modelName ||
|
||||
firstPresenceSession?.model_name ||
|
||||
firstPresenceSession?.resolvedModel ||
|
||||
null;
|
||||
|
||||
return {
|
||||
...agent,
|
||||
model: typeof model === 'string' && model.trim() ? model.trim() : null,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom hook for WebSocket connection lifecycle and event handling.
|
||||
* Manages clientRef, connection, and ALL event handlers.
|
||||
@@ -805,7 +1105,15 @@ export function useWebSocketConnection({
|
||||
useOpenClawStore.getState().setStatusLoading(false);
|
||||
},
|
||||
openclaw_sessions_loaded: (e) => {
|
||||
useOpenClawStore.getState().setOpenclawSessions(e.data || e);
|
||||
const payload = e.data || e;
|
||||
useOpenClawStore.getState().setOpenclawSessions(payload);
|
||||
const currentAgents = useOpenClawStore.getState().agents || [];
|
||||
const presence = useOpenClawStore.getState().agentsPresence;
|
||||
if (currentAgents.length > 0) {
|
||||
useOpenClawStore.getState().setAgents(
|
||||
normalizeOpenClawAgents(currentAgents, presence, payload),
|
||||
);
|
||||
}
|
||||
useOpenClawStore.getState().setSessionsLoading(false);
|
||||
},
|
||||
openclaw_session_detail_loaded: (e) => {
|
||||
@@ -813,7 +1121,120 @@ export function useWebSocketConnection({
|
||||
useOpenClawStore.getState().setSessionDetailLoading(false);
|
||||
},
|
||||
openclaw_session_history_loaded: (e) => {
|
||||
useOpenClawStore.getState().setOpenclawSessionHistory(e.data || e);
|
||||
const data = e.data || e;
|
||||
const sessionKey = e.session_key || data?.session_key || useOpenClawStore.getState().selectedSessionKey;
|
||||
useOpenClawStore.getState().setOpenclawSessionHistory(data);
|
||||
if (sessionKey) {
|
||||
useOpenClawStore.getState().replaceOpenclawChatHistory(
|
||||
sessionKey,
|
||||
normalizeOpenClawHistoryItems(data?.history || []),
|
||||
);
|
||||
}
|
||||
},
|
||||
openclaw_session_resolved: (e) => {
|
||||
const d = e.data || {};
|
||||
useOpenClawStore.getState().setOpenclawResolvedSessionKey(d.key || null);
|
||||
if (d?.error) {
|
||||
useOpenClawStore.getState().setChatError(d.error);
|
||||
} else {
|
||||
useOpenClawStore.getState().setChatError(null);
|
||||
}
|
||||
},
|
||||
openclaw_session_created: (e) => {
|
||||
const d = e.data || {};
|
||||
if (d?.error) {
|
||||
useOpenClawStore.getState().setChatError(d.error);
|
||||
return;
|
||||
}
|
||||
if (d?.entry || d?.key) {
|
||||
const createdKey = d?.key || d?.entry?.key || d?.entry?.sessionKey || '';
|
||||
useOpenClawStore.getState().appendOpenclawSession(
|
||||
d.entry || {
|
||||
key: createdKey,
|
||||
sessionKey: createdKey,
|
||||
agentId: String(createdKey).split(':')[1] || '',
|
||||
}
|
||||
);
|
||||
}
|
||||
if (d?.key) {
|
||||
useOpenClawStore.getState().setSelectedSessionKey(d.key);
|
||||
useOpenClawStore.getState().setOpenclawResolvedSessionKey(d.key);
|
||||
useOpenClawStore.getState().setChatError(null);
|
||||
}
|
||||
},
|
||||
openclaw_session_subscribed: (e) => {
|
||||
const sessionKey = e.session_key || e.data?.key || null;
|
||||
if (sessionKey) {
|
||||
useOpenClawStore.getState().setOpenclawSessionSubscribed(sessionKey, true);
|
||||
}
|
||||
if (e.data?.error) {
|
||||
useOpenClawStore.getState().setChatError(e.data.error);
|
||||
}
|
||||
},
|
||||
openclaw_session_unsubscribed: (e) => {
|
||||
const sessionKey = e.session_key || e.data?.key || null;
|
||||
if (sessionKey) {
|
||||
useOpenClawStore.getState().setOpenclawSessionSubscribed(sessionKey, false);
|
||||
}
|
||||
},
|
||||
openclaw_session_reset: (e) => {
|
||||
const sessionKey = e.session_key || e.data?.key || null;
|
||||
if (e.data?.error) {
|
||||
useOpenClawStore.getState().setChatError(e.data.error);
|
||||
return;
|
||||
}
|
||||
if (sessionKey) {
|
||||
useOpenClawStore.getState().replaceOpenclawChatHistory(sessionKey, []);
|
||||
useOpenClawStore.getState().setChatError(null);
|
||||
}
|
||||
},
|
||||
openclaw_session_deleted: (e) => {
|
||||
const sessionKey = e.session_key || e.data?.key || null;
|
||||
if (e.data?.error) {
|
||||
useOpenClawStore.getState().setChatError(e.data.error);
|
||||
return;
|
||||
}
|
||||
if (sessionKey) {
|
||||
useOpenClawStore.getState().removeOpenclawSession(sessionKey);
|
||||
useOpenClawStore.getState().setChatError(null);
|
||||
}
|
||||
},
|
||||
openclaw_message_sent: (e) => {
|
||||
const sessionKey = e.session_key || e.data?.key || useOpenClawStore.getState().selectedSessionKey;
|
||||
if (sessionKey) {
|
||||
useOpenClawStore.getState().setOpenclawChatSendingForSession(sessionKey, false);
|
||||
}
|
||||
if (e.data?.error) {
|
||||
useOpenClawStore.getState().setChatError(e.data.error);
|
||||
} else {
|
||||
useOpenClawStore.getState().setChatError(null);
|
||||
if (sessionKey && (e.data?.status || e.data?.runId || e.data?.messageSeq !== undefined)) {
|
||||
const statusBits = [
|
||||
e.data?.status ? `status=${e.data.status}` : null,
|
||||
e.data?.runId ? `runId=${e.data.runId}` : null,
|
||||
e.data?.messageSeq !== undefined ? `seq=${e.data.messageSeq}` : null,
|
||||
].filter(Boolean);
|
||||
useOpenClawStore.getState().appendOpenclawChatMessage(sessionKey, {
|
||||
id: `send-meta:${e.data?.runId || Date.now()}`,
|
||||
role: 'system',
|
||||
text: `消息已提交到 OpenClaw${statusBits.length ? ` (${statusBits.join(', ')})` : ''}`,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
if (sessionKey) {
|
||||
window.setTimeout(() => requestOpenClawSessionHistory(clientRef, sessionKey, 30), 600);
|
||||
}
|
||||
}
|
||||
},
|
||||
openclaw_session_event: (e) => {
|
||||
const sessionKey = e.session_key || e.payload?.sessionKey || e.payload?.key;
|
||||
if (!sessionKey || !shouldAppendOpenClawLiveEvent(e)) {
|
||||
return;
|
||||
}
|
||||
useOpenClawStore.getState().appendOpenclawChatMessage(
|
||||
sessionKey,
|
||||
normalizeOpenClawLiveEvent(e),
|
||||
);
|
||||
},
|
||||
openclaw_cron_loaded: (e) => {
|
||||
useOpenClawStore.getState().setOpenclawCronJobs(e.data || e);
|
||||
@@ -829,12 +1250,30 @@ export function useWebSocketConnection({
|
||||
if (d?.error) {
|
||||
useOpenClawStore.getState().setAgentsError(d.error);
|
||||
} else {
|
||||
useOpenClawStore.getState().setAgents(d?.agents || []);
|
||||
const presence = useOpenClawStore.getState().agentsPresence;
|
||||
const sessionsPayload = {
|
||||
sessions: useOpenClawStore.getState().openclawSessions || [],
|
||||
defaults: useOpenClawStore.getState().openclawSessionsDefaults || null,
|
||||
};
|
||||
useOpenClawStore.getState().setAgents(
|
||||
normalizeOpenClawAgents(d?.agents || [], presence, sessionsPayload),
|
||||
);
|
||||
useOpenClawStore.getState().setAgentsError(null);
|
||||
}
|
||||
},
|
||||
openclaw_agents_presence_loaded: (e) => {
|
||||
useOpenClawStore.getState().setAgentsPresence((e.data?.data ?? e.data) || {});
|
||||
const presencePayload = (e.data?.data ?? e.data) || {};
|
||||
useOpenClawStore.getState().setAgentsPresence(presencePayload);
|
||||
const currentAgents = useOpenClawStore.getState().agents || [];
|
||||
if (currentAgents.length > 0) {
|
||||
const sessionsPayload = {
|
||||
sessions: useOpenClawStore.getState().openclawSessions || [],
|
||||
defaults: useOpenClawStore.getState().openclawSessionsDefaults || null,
|
||||
};
|
||||
useOpenClawStore.getState().setAgents(
|
||||
normalizeOpenClawAgents(currentAgents, presencePayload, sessionsPayload),
|
||||
);
|
||||
}
|
||||
},
|
||||
openclaw_skills_loaded: (e) => {
|
||||
useOpenClawStore.getState().setSkillsLoading(false);
|
||||
|
||||
@@ -7,10 +7,16 @@ export const useOpenClawStore = create(
|
||||
// Raw data
|
||||
openclawStatus: null,
|
||||
openclawSessions: [],
|
||||
openclawSessionsDefaults: null,
|
||||
openclawSessionDetail: null,
|
||||
openclawSessionHistory: [],
|
||||
openclawCronJobs: [],
|
||||
openclawApprovals: [],
|
||||
openclawResolvedSessionKey: null,
|
||||
openclawChatMessagesBySession: {},
|
||||
openclawChatDraftBySession: {},
|
||||
openclawChatSendingBySession: {},
|
||||
openclawSessionSubscriptions: {},
|
||||
|
||||
// Loading states
|
||||
isStatusLoading: false,
|
||||
@@ -18,6 +24,7 @@ export const useOpenClawStore = create(
|
||||
isSessionDetailLoading: false,
|
||||
isCronLoading: false,
|
||||
isApprovalsLoading: false,
|
||||
isChatSending: false,
|
||||
|
||||
// Error states
|
||||
statusError: null,
|
||||
@@ -25,6 +32,7 @@ export const useOpenClawStore = create(
|
||||
sessionDetailError: null,
|
||||
cronError: null,
|
||||
approvalsError: null,
|
||||
chatError: null,
|
||||
|
||||
// Agents state
|
||||
agents: [],
|
||||
@@ -119,11 +127,129 @@ export const useOpenClawStore = create(
|
||||
|
||||
// Setters
|
||||
setOpenclawStatus: (data) => set({ openclawStatus: data, statusError: null }),
|
||||
setOpenclawSessions: (data) => set({ openclawSessions: data?.sessions || [], sessionsError: null }),
|
||||
setOpenclawSessionDetail: (data) => set({ openclawSessionDetail: data?.session || null, sessionDetailError: null }),
|
||||
setOpenclawSessionHistory: (data) => set({ openclawSessionHistory: data?.history || [], sessionDetailError: null }),
|
||||
setOpenclawSessions: (data) => set({
|
||||
openclawSessions: data?.sessions || [],
|
||||
openclawSessionsDefaults: data?.defaults || null,
|
||||
sessionsError: null,
|
||||
}),
|
||||
appendOpenclawSession: (session) => set((state) => {
|
||||
const key = session?.key || session?.sessionKey;
|
||||
if (!key) {
|
||||
return {};
|
||||
}
|
||||
const existing = state.openclawSessions || [];
|
||||
const deduped = existing.filter((item) => (item?.key || item?.sessionKey) !== key);
|
||||
return { openclawSessions: [session, ...deduped] };
|
||||
}),
|
||||
removeOpenclawSession: (sessionKey) => set((state) => ({
|
||||
openclawSessions: (state.openclawSessions || []).filter(
|
||||
(item) => (item?.key || item?.sessionKey) !== sessionKey
|
||||
),
|
||||
selectedSessionKey:
|
||||
state.selectedSessionKey === sessionKey ? null : state.selectedSessionKey,
|
||||
})),
|
||||
setOpenclawSessionDetail: (data) => set({ openclawSessionDetail: data?.session || null, sessionDetailError: data?.error || null }),
|
||||
setOpenclawSessionHistory: (data) => set({ openclawSessionHistory: data?.history || [], sessionDetailError: data?.error || null }),
|
||||
setOpenclawCronJobs: (data) => set({ openclawCronJobs: data?.cron || [], cronError: null }),
|
||||
setOpenclawApprovals: (data) => set({ openclawApprovals: data?.approvals || [], approvalsError: null }),
|
||||
setOpenclawResolvedSessionKey: (key) => set({ openclawResolvedSessionKey: key || null }),
|
||||
setOpenclawChatDraft: (sessionKey, value) => set((state) => ({
|
||||
openclawChatDraftBySession: { ...state.openclawChatDraftBySession, [sessionKey]: value },
|
||||
})),
|
||||
appendOpenclawChatMessage: (sessionKey, message) => set((state) => {
|
||||
const current = state.openclawChatMessagesBySession[sessionKey] || [];
|
||||
const sameMessageIndex = current.findIndex((item) => {
|
||||
const sameId = Boolean(message?.id && item?.id && message.id === item.id);
|
||||
const sameMessageId = Boolean(
|
||||
message?.messageId &&
|
||||
item?.messageId &&
|
||||
message.messageId === item.messageId
|
||||
);
|
||||
const sameSeq = Boolean(
|
||||
message?.seq !== undefined &&
|
||||
message?.seq !== null &&
|
||||
item?.seq !== undefined &&
|
||||
item?.seq !== null &&
|
||||
message.seq === item.seq &&
|
||||
message?.role === item?.role
|
||||
);
|
||||
const incomingText = String(message?.text || '').trim();
|
||||
const existingText = String(item?.text || '').trim();
|
||||
const incomingTs = Date.parse(message?.timestamp || '');
|
||||
const existingTs = Date.parse(item?.timestamp || '');
|
||||
const nearInTime =
|
||||
Number.isFinite(incomingTs) &&
|
||||
Number.isFinite(existingTs) &&
|
||||
Math.abs(incomingTs - existingTs) < 1500;
|
||||
const sameAssistantText =
|
||||
message?.role === 'assistant' &&
|
||||
item?.role === 'assistant' &&
|
||||
incomingText &&
|
||||
existingText &&
|
||||
(
|
||||
incomingText === existingText ||
|
||||
incomingText.startsWith(existingText) ||
|
||||
existingText.startsWith(incomingText)
|
||||
) &&
|
||||
nearInTime;
|
||||
return sameId || sameMessageId || sameSeq || sameAssistantText;
|
||||
});
|
||||
|
||||
if (sameMessageIndex >= 0) {
|
||||
const next = [...current];
|
||||
next[sameMessageIndex] = { ...next[sameMessageIndex], ...message };
|
||||
return {
|
||||
openclawChatMessagesBySession: {
|
||||
...state.openclawChatMessagesBySession,
|
||||
[sessionKey]: next,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
openclawChatMessagesBySession: {
|
||||
...state.openclawChatMessagesBySession,
|
||||
[sessionKey]: [...current, message],
|
||||
},
|
||||
};
|
||||
}),
|
||||
replaceOpenclawChatHistory: (sessionKey, messages) => set((state) => {
|
||||
const incoming = Array.isArray(messages) ? messages : [];
|
||||
const existing = state.openclawChatMessagesBySession[sessionKey] || [];
|
||||
const merged = [];
|
||||
const seen = new Set();
|
||||
|
||||
const signatureFor = (message) => {
|
||||
if (!message) return "";
|
||||
if (message.id) return `id:${message.id}`;
|
||||
if (message.messageId) return `mid:${message.messageId}`;
|
||||
if (message.seq !== undefined && message.seq !== null) return `seq:${message.seq}`;
|
||||
return `txt:${message.role || ""}:${String(message.text || "").trim()}`;
|
||||
};
|
||||
|
||||
for (const message of [...incoming, ...existing]) {
|
||||
const signature = signatureFor(message);
|
||||
if (!signature || seen.has(signature)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(signature);
|
||||
merged.push(message);
|
||||
}
|
||||
|
||||
return {
|
||||
openclawChatMessagesBySession: {
|
||||
...state.openclawChatMessagesBySession,
|
||||
[sessionKey]: merged,
|
||||
},
|
||||
};
|
||||
}),
|
||||
setOpenclawChatSendingForSession: (sessionKey, value) => set((state) => ({
|
||||
openclawChatSendingBySession: { ...state.openclawChatSendingBySession, [sessionKey]: Boolean(value) },
|
||||
isChatSending: Boolean(value),
|
||||
})),
|
||||
setOpenclawSessionSubscribed: (sessionKey, value) => set((state) => ({
|
||||
openclawSessionSubscriptions: { ...state.openclawSessionSubscriptions, [sessionKey]: Boolean(value) },
|
||||
})),
|
||||
|
||||
setSelectedSessionKey: (key) => set({ selectedSessionKey: key }),
|
||||
|
||||
@@ -138,6 +264,7 @@ export const useOpenClawStore = create(
|
||||
setSessionDetailError: (e) => set({ sessionDetailError: e }),
|
||||
setCronError: (e) => set({ cronError: e }),
|
||||
setApprovalsError: (e) => set({ approvalsError: e }),
|
||||
setChatError: (e) => set({ chatError: e }),
|
||||
|
||||
setAgents: (agents) => set({ agents }),
|
||||
setAgentsLoading: (loading) => set({ agentsLoading: loading }),
|
||||
|
||||
Reference in New Issue
Block a user