mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
feat: migrate conversation orchestration to Rust-side Tauri commands
- Added `chat_send` and `chat_cancel` Tauri commands for backend-driven conversation loops. - Moved agentic loop logic from frontend to Rust, optimizing performance and reducing frontend responsibilities. - Implemented event protocol (`chat:tool_call`, `chat:tool_result`, `chat:done`, `chat:error`) to communicate loop progress to the frontend. - Introduced AI config loading, OpenClaw context building, and cancellation support for chat requests.
This commit is contained in:
@@ -54,6 +54,7 @@
|
||||
"@tauri-apps/plugin-os": "^2.3.2",
|
||||
"@tauri-apps/plugin-shell": "~2",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/three": "^0.183.1",
|
||||
"buffer": "^6.0.3",
|
||||
"debug": "^4.4.3",
|
||||
"immer": "^11.1.3",
|
||||
@@ -70,6 +71,7 @@
|
||||
"redux-persist": "^6.0.0",
|
||||
"socket.io-client": "^4.8.3",
|
||||
"telegram": "^2.26.22",
|
||||
"three": "^0.183.2",
|
||||
"util": "^0.12.5",
|
||||
"zustand": "^5.0.10"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
pub mod auth;
|
||||
pub mod chat;
|
||||
pub mod memory;
|
||||
pub mod model;
|
||||
pub mod runtime;
|
||||
@@ -11,6 +12,7 @@ pub mod window;
|
||||
|
||||
// Re-export all commands for registration
|
||||
pub use auth::*;
|
||||
pub use chat::{chat_cancel, chat_send};
|
||||
pub use memory::*;
|
||||
pub use model::*;
|
||||
pub use runtime::*;
|
||||
|
||||
@@ -20,6 +20,7 @@ mod unified_skills;
|
||||
mod utils;
|
||||
|
||||
use ai::*;
|
||||
use commands::chat::ChatState;
|
||||
use commands::unified_skills::{
|
||||
unified_execute_skill, unified_generate_skill, unified_list_skills,
|
||||
unified_self_evolve_skill,
|
||||
@@ -648,6 +649,11 @@ pub fn run() {
|
||||
app.manage(commands::memory::MemoryState(std::sync::Mutex::new(None)));
|
||||
log::info!("[memory] Memory state registered — awaiting JWT from frontend");
|
||||
|
||||
// Initialize ChatState for managing in-flight conversation requests
|
||||
let chat_state = std::sync::Arc::new(ChatState::new());
|
||||
app.manage(chat_state);
|
||||
log::info!("[chat] ChatState registered");
|
||||
|
||||
// Store SocketManager as Tauri state
|
||||
app.manage(socket_mgr.clone());
|
||||
|
||||
@@ -798,6 +804,9 @@ pub fn run() {
|
||||
init_memory_client,
|
||||
memory_query,
|
||||
recall_memory,
|
||||
// Chat commands (agentic conversation loop)
|
||||
chat_send,
|
||||
chat_cancel,
|
||||
]
|
||||
}
|
||||
#[cfg(not(desktop))]
|
||||
@@ -918,6 +927,9 @@ pub fn run() {
|
||||
init_memory_client,
|
||||
memory_query,
|
||||
recall_memory,
|
||||
// Chat commands (agentic conversation loop)
|
||||
chat_send,
|
||||
chat_cancel,
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"resources": [
|
||||
"../skills/skills"
|
||||
"../skills/skills",
|
||||
"../ai"
|
||||
],
|
||||
"macOS": {
|
||||
"minimumSystemVersion": "10.15",
|
||||
|
||||
+219
-48
@@ -18,8 +18,18 @@ import {
|
||||
type ModelInfo,
|
||||
type Tool,
|
||||
} from '../services/api/inferenceApi';
|
||||
import {
|
||||
chatCancel,
|
||||
chatSend,
|
||||
subscribeChatEvents,
|
||||
useRustChat,
|
||||
type ChatToolCallEvent,
|
||||
type ChatToolResultEvent,
|
||||
} from '../services/chatService';
|
||||
import { useAppDispatch, useAppSelector } from '../store/hooks';
|
||||
import type { NotionPageSummary, NotionSummary, NotionUserProfile } from '../store/notionSlice';
|
||||
import { store } from '../store';
|
||||
import { BACKEND_URL } from '../utils/config';
|
||||
import {
|
||||
addInferenceResponse,
|
||||
addMessageLocal,
|
||||
@@ -141,6 +151,17 @@ const Conversations = () => {
|
||||
const [isLoadingModels, setIsLoadingModels] = useState(false);
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const [sendError, setSendError] = useState<string | null>(null);
|
||||
const [activeToolCall, setActiveToolCall] = useState<{ name: string; args: unknown } | null>(
|
||||
null
|
||||
);
|
||||
const rustChat = useRustChat();
|
||||
|
||||
// Ref to track selectedThreadId inside event callbacks without re-subscribing
|
||||
const selectedThreadIdRef = useRef(selectedThreadId);
|
||||
useEffect(() => {
|
||||
selectedThreadIdRef.current = selectedThreadId;
|
||||
}, [selectedThreadId]);
|
||||
|
||||
// Budget state
|
||||
const [teamUsage, setTeamUsage] = useState<TeamUsage | null>(null);
|
||||
const [isLoadingBudget, setIsLoadingBudget] = useState(false);
|
||||
@@ -288,6 +309,78 @@ const Conversations = () => {
|
||||
}
|
||||
}, [inputValue, sendError]);
|
||||
|
||||
// Subscribe to Rust chat events when running in Tauri.
|
||||
// Registered ONCE (deps: [rustChat]) — uses refs for values that change.
|
||||
useEffect(() => {
|
||||
if (!rustChat) return;
|
||||
|
||||
let cleanup: (() => void) | null = null;
|
||||
let mounted = true;
|
||||
|
||||
subscribeChatEvents({
|
||||
onToolCall: (event: ChatToolCallEvent) => {
|
||||
if (event.thread_id !== selectedThreadIdRef.current) return;
|
||||
setActiveToolCall({ name: event.tool_name, args: event.args });
|
||||
},
|
||||
onToolResult: (event: ChatToolResultEvent) => {
|
||||
if (event.thread_id !== selectedThreadIdRef.current) return;
|
||||
setActiveToolCall(null);
|
||||
},
|
||||
onDone: event => {
|
||||
// Guard against duplicate dispatch (React StrictMode double-fires effects in dev)
|
||||
const currentState = store.getState() as {
|
||||
thread: { messagesByThreadId: Record<string, ThreadMessage[]> };
|
||||
};
|
||||
const threadMessages = currentState.thread.messagesByThreadId[event.thread_id] || [];
|
||||
const lastMsg = threadMessages[threadMessages.length - 1];
|
||||
if (lastMsg?.sender === 'agent' && lastMsg?.content === event.full_response) {
|
||||
return; // Already added — skip duplicate
|
||||
}
|
||||
|
||||
dispatch(addInferenceResponse({ content: event.full_response, threadId: event.thread_id }));
|
||||
setIsSending(false);
|
||||
setActiveToolCall(null);
|
||||
dispatch(setActiveThread(null));
|
||||
},
|
||||
onError: event => {
|
||||
if (event.thread_id !== selectedThreadIdRef.current) return;
|
||||
if (event.error_type !== 'cancelled') {
|
||||
setSendError(event.message);
|
||||
}
|
||||
setIsSending(false);
|
||||
setActiveToolCall(null);
|
||||
dispatch(setActiveThread(null));
|
||||
|
||||
// Remove the optimistic user message on error
|
||||
dispatch((innerDispatch, getState) => {
|
||||
const state = getState() as {
|
||||
thread: { messagesByThreadId: Record<string, ThreadMessage[]> };
|
||||
};
|
||||
const persistedMessages = state.thread.messagesByThreadId[event.thread_id] || [];
|
||||
const lastUserIdx = [...persistedMessages]
|
||||
.reverse()
|
||||
.findIndex(m => m.sender === 'user');
|
||||
if (lastUserIdx !== -1) {
|
||||
const actualIdx = persistedMessages.length - 1 - lastUserIdx;
|
||||
const updated = persistedMessages.filter((_, i) => i !== actualIdx);
|
||||
innerDispatch(updateMessagesForThread({ threadId: event.thread_id, messages: updated }));
|
||||
if (event.thread_id === selectedThreadIdRef.current) {
|
||||
innerDispatch(setSelectedThread(event.thread_id));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
}).then(fn => {
|
||||
if (mounted) cleanup = fn;
|
||||
});
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
cleanup?.();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [rustChat]);
|
||||
|
||||
const handleSelectThread = (threadId: string) => {
|
||||
if (threadId === selectedThreadId) return;
|
||||
navigate(`/conversations/${threadId}`, { replace: true });
|
||||
@@ -321,49 +414,14 @@ const Conversations = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendMessage = async (text?: string) => {
|
||||
const trimmed = text ?? inputValue.trim();
|
||||
if (!trimmed || !selectedThreadId || isSending) return;
|
||||
|
||||
// Check if another thread is already sending
|
||||
if (activeThreadId && activeThreadId !== selectedThreadId) {
|
||||
return; // Block sending from non-active threads
|
||||
}
|
||||
|
||||
// Store the original thread ID to ensure response goes to correct thread
|
||||
const sendingThreadId = selectedThreadId;
|
||||
|
||||
// Create stable user message and persist immediately
|
||||
const userMessage: ThreadMessage = {
|
||||
id: `msg_${Date.now()}_${Math.random()}`,
|
||||
content: trimmed,
|
||||
type: 'text',
|
||||
extraMetadata: {},
|
||||
sender: 'user',
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Immediately persist user message to both current view and persistent storage
|
||||
dispatch(addMessageLocal({ threadId: sendingThreadId, message: userMessage }));
|
||||
|
||||
// Update current view if this is the selected thread
|
||||
if (sendingThreadId === selectedThreadId) {
|
||||
// Message is already added to persistent storage, reload current view
|
||||
dispatch(setSelectedThread(sendingThreadId));
|
||||
}
|
||||
|
||||
// Snapshot history for AI request (excluding the just-added user message since we'll add it manually)
|
||||
const historySnapshot = messages.filter(
|
||||
m => !m.id.startsWith('optimistic-') && m.id !== userMessage.id
|
||||
);
|
||||
|
||||
setInputValue('');
|
||||
setSendError(null);
|
||||
setIsSending(true);
|
||||
|
||||
// Set this thread as active
|
||||
dispatch(setActiveThread(sendingThreadId));
|
||||
|
||||
// Web fallback: the original orchestration logic, preserved exactly as-is.
|
||||
// Called when not running in Tauri.
|
||||
const handleSendMessageWeb = async (
|
||||
sendingThreadId: string,
|
||||
trimmed: string,
|
||||
userMessage: ThreadMessage,
|
||||
historySnapshot: ThreadMessage[]
|
||||
) => {
|
||||
// Safety-net timeout: force-clear loading states if everything hangs
|
||||
const OVERALL_TIMEOUT_MS = 240_000;
|
||||
const safetyTimeout = setTimeout(() => {
|
||||
@@ -387,7 +445,7 @@ const Conversations = () => {
|
||||
const { invoke } = await import('@tauri-apps/api/core');
|
||||
const recalledContext = await invoke<string | null>('recall_memory', {
|
||||
skillId: 'conversations',
|
||||
integrationId: selectedThreadId,
|
||||
integrationId: sendingThreadId,
|
||||
maxChunks: 10,
|
||||
});
|
||||
if (recalledContext) {
|
||||
@@ -519,7 +577,10 @@ const Conversations = () => {
|
||||
skillManager.callTool(skillId, toolName, toolArgs),
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(
|
||||
() => reject(new Error(`Tool "${toolName}" timed out after ${TOOL_TIMEOUT_MS / 1000}s`)),
|
||||
() =>
|
||||
reject(
|
||||
new Error(`Tool "${toolName}" timed out after ${TOOL_TIMEOUT_MS / 1000}s`)
|
||||
),
|
||||
TOOL_TIMEOUT_MS
|
||||
)
|
||||
),
|
||||
@@ -575,17 +636,19 @@ const Conversations = () => {
|
||||
} catch (err) {
|
||||
// Remove the user message from persistent storage on error
|
||||
// We'll use a thunk-like approach to access current state
|
||||
dispatch((dispatch, getState) => {
|
||||
dispatch((innerDispatch, getState) => {
|
||||
const state = getState() as {
|
||||
thread: { messagesByThreadId: Record<string, ThreadMessage[]> };
|
||||
};
|
||||
const persistedMessages = state.thread.messagesByThreadId[sendingThreadId] || [];
|
||||
const currentMessages = persistedMessages.filter(m => m.id !== userMessage.id);
|
||||
dispatch(updateMessagesForThread({ threadId: sendingThreadId, messages: currentMessages }));
|
||||
innerDispatch(
|
||||
updateMessagesForThread({ threadId: sendingThreadId, messages: currentMessages })
|
||||
);
|
||||
|
||||
// Also remove from current view if this is the selected thread
|
||||
if (sendingThreadId === selectedThreadId) {
|
||||
dispatch(setSelectedThread(sendingThreadId));
|
||||
innerDispatch(setSelectedThread(sendingThreadId));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -602,6 +665,96 @@ const Conversations = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendMessage = async (text?: string) => {
|
||||
const trimmed = text ?? inputValue.trim();
|
||||
if (!trimmed || !selectedThreadId || isSending) return;
|
||||
|
||||
// Check if another thread is already sending
|
||||
if (activeThreadId && activeThreadId !== selectedThreadId) {
|
||||
return; // Block sending from non-active threads
|
||||
}
|
||||
|
||||
// Store the original thread ID to ensure response goes to correct thread
|
||||
const sendingThreadId = selectedThreadId;
|
||||
|
||||
// Create stable user message and persist immediately
|
||||
const userMessage: ThreadMessage = {
|
||||
id: `msg_${Date.now()}_${Math.random()}`,
|
||||
content: trimmed,
|
||||
type: 'text',
|
||||
extraMetadata: {},
|
||||
sender: 'user',
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Immediately persist user message to both current view and persistent storage
|
||||
dispatch(addMessageLocal({ threadId: sendingThreadId, message: userMessage }));
|
||||
|
||||
// Update current view if this is the selected thread
|
||||
if (sendingThreadId === selectedThreadId) {
|
||||
// Message is already added to persistent storage, reload current view
|
||||
dispatch(setSelectedThread(sendingThreadId));
|
||||
}
|
||||
|
||||
// Snapshot history for AI request (excluding the just-added user message since we'll add it manually)
|
||||
const historySnapshot = messages.filter(
|
||||
m => !m.id.startsWith('optimistic-') && m.id !== userMessage.id
|
||||
);
|
||||
|
||||
setInputValue('');
|
||||
setSendError(null);
|
||||
setIsSending(true);
|
||||
|
||||
// Set this thread as active
|
||||
dispatch(setActiveThread(sendingThreadId));
|
||||
|
||||
if (rustChat) {
|
||||
// ── Rust path ────────────────────────────────────────────────────────
|
||||
try {
|
||||
const chatMessages = historySnapshot.map(m => ({
|
||||
role: m.sender === 'user' ? 'user' : 'assistant',
|
||||
content: m.content,
|
||||
}));
|
||||
|
||||
const notionCtx = buildNotionContext(
|
||||
notionProfile,
|
||||
notionPages,
|
||||
notionSummaries,
|
||||
notionWorkspaceName
|
||||
);
|
||||
|
||||
const authToken = (store.getState() as { auth: { token: string | null } }).auth.token;
|
||||
if (!authToken) {
|
||||
setSendError('Not authenticated');
|
||||
setIsSending(false);
|
||||
dispatch(setActiveThread(null));
|
||||
return;
|
||||
}
|
||||
|
||||
await chatSend({
|
||||
threadId: sendingThreadId,
|
||||
message: trimmed,
|
||||
model: selectedModel,
|
||||
authToken,
|
||||
backendUrl: BACKEND_URL,
|
||||
messages: chatMessages,
|
||||
notionContext: notionCtx,
|
||||
});
|
||||
|
||||
// setIsSending(false) and setActiveThread(null) happen in the onDone/onError event handlers
|
||||
} catch (err) {
|
||||
// invoke() itself failed (the chat loop reports errors via events)
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
setSendError(msg);
|
||||
setIsSending(false);
|
||||
dispatch(setActiveThread(null));
|
||||
}
|
||||
} else {
|
||||
// ── Web fallback (existing orchestration logic) ───────────────────────
|
||||
await handleSendMessageWeb(sendingThreadId, trimmed, userMessage, historySnapshot);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
@@ -1010,6 +1163,24 @@ const Conversations = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Tool call indicator — shown when Rust backend is executing a tool */}
|
||||
{activeToolCall && isSending && (
|
||||
<div className="flex items-center gap-2 px-1 py-1 text-xs text-stone-400">
|
||||
<span className="animate-pulse">Running tool: {activeToolCall.name}</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Cancel button — shown when Rust backend is processing */}
|
||||
{isSending && rustChat && (
|
||||
<div className="flex justify-start px-1">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (selectedThreadId) void chatCancel(selectedThreadId);
|
||||
}}
|
||||
className="text-xs text-stone-400 hover:text-stone-200 transition-colors">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Chat Service — sends messages via Rust backend (Tauri) or falls back to
|
||||
* frontend-driven orchestration (web mode).
|
||||
*/
|
||||
import { isTauri as coreIsTauri, invoke } from '@tauri-apps/api/core';
|
||||
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
|
||||
|
||||
// ─── Event payload types (must match Rust structs exactly — snake_case) ───────
|
||||
|
||||
export interface ChatToolCallEvent {
|
||||
thread_id: string;
|
||||
tool_name: string;
|
||||
skill_id: string;
|
||||
args: Record<string, unknown>;
|
||||
round: number;
|
||||
}
|
||||
|
||||
export interface ChatToolResultEvent {
|
||||
thread_id: string;
|
||||
tool_name: string;
|
||||
skill_id: string;
|
||||
output: string;
|
||||
success: boolean;
|
||||
round: number;
|
||||
}
|
||||
|
||||
export interface ChatDoneEvent {
|
||||
thread_id: string;
|
||||
full_response: string;
|
||||
rounds_used: number;
|
||||
total_input_tokens: number;
|
||||
total_output_tokens: number;
|
||||
}
|
||||
|
||||
export interface ChatErrorEvent {
|
||||
thread_id: string;
|
||||
message: string;
|
||||
error_type: 'network' | 'timeout' | 'tool_error' | 'inference' | 'cancelled';
|
||||
round: number | null;
|
||||
}
|
||||
|
||||
// ─── Listener setup ───────────────────────────────────────────────────────────
|
||||
|
||||
export interface ChatEventListeners {
|
||||
onToolCall?: (event: ChatToolCallEvent) => void;
|
||||
onToolResult?: (event: ChatToolResultEvent) => void;
|
||||
onDone?: (event: ChatDoneEvent) => void;
|
||||
onError?: (event: ChatErrorEvent) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to chat events from the Rust backend.
|
||||
* Returns a cleanup function that removes all listeners.
|
||||
* Only works in Tauri mode.
|
||||
*/
|
||||
export async function subscribeChatEvents(listeners: ChatEventListeners): Promise<() => void> {
|
||||
const unlisteners: UnlistenFn[] = [];
|
||||
|
||||
if (listeners.onToolCall) {
|
||||
const cb = listeners.onToolCall;
|
||||
unlisteners.push(await listen<ChatToolCallEvent>('chat:tool_call', e => cb(e.payload)));
|
||||
}
|
||||
if (listeners.onToolResult) {
|
||||
const cb = listeners.onToolResult;
|
||||
unlisteners.push(await listen<ChatToolResultEvent>('chat:tool_result', e => cb(e.payload)));
|
||||
}
|
||||
if (listeners.onDone) {
|
||||
const cb = listeners.onDone;
|
||||
unlisteners.push(await listen<ChatDoneEvent>('chat:done', e => cb(e.payload)));
|
||||
}
|
||||
if (listeners.onError) {
|
||||
const cb = listeners.onError;
|
||||
unlisteners.push(await listen<ChatErrorEvent>('chat:error', e => cb(e.payload)));
|
||||
}
|
||||
|
||||
return () => {
|
||||
for (const unlisten of unlisteners) {
|
||||
unlisten();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Send message ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface ChatSendParams {
|
||||
threadId: string;
|
||||
message: string;
|
||||
model: string;
|
||||
authToken: string;
|
||||
backendUrl: string;
|
||||
messages: Array<{
|
||||
role: string;
|
||||
content: string;
|
||||
tool_calls?: unknown[];
|
||||
tool_call_id?: string;
|
||||
}>;
|
||||
notionContext?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message via the Rust chat_send command.
|
||||
* Returns immediately — results arrive via events.
|
||||
* Tauri v2 converts camelCase param names to snake_case for the Rust command.
|
||||
*/
|
||||
export async function chatSend(params: ChatSendParams): Promise<void> {
|
||||
await invoke('chat_send', {
|
||||
threadId: params.threadId,
|
||||
message: params.message,
|
||||
model: params.model,
|
||||
authToken: params.authToken,
|
||||
backendUrl: params.backendUrl,
|
||||
messages: params.messages,
|
||||
notionContext: params.notionContext ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel an in-flight chat request.
|
||||
*/
|
||||
export async function chatCancel(threadId: string): Promise<boolean> {
|
||||
return await invoke<boolean>('chat_cancel', { threadId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we should use the Rust backend for chat.
|
||||
* Returns true when running in Tauri on desktop.
|
||||
*/
|
||||
export function useRustChat(): boolean {
|
||||
return coreIsTauri();
|
||||
}
|
||||
@@ -319,6 +319,11 @@
|
||||
resolved "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz"
|
||||
integrity sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==
|
||||
|
||||
"@dimforge/rapier3d-compat@~0.12.0":
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz#7b3365e1dfdc5cd957b45afe920b4ac06c7cd389"
|
||||
integrity sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==
|
||||
|
||||
"@esbuild/aix-ppc64@0.27.2":
|
||||
version "0.27.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz#521cbd968dcf362094034947f76fa1b18d2d403c"
|
||||
@@ -1461,6 +1466,11 @@
|
||||
minimatch "^9.0.0"
|
||||
parse-imports-exports "^0.2.4"
|
||||
|
||||
"@tweenjs/tween.js@~23.1.3":
|
||||
version "23.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@tweenjs/tween.js/-/tween.js-23.1.3.tgz#eff0245735c04a928bb19c026b58c2a56460539d"
|
||||
integrity sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==
|
||||
|
||||
"@types/aria-query@^5.0.1":
|
||||
version "5.0.4"
|
||||
resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz"
|
||||
@@ -1661,11 +1671,29 @@
|
||||
resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz"
|
||||
integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==
|
||||
|
||||
"@types/stats.js@*":
|
||||
version "0.17.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/stats.js/-/stats.js-0.17.4.tgz#1933e5ff153a23c7664487833198d685c22e791e"
|
||||
integrity sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==
|
||||
|
||||
"@types/statuses@^2.0.6":
|
||||
version "2.0.6"
|
||||
resolved "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz"
|
||||
integrity sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==
|
||||
|
||||
"@types/three@^0.183.1":
|
||||
version "0.183.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/three/-/three-0.183.1.tgz#d812d028b38ad68843725e3e7bd3268607cef150"
|
||||
integrity sha512-f2Pu5Hrepfgavttdye3PsH5RWyY/AvdZQwIVhrc4uNtvF7nOWJacQKcoVJn0S4f0yYbmAE6AR+ve7xDcuYtMGw==
|
||||
dependencies:
|
||||
"@dimforge/rapier3d-compat" "~0.12.0"
|
||||
"@tweenjs/tween.js" "~23.1.3"
|
||||
"@types/stats.js" "*"
|
||||
"@types/webxr" ">=0.5.17"
|
||||
"@webgpu/types" "*"
|
||||
fflate "~0.8.2"
|
||||
meshoptimizer "~1.0.1"
|
||||
|
||||
"@types/unist@*", "@types/unist@^3.0.0":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz"
|
||||
@@ -1681,6 +1709,11 @@
|
||||
resolved "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz"
|
||||
integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==
|
||||
|
||||
"@types/webxr@>=0.5.17":
|
||||
version "0.5.24"
|
||||
resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.24.tgz#734d5d90dadc5809a53e422726c60337fa2f4a44"
|
||||
integrity sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==
|
||||
|
||||
"@types/which@^2.0.1":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz"
|
||||
@@ -2109,6 +2142,11 @@
|
||||
dependencies:
|
||||
"@wdio/logger" "9.18.0"
|
||||
|
||||
"@webgpu/types@*":
|
||||
version "0.1.69"
|
||||
resolved "https://registry.yarnpkg.com/@webgpu/types/-/types-0.1.69.tgz#6b849bf370a1f29c78bd3aeba8e84c1150b237f2"
|
||||
integrity sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==
|
||||
|
||||
"@xmldom/xmldom@^0.9.5":
|
||||
version "0.9.8"
|
||||
resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.8.tgz"
|
||||
@@ -4127,6 +4165,11 @@ fdir@^6.5.0:
|
||||
resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz"
|
||||
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
|
||||
|
||||
fflate@~0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea"
|
||||
integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==
|
||||
|
||||
figures@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz"
|
||||
@@ -5594,6 +5637,11 @@ merge2@^1.3.0:
|
||||
resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
meshoptimizer@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/meshoptimizer/-/meshoptimizer-1.0.1.tgz#c3ef0d509a8b84ac562493dba5a108fd67fa76dc"
|
||||
integrity sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g==
|
||||
|
||||
micromark-core-commonmark@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz"
|
||||
@@ -7400,7 +7448,16 @@ strict-event-emitter@^0.5.1:
|
||||
resolved "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz"
|
||||
integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -7499,8 +7556,14 @@ stringify-entities@^4.0.0:
|
||||
character-entities-html4 "^2.0.0"
|
||||
character-entities-legacy "^3.0.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
name strip-ansi-cjs
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@@ -7689,6 +7752,11 @@ thenify-all@^1.0.0:
|
||||
dependencies:
|
||||
any-promise "^1.0.0"
|
||||
|
||||
three@^0.183.2:
|
||||
version "0.183.2"
|
||||
resolved "https://registry.yarnpkg.com/three/-/three-0.183.2.tgz#606e3195bf210ef8d1eaaca2ab8c59d92d2bbc18"
|
||||
integrity sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==
|
||||
|
||||
timers-browserify@^2.0.4:
|
||||
version "2.0.12"
|
||||
resolved "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz"
|
||||
@@ -8345,8 +8413,7 @@ workerpool@^6.5.1:
|
||||
resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz"
|
||||
integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
name wrap-ansi-cjs
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
@@ -8364,6 +8431,15 @@ wrap-ansi@^6.2.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"
|
||||
|
||||
Reference in New Issue
Block a user