mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
Refactor testing scripts in package.json and update dependencies
- Simplified test scripts in package.json by removing specific config references for vitest. - Updated @tauri-apps/api dependency version to 2.10.1. - Removed unused dependencies from yarn.lock and updated Cargo.toml and Cargo.lock for tinyhumansai to version 0.1.4. - Enhanced memory management in Conversations and Login components by ensuring async token synchronization. - Introduced recall_memory command in Tauri for improved context retrieval from memory.
This commit is contained in:
+6
-8
@@ -21,11 +21,11 @@
|
||||
"macos:dev": "yarn macos:build:debug && open 'src-tauri/target/debug/bundle/macos/AlphaHuman.app'",
|
||||
"android:dev": "tauri android dev",
|
||||
"android:build": "tauri android build",
|
||||
"test": "vitest run --config test/vitest.config.ts",
|
||||
"test:unit": "vitest run --config test/vitest.config.ts",
|
||||
"test:unit:watch": "vitest --config test/vitest.config.ts --watch",
|
||||
"test:watch": "vitest --config test/vitest.config.ts --watch",
|
||||
"test:coverage": "vitest run --config test/vitest.config.ts --coverage",
|
||||
"test": "vitest run",
|
||||
"test:unit": "vitest run",
|
||||
"test:unit:watch": "vitest",
|
||||
"test:watch": "vitest",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"test:rust": "source $HOME/.cargo/env 2>/dev/null; cargo test --manifest-path src-tauri/Cargo.toml",
|
||||
"test:e2e:build": "bash scripts/e2e-build.sh",
|
||||
"test:e2e:login": "bash scripts/e2e-login.sh",
|
||||
@@ -48,13 +48,12 @@
|
||||
"@scure/bip32": "^2.0.1",
|
||||
"@scure/bip39": "^2.0.1",
|
||||
"@sentry/react": "^10.38.0",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/api": "2.10.1",
|
||||
"@tauri-apps/plugin-deep-link": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@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",
|
||||
@@ -71,7 +70,6 @@
|
||||
"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"
|
||||
},
|
||||
|
||||
Generated
+2
-2
@@ -8405,9 +8405,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tinyhumansai"
|
||||
version = "0.1.2"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85a17392a3521fbef245da18fcef9e37bca8d97d9795da973a398e5d64d1b8af"
|
||||
checksum = "f7a88dba7fe194a507d0351c5f8b5cd8518fb30f8307dd788333f9715f51c44e"
|
||||
dependencies = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
|
||||
@@ -131,7 +131,7 @@ opentelemetry_sdk = { version = "0.31", default-features = false, features = ["t
|
||||
opentelemetry-otlp = { version = "0.31", default-features = false, features = ["trace", "metrics", "http-proto", "reqwest-client", "reqwest-rustls-webpki-roots"] }
|
||||
tokio-stream = { version = "0.1.18", features = ["full"] }
|
||||
url = "2"
|
||||
tinyhumansai = "0.1"
|
||||
tinyhumansai = "0.1.4"
|
||||
|
||||
# Optional integrations
|
||||
matrix-sdk = { version = "0.16", optional = true, default-features = false, features = ["e2e-encryption", "rustls-tls", "markdown"] }
|
||||
|
||||
@@ -27,6 +27,40 @@ pub async fn init_memory_client(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Recall context from the TinyHumans Master memory node for a skill integration.
|
||||
/// Returns the recalled context string (or null if the server had nothing to return).
|
||||
#[tauri::command]
|
||||
pub async fn recall_memory(
|
||||
skill_id: String,
|
||||
integration_id: String,
|
||||
max_chunks: Option<u32>,
|
||||
state: tauri::State<'_, MemoryState>,
|
||||
) -> Result<Option<String>, String> {
|
||||
log::info!(
|
||||
"[memory] recall_memory: entry (skill_id={skill_id}, integration_id={integration_id}, max_chunks={max_chunks:?})"
|
||||
);
|
||||
let client = state.0.lock().map_err(|e| e.to_string())?.clone();
|
||||
match client {
|
||||
Some(c) => {
|
||||
let result = c
|
||||
.recall_skill_context(&skill_id, &integration_id, max_chunks.unwrap_or(10))
|
||||
.await;
|
||||
match &result {
|
||||
Ok(ctx) => log::info!(
|
||||
"[memory] recall_memory: exit — ok (has_context={})",
|
||||
ctx.is_some()
|
||||
),
|
||||
Err(e) => log::warn!("[memory] recall_memory: exit — error: {e}"),
|
||||
}
|
||||
result
|
||||
}
|
||||
None => {
|
||||
log::warn!("[memory] recall_memory: exit — client not initialised (no JWT set)");
|
||||
Err("Memory layer not configured — JWT token not yet set".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Query the TinyHumans memory for a skill integration.
|
||||
/// Returns the RAG context string to inject into AI prompts.
|
||||
#[tauri::command]
|
||||
|
||||
@@ -797,6 +797,7 @@ pub fn run() {
|
||||
// Memory commands (TinyHumans Neocortex)
|
||||
init_memory_client,
|
||||
memory_query,
|
||||
recall_memory,
|
||||
]
|
||||
}
|
||||
#[cfg(not(desktop))]
|
||||
@@ -916,6 +917,7 @@ pub fn run() {
|
||||
// Memory commands (TinyHumans Neocortex)
|
||||
init_memory_client,
|
||||
memory_query,
|
||||
recall_memory,
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
+72
-10
@@ -6,8 +6,8 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
use tinyhumansai::{
|
||||
DeleteMemoryParams, InsertMemoryParams, QueryMemoryParams, TinyHumanConfig,
|
||||
TinyHumanMemoryClient,
|
||||
DeleteMemoryParams, InsertMemoryParams, QueryMemoryParams, RecallMemoryParams,
|
||||
TinyHumanConfig, TinyHumanMemoryClient,
|
||||
};
|
||||
|
||||
/// Shared, cloneable handle to the memory client.
|
||||
@@ -21,15 +21,23 @@ impl MemoryClient {
|
||||
/// Construct from a JWT token (sourced from `authSlice.token` in the Redux store).
|
||||
/// Returns `None` if the token is empty or client construction fails.
|
||||
pub fn from_token(jwt_token: String) -> Option<Self> {
|
||||
log::debug!("[memory] from_token: entry (token_len={})", jwt_token.trim().len());
|
||||
log::info!("[memory] from_token: entry (token_len={})", jwt_token.trim().len());
|
||||
if jwt_token.trim().is_empty() {
|
||||
log::warn!("[memory] from_token: exit — token is empty, returning None");
|
||||
return None;
|
||||
}
|
||||
let base_url = std::env::var("TINYHUMANS_BASE_URL")
|
||||
.unwrap_or_else(|_| "https://api.tinyhumans.ai".to_string());
|
||||
log::debug!("[memory] from_token: constructing client (base_url={base_url})");
|
||||
let config = TinyHumanConfig::new(jwt_token).with_base_url(base_url);
|
||||
let config = match std::env::var("ALPHAHUMAN_BASE_URL")
|
||||
.or_else(|_| std::env::var("TINYHUMANS_BASE_URL"))
|
||||
{
|
||||
Ok(base_url) => {
|
||||
log::info!("[memory] from_token: constructing client (base_url={base_url})");
|
||||
TinyHumanConfig::new(jwt_token).with_base_url(base_url)
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!("[memory] from_token: constructing client (base_url=<sdk default>)");
|
||||
TinyHumanConfig::new(jwt_token)
|
||||
}
|
||||
};
|
||||
match TinyHumanMemoryClient::new(config) {
|
||||
Ok(inner) => {
|
||||
log::info!("[memory] from_token: exit — client created successfully");
|
||||
@@ -58,6 +66,7 @@ impl MemoryClient {
|
||||
"[memory] store_skill_sync: payload → namespace={namespace} | title={title} | content={}",
|
||||
content
|
||||
);
|
||||
log::info!("[memory] insert_memory: calling SDK (namespace={namespace}, title={title:?})");
|
||||
let result = self.inner
|
||||
.insert_memory(InsertMemoryParams {
|
||||
title: title.to_string(),
|
||||
@@ -66,8 +75,13 @@ impl MemoryClient {
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| format!("Memory insert failed: {e}"));
|
||||
.map(|_| {
|
||||
log::info!("[memory] insert_memory: success (namespace={namespace}, title={title:?})");
|
||||
})
|
||||
.map_err(|e| {
|
||||
log::warn!("[memory] insert_memory: SDK error — kind={:?} msg={e}", classify_insert_error(&e));
|
||||
format!("Memory insert failed: {e}")
|
||||
});
|
||||
match &result {
|
||||
Ok(()) => log::info!("[memory] store_skill_sync: exit — ok (namespace={namespace})"),
|
||||
Err(e) => log::warn!("[memory] store_skill_sync: exit — error (namespace={namespace}): {e}"),
|
||||
@@ -93,7 +107,7 @@ impl MemoryClient {
|
||||
.query_memory(QueryMemoryParams {
|
||||
query: query.to_string(),
|
||||
namespace: Some(namespace.clone()),
|
||||
max_chunks: Some(max_chunks),
|
||||
max_chunks: Some(f64::from(max_chunks)),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
@@ -106,6 +120,37 @@ impl MemoryClient {
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Recall context from the Master memory node for a given namespace.
|
||||
/// Returns the synthesised `response` string, or `None` if the server returned nothing.
|
||||
pub async fn recall_skill_context(
|
||||
&self,
|
||||
skill_id: &str,
|
||||
integration_id: &str,
|
||||
max_chunks: u32,
|
||||
) -> Result<Option<String>, String> {
|
||||
let namespace = format!("skill:{skill_id}:{integration_id}");
|
||||
log::info!(
|
||||
"[memory] recall_skill_context: entry (namespace={namespace}, max_chunks={max_chunks})"
|
||||
);
|
||||
let res = self
|
||||
.inner
|
||||
.recall_memory(RecallMemoryParams {
|
||||
namespace: Some(namespace.clone()),
|
||||
max_chunks: Some(f64::from(max_chunks)),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::warn!("[memory] recall_skill_context: exit — error (namespace={namespace}): {e}");
|
||||
format!("Memory recall failed: {e}")
|
||||
})?;
|
||||
let response = res.data.response;
|
||||
log::info!(
|
||||
"[memory] recall_skill_context: exit — ok (namespace={namespace}, has_response={})",
|
||||
response.is_some()
|
||||
);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Delete all memories for a skill integration (e.g. on OAuth revoke).
|
||||
pub async fn clear_skill_memory(
|
||||
&self,
|
||||
@@ -130,6 +175,23 @@ impl MemoryClient {
|
||||
}
|
||||
}
|
||||
|
||||
fn classify_insert_error(e: &tinyhumansai::TinyHumanError) -> &'static str {
|
||||
let msg = e.to_string();
|
||||
if msg.contains("dns") || msg.contains("resolve") || msg.contains("lookup") {
|
||||
"dns_failure"
|
||||
} else if msg.contains("tls") || msg.contains("certificate") || msg.contains("ssl") {
|
||||
"tls_failure"
|
||||
} else if msg.contains("Connection refused") || msg.contains("connection refused") {
|
||||
"connection_refused"
|
||||
} else if msg.contains("timed out") || msg.contains("deadline") {
|
||||
"timeout"
|
||||
} else if msg.contains("error sending request") {
|
||||
"transport_error"
|
||||
} else {
|
||||
"other"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -330,7 +330,7 @@ async fn run_event_loop(
|
||||
// messages (events, pings, etc.) but queue any new CallTool.
|
||||
match rx.try_recv() {
|
||||
Ok(msg) => {
|
||||
let should_stop = handle_message(rt, ctx, msg, state, skill_id, &mut pending_tool, app_handle).await;
|
||||
let should_stop = handle_message(rt, ctx, msg, state, skill_id, &mut pending_tool, app_handle, ops_state).await;
|
||||
if should_stop {
|
||||
break;
|
||||
}
|
||||
@@ -447,6 +447,7 @@ async fn handle_message(
|
||||
skill_id: &str,
|
||||
pending_tool: &mut Option<PendingToolCall>,
|
||||
app_handle: Option<&tauri::AppHandle>,
|
||||
ops_state: &Arc<RwLock<qjs_ops::SkillState>>,
|
||||
) -> bool {
|
||||
match msg {
|
||||
SkillMessage::CallTool { tool_name, arguments, reply } => {
|
||||
@@ -553,12 +554,13 @@ async fn handle_message(
|
||||
}
|
||||
}
|
||||
SkillMessage::Rpc { method, params, reply } => {
|
||||
// Resolve the optional memory client once for this RPC call
|
||||
let memory_client_opt: Option<crate::memory::MemoryClientRef> =
|
||||
app_handle.and_then(|ah| {
|
||||
ah.try_state::<Option<crate::memory::MemoryClientRef>>()
|
||||
.and_then(|s: tauri::State<'_, Option<crate::memory::MemoryClientRef>>| s.inner().clone())
|
||||
});
|
||||
// Resolve the optional memory client once for this RPC call.
|
||||
// State is registered as MemoryState(Mutex<Option<MemoryClientRef>>), not
|
||||
// Option<MemoryClientRef> directly, so we must use the newtype wrapper.
|
||||
let memory_client_opt = app_handle.and_then(|ah| {
|
||||
ah.try_state::<crate::commands::memory::MemoryState>()
|
||||
.and_then(|s| s.0.lock().ok().and_then(|g| g.clone()))
|
||||
});
|
||||
|
||||
let result = match method.as_str() {
|
||||
"oauth/complete" => {
|
||||
@@ -582,8 +584,11 @@ async fn handle_message(
|
||||
let params_str = serde_json::to_string(¶ms).unwrap_or_else(|_| "{}".to_string());
|
||||
let result = handle_js_call(rt, ctx, "onOAuthComplete", ¶ms_str).await;
|
||||
|
||||
// Fire-and-forget: persist returned payload to TinyHumans memory
|
||||
if let Ok(ref payload) = result {
|
||||
// Fire-and-forget: persist published ops state to TinyHumans memory.
|
||||
// Skills publish data via state.set()/setPartial() into ops_state.data,
|
||||
// not as the return value of onOAuthComplete() (which is typically undefined).
|
||||
let state_snapshot = ops_state.read().data.clone();
|
||||
if !state_snapshot.is_empty() {
|
||||
if let Some(client) = memory_client_opt.clone() {
|
||||
let skill = skill_id.to_string();
|
||||
let integration_id = params
|
||||
@@ -591,8 +596,10 @@ async fn handle_message(
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
let content = serde_json::to_string_pretty(payload)
|
||||
.unwrap_or_else(|_| payload.to_string());
|
||||
let content = serde_json::to_string_pretty(
|
||||
&serde_json::Value::Object(state_snapshot),
|
||||
)
|
||||
.unwrap_or_else(|_| "{}".to_string());
|
||||
let title = format!("{} OAuth sync — {}", skill, integration_id);
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = client
|
||||
@@ -614,13 +621,21 @@ async fn handle_message(
|
||||
}
|
||||
"skill/sync" => {
|
||||
let result = handle_js_call(rt, ctx, "onSync", "{}").await;
|
||||
|
||||
// Fire-and-forget: persist sync payload to TinyHumans memory
|
||||
if let Ok(ref payload) = result {
|
||||
// Fire-and-forget: persist published ops state to TinyHumans memory.
|
||||
// Skills publish data via state.set()/setPartial() into ops_state.data,
|
||||
// not as the return value of onSync() (which is typically undefined).
|
||||
let state_snapshot = ops_state.read().data.clone();
|
||||
log::info!(
|
||||
"[memory] store_skill_sync: payload → state_snapshot={:?}",
|
||||
state_snapshot
|
||||
);
|
||||
if !state_snapshot.is_empty() {
|
||||
if let Some(client) = memory_client_opt.clone() {
|
||||
let skill = skill_id.to_string();
|
||||
let content = serde_json::to_string_pretty(payload)
|
||||
.unwrap_or_else(|_| payload.to_string());
|
||||
let content = serde_json::to_string_pretty(
|
||||
&serde_json::Value::Object(state_snapshot),
|
||||
)
|
||||
.unwrap_or_else(|_| "{}".to_string());
|
||||
let title = format!("{} periodic sync", skill);
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = client
|
||||
|
||||
+2
-2
@@ -27,10 +27,10 @@ function App() {
|
||||
<PersistGate
|
||||
loading={null}
|
||||
persistor={persistor}
|
||||
onBeforeLift={() => {
|
||||
onBeforeLift={async () => {
|
||||
const token = store.getState().auth.token;
|
||||
console.info('[memory] PersistGate onBeforeLift: token_present=%s', !!token);
|
||||
if (token) void syncMemoryClientToken(token);
|
||||
if (token) await syncMemoryClientToken(token);
|
||||
}}>
|
||||
<UserProvider>
|
||||
<SocketProvider>
|
||||
|
||||
+82
-38
@@ -12,13 +12,13 @@ import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { injectAll } from '../lib/ai/injector';
|
||||
import type { Message } from '../lib/ai/providers/interface';
|
||||
import { skillManager } from '../lib/skills/manager';
|
||||
import { creditsApi, type TeamUsage } from '../services/api/creditsApi';
|
||||
import {
|
||||
type ChatMessage,
|
||||
inferenceApi,
|
||||
type ModelInfo,
|
||||
type Tool,
|
||||
} from '../services/api/inferenceApi';
|
||||
import { type TeamUsage, creditsApi } from '../services/api/creditsApi';
|
||||
import { useAppDispatch, useAppSelector } from '../store/hooks';
|
||||
import type { NotionPageSummary, NotionSummary, NotionUserProfile } from '../store/notionSlice';
|
||||
import {
|
||||
@@ -388,6 +388,23 @@ const Conversations = () => {
|
||||
// Continue with original message
|
||||
}
|
||||
|
||||
// Prepend recalled memory context from TinyHumans Master node
|
||||
try {
|
||||
const { invoke } = await import('@tauri-apps/api/core');
|
||||
const recalledContext = await invoke<string | null>('recall_memory', {
|
||||
skillId: 'conversations',
|
||||
integrationId: selectedThreadId,
|
||||
maxChunks: 10,
|
||||
});
|
||||
if (recalledContext) {
|
||||
processedUserContent = `[MEMORY_CONTEXT]\n${recalledContext}\n[/MEMORY_CONTEXT]\n\n${processedUserContent}`;
|
||||
console.log('✅ Memory recall injected into prompt');
|
||||
}
|
||||
} catch (recallError) {
|
||||
console.warn('⚠️ Memory recall skipped:', recallError);
|
||||
// Non-fatal — continue without recalled context
|
||||
}
|
||||
|
||||
// Prepend Notion workspace context if connected
|
||||
const notionContext = buildNotionContext(
|
||||
notionProfile,
|
||||
@@ -1077,46 +1094,73 @@ const Conversations = () => {
|
||||
)}
|
||||
<div className="flex-1" />
|
||||
{/* Budget indicator — circular */}
|
||||
{(isLoadingBudget || teamUsage) && (() => {
|
||||
const size = 22;
|
||||
const r = 9;
|
||||
const circ = 2 * Math.PI * r;
|
||||
const pct = teamUsage
|
||||
? Math.min(1, teamUsage.remainingUsd / teamUsage.cycleBudgetUsd)
|
||||
: 0;
|
||||
const dash = pct * circ;
|
||||
return (
|
||||
<div className="flex items-center gap-1.5" title={teamUsage ? `$${teamUsage.remainingUsd.toFixed(2)} of $${teamUsage.cycleBudgetUsd.toFixed(2)} remaining` : 'Loading budget…'}>
|
||||
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} className="-rotate-90">
|
||||
<circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke="currentColor" strokeWidth="2.5" className="text-white/10" />
|
||||
{teamUsage ? (
|
||||
{(isLoadingBudget || teamUsage) &&
|
||||
(() => {
|
||||
const size = 22;
|
||||
const r = 9;
|
||||
const circ = 2 * Math.PI * r;
|
||||
const pct = teamUsage
|
||||
? Math.min(1, teamUsage.remainingUsd / teamUsage.cycleBudgetUsd)
|
||||
: 0;
|
||||
const dash = pct * circ;
|
||||
return (
|
||||
<div
|
||||
className="flex items-center gap-1.5"
|
||||
title={
|
||||
teamUsage
|
||||
? `$${teamUsage.remainingUsd.toFixed(2)} of $${teamUsage.cycleBudgetUsd.toFixed(2)} remaining`
|
||||
: 'Loading budget…'
|
||||
}>
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox={`0 0 ${size} ${size}`}
|
||||
className="-rotate-90">
|
||||
<circle
|
||||
cx={size / 2} cy={size / 2} r={r}
|
||||
fill="none" stroke="currentColor" strokeWidth="2.5"
|
||||
strokeDasharray={`${dash} ${circ}`}
|
||||
strokeLinecap="round"
|
||||
className={pct < 0.2 ? 'text-amber-500' : 'text-primary-500'}
|
||||
style={{ transition: 'stroke-dasharray 0.3s ease' }}
|
||||
/>
|
||||
) : (
|
||||
<circle
|
||||
cx={size / 2} cy={size / 2} r={r}
|
||||
fill="none" stroke="currentColor" strokeWidth="2.5"
|
||||
strokeDasharray={`${circ * 0.25} ${circ}`}
|
||||
strokeLinecap="round"
|
||||
className="text-stone-600 animate-spin origin-center"
|
||||
style={{ transformOrigin: `${size / 2}px ${size / 2}px` }}
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={r}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.5"
|
||||
className="text-white/10"
|
||||
/>
|
||||
{teamUsage ? (
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={r}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.5"
|
||||
strokeDasharray={`${dash} ${circ}`}
|
||||
strokeLinecap="round"
|
||||
className={pct < 0.2 ? 'text-amber-500' : 'text-primary-500'}
|
||||
style={{ transition: 'stroke-dasharray 0.3s ease' }}
|
||||
/>
|
||||
) : (
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={r}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.5"
|
||||
strokeDasharray={`${circ * 0.25} ${circ}`}
|
||||
strokeLinecap="round"
|
||||
className="text-stone-600 animate-spin origin-center"
|
||||
style={{ transformOrigin: `${size / 2}px ${size / 2}px` }}
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
{teamUsage && (
|
||||
<span className="text-[10px] text-stone-500">
|
||||
${teamUsage.remainingUsd.toFixed(2)}
|
||||
</span>
|
||||
)}
|
||||
</svg>
|
||||
{teamUsage && (
|
||||
<span className="text-[10px] text-stone-500">
|
||||
${teamUsage.remainingUsd.toFixed(2)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
{sendError && (
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ const Login = () => {
|
||||
|
||||
dispatch(setToken(jwtToken));
|
||||
console.info('[memory] Login: dispatching syncMemoryClientToken after setToken');
|
||||
void syncMemoryClientToken(jwtToken);
|
||||
await syncMemoryClientToken(jwtToken);
|
||||
navigate('/onboarding/', { replace: true });
|
||||
} catch (err) {
|
||||
if (!cancelled) {
|
||||
|
||||
@@ -169,13 +169,20 @@ export async function setWindowTitle(title: string): Promise<void> {
|
||||
* Redux Persist rehydration.
|
||||
*/
|
||||
export async function syncMemoryClientToken(token: string): Promise<void> {
|
||||
console.debug('[memory] syncMemoryClientToken: entry (token_present=%s, is_tauri=%s)', !!token, isTauri());
|
||||
console.debug(
|
||||
'[memory] syncMemoryClientToken: entry (token_present=%s, is_tauri=%s)',
|
||||
!!token,
|
||||
isTauri()
|
||||
);
|
||||
if (!isTauri() || !token) {
|
||||
console.debug('[memory] syncMemoryClientToken: exit — skipped (not Tauri or empty token)');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
console.debug('[memory] syncMemoryClientToken: payload → init_memory_client { jwtToken: <redacted, len=%d> }', token.length);
|
||||
console.debug(
|
||||
'[memory] syncMemoryClientToken: payload → init_memory_client { jwtToken: <redacted, len=%d> }',
|
||||
token.length
|
||||
);
|
||||
await invoke('init_memory_client', { jwtToken: token });
|
||||
console.info('[memory] syncMemoryClientToken: exit — ok');
|
||||
} catch (err) {
|
||||
|
||||
@@ -319,11 +319,6 @@
|
||||
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"
|
||||
@@ -1299,7 +1294,12 @@
|
||||
dependencies:
|
||||
postcss-selector-parser "6.0.10"
|
||||
|
||||
"@tauri-apps/api@^2", "@tauri-apps/api@^2.8.0":
|
||||
"@tauri-apps/api@2.10.1":
|
||||
version "2.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.10.1.tgz#57c1bae6114ec33d977eb2b50dfefc25fa84fc93"
|
||||
integrity sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==
|
||||
|
||||
"@tauri-apps/api@^2.8.0":
|
||||
version "2.9.1"
|
||||
resolved "https://registry.npmjs.org/@tauri-apps/api/-/api-2.9.1.tgz"
|
||||
integrity sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw==
|
||||
@@ -1461,11 +1461,6 @@
|
||||
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"
|
||||
@@ -1666,29 +1661,11 @@
|
||||
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"
|
||||
@@ -1704,11 +1681,6 @@
|
||||
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"
|
||||
@@ -2137,11 +2109,6 @@
|
||||
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"
|
||||
@@ -4160,11 +4127,6 @@ 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"
|
||||
@@ -5632,11 +5594,6 @@ 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"
|
||||
@@ -7732,11 +7689,6 @@ 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"
|
||||
|
||||
Reference in New Issue
Block a user