mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-14 18:47:42 +01:00
chore: delete unused code files and dependencies (#841)
Mainly delete unused webSocket content, and delete other unused code files and dependencies
This commit is contained in:
2
.env
2
.env
@@ -1,5 +1,3 @@
|
||||
COCO_SERVER_URL=http://localhost:9000 #https://coco.infini.cloud #http://localhost:9000
|
||||
|
||||
COCO_WEBSOCKET_URL=ws://localhost:9000/ws #wss://coco.infini.cloud/ws #ws://localhost:9000/ws
|
||||
|
||||
#TAURI_DEV_HOST=0.0.0.0
|
||||
@@ -24,6 +24,7 @@ Information about release notes of Coco App is provided here.
|
||||
|
||||
- refactor: split query_coco_fusion() #836
|
||||
- chore: web component loading font icon #838
|
||||
- chore: delete unused code files and dependencies #841
|
||||
|
||||
## 0.7.1 (2025-07-27)
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
"@tauri-apps/plugin-process": "^2.2.1",
|
||||
"@tauri-apps/plugin-shell": "^2.2.1",
|
||||
"@tauri-apps/plugin-updater": "github:infinilabs/tauri-plugin-updater#v2",
|
||||
"@tauri-apps/plugin-websocket": "~2.3.0",
|
||||
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
|
||||
"@wavesurfer/react": "^1.0.11",
|
||||
"ahooks": "^3.8.4",
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -47,9 +47,6 @@ importers:
|
||||
'@tauri-apps/plugin-updater':
|
||||
specifier: github:infinilabs/tauri-plugin-updater#v2
|
||||
version: https://codeload.github.com/infinilabs/tauri-plugin-updater/tar.gz/358e689c65e9943b53eff50bcb9dfd5b1cfc4072
|
||||
'@tauri-apps/plugin-websocket':
|
||||
specifier: ~2.3.0
|
||||
version: 2.3.0
|
||||
'@tauri-apps/plugin-window':
|
||||
specifier: 2.0.0-alpha.1
|
||||
version: 2.0.0-alpha.1
|
||||
@@ -1261,9 +1258,6 @@ packages:
|
||||
resolution: {tarball: https://codeload.github.com/infinilabs/tauri-plugin-updater/tar.gz/358e689c65e9943b53eff50bcb9dfd5b1cfc4072}
|
||||
version: 2.7.1
|
||||
|
||||
'@tauri-apps/plugin-websocket@2.3.0':
|
||||
resolution: {integrity: sha512-eAwRGe3tnqDeQYE0wq4g1PUKbam9tYvlC4uP/au12Y/z7MP4lrS4ylv+aoZ5Ly+hTlBdi7hDkhHomwF/UeBesA==}
|
||||
|
||||
'@tauri-apps/plugin-window@2.0.0-alpha.1':
|
||||
resolution: {integrity: sha512-dFOAgal/3Txz3SQ+LNQq0AK1EPC+acdaFlwPVB/6KXUZYmaFleIlzgxDVoJCQ+/xOhxvYrdQaFLefh0I/Kldbg==}
|
||||
|
||||
@@ -4643,10 +4637,6 @@ snapshots:
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.5.0
|
||||
|
||||
'@tauri-apps/plugin-websocket@2.3.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.5.0
|
||||
|
||||
'@tauri-apps/plugin-window@2.0.0-alpha.1':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.0-alpha.6
|
||||
|
||||
57
src-tauri/Cargo.lock
generated
57
src-tauri/Cargo.lock
generated
@@ -900,13 +900,12 @@ dependencies = [
|
||||
"tauri-plugin-single-instance",
|
||||
"tauri-plugin-store",
|
||||
"tauri-plugin-updater",
|
||||
"tauri-plugin-websocket",
|
||||
"tauri-plugin-windows-version",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-stream",
|
||||
"tokio-tungstenite 0.20.1",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"tungstenite 0.24.0",
|
||||
"url",
|
||||
@@ -6556,25 +6555,6 @@ dependencies = [
|
||||
"zip 2.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-websocket"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af3ac71aec5fb0ae5441e830cd075b1cbed49ac3d39cb975a4894ea8fa2e62b9"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.26.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-windows-version"
|
||||
version = "2.0.0"
|
||||
@@ -6909,22 +6889,6 @@ dependencies = [
|
||||
"tungstenite 0.20.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tungstenite 0.26.2",
|
||||
"webpki-roots 0.26.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.15"
|
||||
@@ -7131,25 +7095,6 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http 1.3.1",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.9.1",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"sha1",
|
||||
"thiserror 2.0.12",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.3"
|
||||
|
||||
@@ -51,7 +51,6 @@ serde = { version = "1", features = ["derive"] }
|
||||
# see: https://docs.rs/serde_json/latest/serde_json/struct.Number.html#method.from_u128
|
||||
serde_json = { version = "1", features = ["arbitrary_precision", "preserve_order"] }
|
||||
tauri-plugin-http = "2"
|
||||
tauri-plugin-websocket = "2"
|
||||
tauri-plugin-deep-link = "2.0.0"
|
||||
tauri-plugin-store = "2.2.0"
|
||||
tauri-plugin-os = "2"
|
||||
|
||||
@@ -37,9 +37,6 @@
|
||||
"http:allow-fetch-cancel",
|
||||
"http:allow-fetch-read-body",
|
||||
"http:allow-fetch-send",
|
||||
"websocket:default",
|
||||
"websocket:allow-connect",
|
||||
"websocket:allow-send",
|
||||
"autostart:allow-enable",
|
||||
"autostart:allow-disable",
|
||||
"autostart:allow-is-enabled",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::common::assistant::ChatRequestMessage;
|
||||
use crate::common::http::{GetResponse, convert_query_params_to_strings};
|
||||
use crate::common::http::convert_query_params_to_strings;
|
||||
use crate::common::register::SearchSourceRegistry;
|
||||
use crate::server::http_client::HttpClient;
|
||||
use crate::{common, server::servers::COCO_SERVERS};
|
||||
@@ -111,54 +111,6 @@ pub async fn cancel_session_chat<R: Runtime>(
|
||||
common::http::get_response_body_text(response).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn new_chat<R: Runtime>(
|
||||
_app_handle: AppHandle<R>,
|
||||
server_id: String,
|
||||
websocket_id: String,
|
||||
message: String,
|
||||
query_params: Option<HashMap<String, Value>>,
|
||||
) -> Result<GetResponse, String> {
|
||||
let body = if !message.is_empty() {
|
||||
let message = ChatRequestMessage {
|
||||
message: Some(message),
|
||||
};
|
||||
Some(
|
||||
serde_json::to_string(&message)
|
||||
.map_err(|e| format!("Failed to serialize message: {}", e))?
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("WEBSOCKET-SESSION-ID".to_string(), websocket_id.into());
|
||||
|
||||
let response = HttpClient::advanced_post(
|
||||
&server_id,
|
||||
"/chat/_new",
|
||||
Some(headers),
|
||||
convert_query_params_to_strings(query_params),
|
||||
body,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Error sending message: {}", e))?;
|
||||
|
||||
let body_text = common::http::get_response_body_text(response).await?;
|
||||
|
||||
log::debug!("New chat response: {}", &body_text);
|
||||
|
||||
let chat_response: GetResponse = serde_json::from_str(&body_text)
|
||||
.map_err(|e| format!("Failed to parse response JSON: {}", e))?;
|
||||
|
||||
if chat_response.result != "created" {
|
||||
return Err(format!("Unexpected result: {}", chat_response.result));
|
||||
}
|
||||
|
||||
Ok(chat_response)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn chat_create<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
@@ -222,37 +174,6 @@ pub async fn chat_create<R: Runtime>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn send_message<R: Runtime>(
|
||||
_app_handle: AppHandle<R>,
|
||||
server_id: String,
|
||||
websocket_id: String,
|
||||
session_id: String,
|
||||
message: String,
|
||||
query_params: Option<HashMap<String, Value>>, //search,deep_thinking
|
||||
) -> Result<String, String> {
|
||||
let path = format!("/chat/{}/_send", session_id);
|
||||
let msg = ChatRequestMessage {
|
||||
message: Some(message),
|
||||
};
|
||||
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("WEBSOCKET-SESSION-ID".to_string(), websocket_id.into());
|
||||
|
||||
let body = reqwest::Body::from(serde_json::to_string(&msg).unwrap());
|
||||
let response = HttpClient::advanced_post(
|
||||
&server_id,
|
||||
path.as_str(),
|
||||
Some(headers),
|
||||
convert_query_params_to_strings(query_params),
|
||||
Some(body),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Error cancel session: {}", e))?;
|
||||
|
||||
common::http::get_response_body_text(response).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn chat_chat<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
|
||||
@@ -130,9 +130,7 @@ pub fn run() {
|
||||
server::connector::get_connectors_by_server,
|
||||
search::query_coco_fusion,
|
||||
assistant::chat_history,
|
||||
assistant::new_chat,
|
||||
assistant::chat_create,
|
||||
assistant::send_message,
|
||||
assistant::chat_chat,
|
||||
assistant::session_chat_history,
|
||||
assistant::open_session_chat,
|
||||
@@ -145,8 +143,6 @@ pub fn run() {
|
||||
assistant::assistant_get_multi,
|
||||
// server::get_coco_server_datasources,
|
||||
// server::get_coco_server_connectors,
|
||||
server::websocket::connect_to_server,
|
||||
server::websocket::disconnect,
|
||||
get_app_search_source,
|
||||
server::attachment::upload_attachment,
|
||||
server::attachment::get_attachment,
|
||||
@@ -201,7 +197,6 @@ pub fn run() {
|
||||
let registry = SearchSourceRegistry::default();
|
||||
|
||||
app.manage(registry); // Store registry in Tauri's app state
|
||||
app.manage(server::websocket::WebSocketManager::default());
|
||||
|
||||
// This has to be called before initializing extensions as doing that
|
||||
// requires access to the shortcut store, which will be set by this
|
||||
|
||||
@@ -11,4 +11,3 @@ pub mod servers;
|
||||
pub mod synthesize;
|
||||
pub mod system_settings;
|
||||
pub mod transcription;
|
||||
pub mod websocket;
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
use crate::server::servers::{get_server_by_id, get_server_token};
|
||||
use futures::StreamExt;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tauri::{AppHandle, Emitter, Runtime};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::{Mutex, mpsc};
|
||||
use tokio_tungstenite::MaybeTlsStream;
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
use tokio_tungstenite::tungstenite::Message;
|
||||
use tokio_tungstenite::tungstenite::handshake::client::generate_key;
|
||||
use tokio_tungstenite::{Connector, connect_async_tls_with_config};
|
||||
#[derive(Default)]
|
||||
pub struct WebSocketManager {
|
||||
connections: Arc<Mutex<HashMap<String, Arc<WebSocketInstance>>>>,
|
||||
}
|
||||
|
||||
struct WebSocketInstance {
|
||||
ws_connection: Mutex<WebSocketStream<MaybeTlsStream<TcpStream>>>, // No need to lock the entire map
|
||||
cancel_tx: mpsc::Sender<()>,
|
||||
}
|
||||
|
||||
fn convert_to_websocket(endpoint: &str) -> Result<String, String> {
|
||||
let url = url::Url::parse(endpoint).map_err(|e| format!("Invalid URL: {}", e))?;
|
||||
let ws_protocol = if url.scheme() == "https" {
|
||||
"wss://"
|
||||
} else {
|
||||
"ws://"
|
||||
};
|
||||
let host = url.host_str().ok_or("No host found in URL")?;
|
||||
let port = url
|
||||
.port_or_known_default()
|
||||
.unwrap_or(if url.scheme() == "https" { 443 } else { 80 });
|
||||
|
||||
let ws_endpoint = if port == 80 || port == 443 {
|
||||
format!("{}{}{}", ws_protocol, host, "/ws")
|
||||
} else {
|
||||
format!("{}{}:{}/ws", ws_protocol, host, port)
|
||||
};
|
||||
Ok(ws_endpoint)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn connect_to_server<R: Runtime>(
|
||||
tauri_app_handle: AppHandle<R>,
|
||||
id: String,
|
||||
client_id: String,
|
||||
state: tauri::State<'_, WebSocketManager>,
|
||||
app_handle: AppHandle,
|
||||
) -> Result<(), String> {
|
||||
let connections_clone = state.connections.clone();
|
||||
|
||||
// Disconnect old connection first
|
||||
disconnect(client_id.clone(), state.clone()).await.ok();
|
||||
|
||||
let server = get_server_by_id(&id)
|
||||
.await
|
||||
.ok_or(format!("Server with ID {} not found", id))?;
|
||||
let endpoint = convert_to_websocket(&server.endpoint)?;
|
||||
let token = get_server_token(&id).await.map(|t| t.access_token.clone());
|
||||
|
||||
let mut request =
|
||||
tokio_tungstenite::tungstenite::client::IntoClientRequest::into_client_request(&endpoint)
|
||||
.map_err(|e| format!("Failed to create WebSocket request: {}", e))?;
|
||||
|
||||
request
|
||||
.headers_mut()
|
||||
.insert("Connection", "Upgrade".parse().unwrap());
|
||||
request
|
||||
.headers_mut()
|
||||
.insert("Upgrade", "websocket".parse().unwrap());
|
||||
request
|
||||
.headers_mut()
|
||||
.insert("Sec-WebSocket-Version", "13".parse().unwrap());
|
||||
request
|
||||
.headers_mut()
|
||||
.insert("Sec-WebSocket-Key", generate_key().parse().unwrap());
|
||||
|
||||
if let Some(token) = token {
|
||||
request
|
||||
.headers_mut()
|
||||
.insert("X-API-TOKEN", token.parse().unwrap());
|
||||
}
|
||||
|
||||
let allow_self_signature =
|
||||
crate::settings::get_allow_self_signature(tauri_app_handle.clone()).await;
|
||||
let tls_connector = tokio_native_tls::native_tls::TlsConnector::builder()
|
||||
.danger_accept_invalid_certs(allow_self_signature)
|
||||
.build()
|
||||
.map_err(|e| format!("TLS build error: {:?}", e))?;
|
||||
|
||||
let connector = Connector::NativeTls(tls_connector.into());
|
||||
|
||||
let (ws_stream, _) = connect_async_tls_with_config(
|
||||
request,
|
||||
None, // WebSocketConfig
|
||||
true, // disable_nagle
|
||||
Some(connector), // Connector
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("WebSocket TLS error: {:?}", e))?;
|
||||
|
||||
let (cancel_tx, mut cancel_rx) = mpsc::channel(1);
|
||||
|
||||
let instance = Arc::new(WebSocketInstance {
|
||||
ws_connection: Mutex::new(ws_stream),
|
||||
cancel_tx,
|
||||
});
|
||||
|
||||
// Insert connection into the map (lock is held briefly)
|
||||
{
|
||||
let mut connections = connections_clone.lock().await;
|
||||
connections.insert(client_id.clone(), instance.clone());
|
||||
}
|
||||
|
||||
// Spawn WebSocket handler in a separate task
|
||||
let app_handle_clone = app_handle.clone();
|
||||
let client_id_clone = client_id.clone();
|
||||
tokio::spawn(async move {
|
||||
let ws = &mut *instance.ws_connection.lock().await;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
msg = ws.next() => {
|
||||
match msg {
|
||||
Some(Ok(Message::Text(text))) => {
|
||||
let _ = app_handle_clone.emit(&format!("ws-message-{}", client_id_clone), text);
|
||||
},
|
||||
Some(Err(_)) | None => {
|
||||
log::debug!("WebSocket connection closed or error");
|
||||
let _ = app_handle_clone.emit(&format!("ws-error-{}", client_id_clone), id.clone());
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ = cancel_rx.recv() => {
|
||||
log::debug!("WebSocket connection cancelled");
|
||||
let _ = app_handle_clone.emit(&format!("ws-cancel-{}", client_id_clone), id.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove connection after it closes
|
||||
let mut connections = connections_clone.lock().await;
|
||||
connections.remove(&client_id_clone);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn disconnect(
|
||||
client_id: String,
|
||||
state: tauri::State<'_, WebSocketManager>,
|
||||
) -> Result<(), String> {
|
||||
let instance = {
|
||||
let mut connections = state.connections.lock().await;
|
||||
connections.remove(&client_id)
|
||||
};
|
||||
|
||||
if let Some(instance) = instance {
|
||||
let _ = instance.cancel_tx.send(()).await;
|
||||
|
||||
// Close WebSocket (lock only the connection, not the whole map)
|
||||
let mut ws = instance.ws_connection.lock().await;
|
||||
let _ = ws.close(None).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -126,7 +126,6 @@
|
||||
"https://release.infinilabs.com/coco/app/.latest.json?target={{target}}&arch={{arch}}¤t_version={{current_version}}"
|
||||
]
|
||||
},
|
||||
"websocket": {},
|
||||
"shell": {},
|
||||
"globalShortcut": {},
|
||||
"deep-link": {
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -172,14 +172,6 @@ export function mcp_server_search({
|
||||
return invokeWithErrorHandler(`mcp_server_search`, { id, queryParams });
|
||||
}
|
||||
|
||||
export function connect_to_server(id: string, clientId: string): Promise<void> {
|
||||
return invokeWithErrorHandler(`connect_to_server`, { id, clientId });
|
||||
}
|
||||
|
||||
export function disconnect(clientId: string): Promise<void> {
|
||||
return invokeWithErrorHandler(`disconnect`, { clientId });
|
||||
}
|
||||
|
||||
export function chat_history({
|
||||
serverId,
|
||||
from = 0,
|
||||
@@ -260,25 +252,6 @@ export function cancel_session_chat({
|
||||
});
|
||||
}
|
||||
|
||||
export function new_chat({
|
||||
serverId,
|
||||
websocketId,
|
||||
message,
|
||||
queryParams,
|
||||
}: {
|
||||
serverId: string;
|
||||
websocketId: string;
|
||||
message: string;
|
||||
queryParams?: Record<string, any>;
|
||||
}): Promise<GetResponse> {
|
||||
return invokeWithErrorHandler(`new_chat`, {
|
||||
serverId,
|
||||
websocketId,
|
||||
message,
|
||||
queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
export function chat_create({
|
||||
serverId,
|
||||
message,
|
||||
@@ -298,28 +271,6 @@ export function chat_create({
|
||||
});
|
||||
}
|
||||
|
||||
export function send_message({
|
||||
serverId,
|
||||
websocketId,
|
||||
sessionId,
|
||||
message,
|
||||
queryParams,
|
||||
}: {
|
||||
serverId: string;
|
||||
websocketId: string;
|
||||
sessionId: string;
|
||||
message: string;
|
||||
queryParams?: Record<string, any>;
|
||||
}): Promise<string> {
|
||||
return invokeWithErrorHandler(`send_message`, {
|
||||
serverId,
|
||||
websocketId,
|
||||
sessionId,
|
||||
message,
|
||||
queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
export function chat_chat({
|
||||
serverId,
|
||||
sessionId,
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import VisibleKey from "@/components/Common/VisibleKey";
|
||||
|
||||
interface ConnectionErrorProps {
|
||||
reconnect: () => void;
|
||||
connected: boolean;
|
||||
}
|
||||
|
||||
export default function ConnectionError({
|
||||
reconnect,
|
||||
connected,
|
||||
}: ConnectionErrorProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [reconnectCountdown, setReconnectCountdown] = useState<number>(0);
|
||||
useEffect(() => {
|
||||
if (!reconnectCountdown || connected) {
|
||||
setReconnectCountdown(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (reconnectCountdown > 0) {
|
||||
const timer = setTimeout(() => {
|
||||
setReconnectCountdown(reconnectCountdown - 1);
|
||||
}, 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [reconnectCountdown, connected]);
|
||||
|
||||
return (
|
||||
<div className="absolute top-0 right-0 bottom-0 left-0 px-2 py-4 bg-[rgba(238,238,238,0.98)] dark:bg-[rgba(32,33,38,0.9)] backdrop-blur-[2px] rounded-md font-normal text-xs text-gray-400 flex items-center gap-4 z-10">
|
||||
{t("search.input.connectionError")}
|
||||
<div
|
||||
className="px-1 h-[24px] text-[#0061FF] font-normal text-xs flex items-center justify-center cursor-pointer underline"
|
||||
onClick={() => {
|
||||
reconnect();
|
||||
setReconnectCountdown(10);
|
||||
}}
|
||||
>
|
||||
{reconnectCountdown > 0 ? (
|
||||
`${t("search.input.connecting")}(${reconnectCountdown}s)`
|
||||
) : (
|
||||
<VisibleKey
|
||||
shortcut="R"
|
||||
onKeyPress={() => {
|
||||
reconnect();
|
||||
setReconnectCountdown(10);
|
||||
}}
|
||||
>
|
||||
{t("search.input.reconnect")}
|
||||
</VisibleKey>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import { Globe } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import SettingsItem from "./SettingsItem";
|
||||
import { useAppStore } from "@/stores/appStore";
|
||||
import { AppEndpoint } from "@/types/index";
|
||||
|
||||
const ENDPOINTS = [
|
||||
{ value: "https://coco.infini.cloud", label: "https://coco.infini.cloud" },
|
||||
{ value: "http://localhost:9000", label: "http://localhost:9000" },
|
||||
{ value: "http://infini.tpddns.cn:27200", label: "http://infini.tpddns.cn:27200" },
|
||||
];
|
||||
|
||||
export default function AdvancedSettings() {
|
||||
const { t } = useTranslation();
|
||||
const endpoint = useAppStore(state => state.endpoint);
|
||||
const setEndpoint = useAppStore(state => state.setEndpoint);
|
||||
|
||||
useEffect(() => {}, [endpoint]);
|
||||
|
||||
const onChangeEndpoint = async (newEndpoint: AppEndpoint) => {
|
||||
await setEndpoint(newEndpoint);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
{t('settings.advanced.title')}
|
||||
</h2>
|
||||
<div className="space-y-6">
|
||||
<SettingsItem
|
||||
icon={Globe}
|
||||
title={t('settings.advanced.endpoint.title')}
|
||||
description={t('settings.advanced.endpoint.description')}
|
||||
>
|
||||
<div className={`p-4 rounded-lg`}>
|
||||
<select
|
||||
value={endpoint}
|
||||
onChange={(e) => onChangeEndpoint(e.target.value as AppEndpoint)}
|
||||
className={`w-full px-3 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white border-gray-300 text-gray-900 dark:bg-gray-800 dark:border-gray-600 dark:text-white`}
|
||||
>
|
||||
{ENDPOINTS.map(({ value, label }) => (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</SettingsItem>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Select } from '@headlessui/react'
|
||||
|
||||
interface SettingsSelectProps {
|
||||
options: string[];
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
export default function SettingsSelect({
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
}: SettingsSelectProps) {
|
||||
return (
|
||||
<Select
|
||||
value={value}
|
||||
onChange={(e) => onChange?.(e.target.value)}
|
||||
className="rounded-md border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:border-blue-500 focus:ring-blue-500"
|
||||
>
|
||||
{options.map((option) => (
|
||||
<option key={option} value={option.toLowerCase()}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { LogicalSize } from "@tauri-apps/api/dpi";
|
||||
|
||||
// Custom hook to auto-resize window based on content height
|
||||
const useAutoResizeWindow = () => {
|
||||
// Function to resize the window to the content's size
|
||||
const resizeWindowToContent = async () => {
|
||||
const contentHeight = document.getElementById("main_window")?.scrollHeight || 0;
|
||||
|
||||
try {
|
||||
// Resize the window to fit content size
|
||||
await getCurrentWebviewWindow()?.setSize(
|
||||
new LogicalSize(680, contentHeight)
|
||||
);
|
||||
|
||||
console.log("Window resized to content size");
|
||||
} catch (error) {
|
||||
console.error("Error resizing window:", error);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Initially resize the window
|
||||
resizeWindowToContent();
|
||||
|
||||
// Set up a ResizeObserver to listen for changes in content size
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
resizeWindowToContent();
|
||||
});
|
||||
|
||||
// Observe the document body for content size changes
|
||||
resizeObserver.observe(document.body);
|
||||
|
||||
// Clean up the observer when the component is unmounted
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, []); // Only run once when the component is mounted
|
||||
|
||||
// Optionally, you can return values if you need to handle window size elsewhere
|
||||
};
|
||||
|
||||
export default useAutoResizeWindow;
|
||||
@@ -1,23 +0,0 @@
|
||||
import { useEffect, RefObject } from 'react';
|
||||
|
||||
export function useClickAway(
|
||||
ref: RefObject<HTMLElement>,
|
||||
handler: (event: MouseEvent | TouchEvent) => void
|
||||
) {
|
||||
useEffect(() => {
|
||||
const listener = (event: MouseEvent | TouchEvent) => {
|
||||
if (!ref.current || ref.current.contains(event.target as Node)) {
|
||||
return;
|
||||
}
|
||||
handler(event);
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', listener);
|
||||
document.addEventListener('touchstart', listener);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', listener);
|
||||
document.removeEventListener('touchstart', listener);
|
||||
};
|
||||
}, [ref, handler]);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import { useConnectStore } from "@/stores/connectStore";
|
||||
|
||||
interface UseFeatureControlProps {
|
||||
initialFeatures: string[];
|
||||
featureToToggle: string;
|
||||
condition: (assistant: any) => boolean;
|
||||
}
|
||||
|
||||
export const useFeatureControl = ({
|
||||
initialFeatures,
|
||||
featureToToggle,
|
||||
condition,
|
||||
}: UseFeatureControlProps) => {
|
||||
const currentAssistant = useConnectStore((state) => state.currentAssistant);
|
||||
const [features, setFeatures] = useState<string[]>(initialFeatures);
|
||||
|
||||
useEffect(() => {
|
||||
if (condition(currentAssistant)) {
|
||||
setFeatures((prev) => prev.filter((feature) => feature !== featureToToggle));
|
||||
} else {
|
||||
setFeatures((prev) => {
|
||||
if (!prev.includes(featureToToggle)) {
|
||||
return [...prev, featureToToggle];
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
}
|
||||
}, [JSON.stringify(currentAssistant), featureToToggle]);
|
||||
|
||||
return features;
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
const useInfiniteScroll = (callback: () => void) => {
|
||||
const loaderRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
{ threshold: 1.0 }
|
||||
);
|
||||
|
||||
if (loaderRef.current) {
|
||||
observer.observe(loaderRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (loaderRef.current) {
|
||||
observer.unobserve(loaderRef.current);
|
||||
}
|
||||
};
|
||||
}, [callback]);
|
||||
|
||||
return loaderRef;
|
||||
};
|
||||
|
||||
export default useInfiniteScroll;
|
||||
@@ -1,196 +0,0 @@
|
||||
import { useEffect, useCallback, useRef } from "react";
|
||||
import { useWebSocket as useWebSocketAHook } from "ahooks";
|
||||
|
||||
import { useAppStore } from "@/stores/appStore";
|
||||
import platformAdapter from "@/utils/platformAdapter";
|
||||
import { Server } from "@/types/server";
|
||||
import { useConnectStore } from "@/stores/connectStore";
|
||||
|
||||
enum ReadyState {
|
||||
Connecting = 0,
|
||||
Open = 1,
|
||||
Closing = 2,
|
||||
Closed = 3,
|
||||
}
|
||||
|
||||
interface WebSocketProps {
|
||||
clientId: string;
|
||||
connected: boolean;
|
||||
setConnected: (connected: boolean) => void;
|
||||
dealMsgRef: React.MutableRefObject<((msg: string) => void) | null>;
|
||||
onWebsocketSessionId?: (sessionId: string) => void;
|
||||
}
|
||||
|
||||
export default function useWebSocket({
|
||||
clientId,
|
||||
connected,
|
||||
setConnected,
|
||||
dealMsgRef,
|
||||
onWebsocketSessionId,
|
||||
}: WebSocketProps) {
|
||||
const isTauri = useAppStore((state) => state.isTauri);
|
||||
const endpoint_websocket = useAppStore((state) => state.endpoint_websocket);
|
||||
const addError = useAppStore((state) => state.addError);
|
||||
|
||||
const currentService = useConnectStore((state) => state.currentService);
|
||||
|
||||
const websocketIdRef = useRef<string>("");
|
||||
const messageQueue = useRef<string[]>([]);
|
||||
const processingRef = useRef(false);
|
||||
|
||||
// web
|
||||
const { readyState, connect, disconnect } = useWebSocketAHook(
|
||||
//"wss://coco.infini.cloud/ws",
|
||||
//"ws://localhost:9000/ws",
|
||||
isTauri ? "" : endpoint_websocket,
|
||||
{
|
||||
manual: true,
|
||||
reconnectLimit: 3,
|
||||
reconnectInterval: 3000,
|
||||
onMessage: (event) => {
|
||||
const msg = event.data as string;
|
||||
messageQueue.current.push(msg);
|
||||
processQueue();
|
||||
},
|
||||
}
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!isTauri) {
|
||||
connect(); // web
|
||||
}
|
||||
}, [isTauri, connect]);
|
||||
|
||||
const processMessage = useCallback(
|
||||
(msg: string) => {
|
||||
try {
|
||||
if (msg.includes("websocket-session-id")) {
|
||||
const sessionId = msg.split(":")[1].trim();
|
||||
websocketIdRef.current = sessionId;
|
||||
setConnected(true); // web connected
|
||||
console.log("setConnected:", sessionId);
|
||||
onWebsocketSessionId?.(sessionId);
|
||||
} else {
|
||||
dealMsgRef.current?.(msg);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error processing message:", error, msg);
|
||||
}
|
||||
},
|
||||
[onWebsocketSessionId]
|
||||
);
|
||||
const processQueue = useCallback(() => {
|
||||
if (processingRef.current || messageQueue.current.length === 0) return;
|
||||
|
||||
processingRef.current = true;
|
||||
while (messageQueue.current.length > 0) {
|
||||
const msg = messageQueue.current.shift();
|
||||
if (msg) {
|
||||
// console.log("Processing message:", msg.substring(0, 100));
|
||||
processMessage(msg);
|
||||
}
|
||||
}
|
||||
processingRef.current = false;
|
||||
}, [processMessage]);
|
||||
useEffect(() => {
|
||||
// web
|
||||
if (readyState !== ReadyState.Open) {
|
||||
setConnected(false); // state
|
||||
}
|
||||
}, [readyState]);
|
||||
|
||||
// Tauri
|
||||
// 1. WebSocket connects when loading or switching services
|
||||
// src/components/Assistant/ChatHeader.tsx
|
||||
// 2. If not connected or disconnected, input box has a connect button, clicking it will connect to WebSocket
|
||||
// src/components/Search/InputBox.tsx
|
||||
const reconnect = useCallback(
|
||||
async (server?: Server) => {
|
||||
setConnected(false); // Disconnect before attempting to reconnect
|
||||
if (isTauri) {
|
||||
const targetServer = server || currentService;
|
||||
if (!targetServer?.id) return;
|
||||
try {
|
||||
// console.log("reconnect", targetServer.id);
|
||||
await platformAdapter.commands("connect_to_server", targetServer.id, clientId);
|
||||
} catch (error) {
|
||||
setConnected(false); // error
|
||||
console.error("Failed to connect:", error);
|
||||
}
|
||||
} else {
|
||||
connect();
|
||||
}
|
||||
},
|
||||
[currentService, clientId]
|
||||
);
|
||||
const disconnectWS = useCallback(async () => {
|
||||
if (!connected) return;
|
||||
if (isTauri) {
|
||||
try {
|
||||
console.log("disconnect");
|
||||
await platformAdapter.commands("disconnect", clientId);
|
||||
setConnected(false); // disconnected
|
||||
} catch (error) {
|
||||
console.error("Failed to disconnect:", error);
|
||||
}
|
||||
} else {
|
||||
disconnect();
|
||||
}
|
||||
}, [connected]);
|
||||
|
||||
const updateDealMsg = useCallback(
|
||||
(newDealMsg: (msg: string) => void) => {
|
||||
dealMsgRef.current = newDealMsg;
|
||||
},
|
||||
[dealMsgRef]
|
||||
);
|
||||
|
||||
const unlistenErrorRef = useRef<Promise<() => void> | null>(null);
|
||||
const unlistenCancelRef = useRef<Promise<() => void> | null>(null);
|
||||
useEffect(() => {
|
||||
if (!isTauri || !currentService?.id) return;
|
||||
|
||||
const unlisten_message = platformAdapter.listenEvent(`ws-message-${clientId}`, (event) => {
|
||||
const msg = event.payload as string;
|
||||
// console.log(`ws-message-${clientId}`, msg);
|
||||
if (msg.includes("websocket-session-id")) {
|
||||
const sessionId = msg.split(":")[1].trim();
|
||||
websocketIdRef.current = sessionId;
|
||||
console.log("setConnected sessionId:", sessionId);
|
||||
setConnected(true); // Tauri connected
|
||||
if (onWebsocketSessionId) {
|
||||
onWebsocketSessionId(sessionId);
|
||||
}
|
||||
// Listen for errors
|
||||
unlistenErrorRef.current = platformAdapter.listenEvent(`ws-error-${clientId}`, (event) => {
|
||||
if (connected) {
|
||||
const id = event.payload as string;
|
||||
console.error(`ws-error-${clientId}`, id === currentService?.id, connected);
|
||||
if (id === currentService?.id) {
|
||||
addError("WebSocket connection failed.");
|
||||
}
|
||||
}
|
||||
setConnected(false); // error
|
||||
});
|
||||
// Listen for cancel
|
||||
unlistenCancelRef.current = platformAdapter.listenEvent(`ws-cancel-${clientId}`, () => {
|
||||
setConnected(false);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
dealMsgRef.current?.(msg);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten_message.then((fn) => fn());
|
||||
if (unlistenErrorRef.current) {
|
||||
unlistenErrorRef.current.then((fn) => fn());
|
||||
}
|
||||
if (unlistenCancelRef.current) {
|
||||
unlistenCancelRef.current.then((fn) => fn());
|
||||
}
|
||||
};
|
||||
}, [currentService?.id, dealMsgRef, connected, clientId]);
|
||||
|
||||
return { reconnect, disconnectWS, updateDealMsg };
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import SVGWrap from "./SVGWrap";
|
||||
|
||||
export default function ArrowLeft(props: I.SVG) {
|
||||
return (
|
||||
<SVGWrap {...props} viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m7.85 13l2.85 2.85q.3.3.288.7t-.288.7q-.3.3-.712.313t-.713-.288L4.7 12.7q-.3-.3-.3-.7t.3-.7l4.575-4.575q.3-.3.713-.287t.712.312q.275.3.288.7t-.288.7L7.85 11H19q.425 0 .713.288T20 12t-.288.713T19 13z"
|
||||
/>
|
||||
</SVGWrap>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import SVGWrap from "./SVGWrap";
|
||||
|
||||
export default function Ask(props: I.SVG) {
|
||||
return (
|
||||
<SVGWrap {...props} viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M13.038 19.927A9.93 9.93 0 0 1 7.7 19L3 20l1.3-3.9C1.976 12.663 2.874 8.228 6.4 5.726c3.526-2.501 8.59-2.296 11.845.48c1.993 1.7 2.93 4.043 2.746 6.346M19 16l-2 3h4l-2 3"
|
||||
/>
|
||||
</SVGWrap>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import SVGWrap from "./SVGWrap";
|
||||
|
||||
export default function Link(props: I.SVG) {
|
||||
return (
|
||||
<SVGWrap {...props} viewBox="0 0 256 256">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M117.18 188.74a12 12 0 0 1 0 17l-5.12 5.12A58.26 58.26 0 0 1 70.6 228a58.62 58.62 0 0 1-41.46-100.08l34.75-34.75a58.64 58.64 0 0 1 98.56 28.11a12 12 0 1 1-23.37 5.44a34.65 34.65 0 0 0-58.22-16.58l-34.75 34.75A34.62 34.62 0 0 0 70.57 204a34.41 34.41 0 0 0 24.49-10.14l5.11-5.12a12 12 0 0 1 17.01 0M226.83 45.17a58.65 58.65 0 0 0-82.93 0l-5.11 5.11a12 12 0 0 0 17 17l5.12-5.12a34.63 34.63 0 1 1 49 49l-34.81 34.7A34.39 34.39 0 0 1 150.61 156a34.63 34.63 0 0 1-33.69-26.72a12 12 0 0 0-23.38 5.44A58.64 58.64 0 0 0 150.56 180h.05a58.28 58.28 0 0 0 41.47-17.17l34.75-34.75a58.62 58.62 0 0 0 0-82.91"
|
||||
/>
|
||||
</SVGWrap>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import SVGWrap from "./SVGWrap";
|
||||
|
||||
export default function Reload(props: I.SVG) {
|
||||
return (
|
||||
<SVGWrap {...props} viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 20q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4q1.725 0 3.3.712T18 6.75V5q0-.425.288-.712T19 4t.713.288T20 5v5q0 .425-.288.713T19 11h-5q-.425 0-.712-.288T13 10t.288-.712T14 9h3.2q-.8-1.4-2.187-2.2T12 6Q9.5 6 7.75 7.75T6 12t1.75 4.25T12 18q1.7 0 3.113-.862t2.187-2.313q.2-.35.563-.487t.737-.013q.4.125.575.525t-.025.75q-1.025 2-2.925 3.2T12 20"
|
||||
/>
|
||||
</SVGWrap>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import SVGWrap from "./SVGWrap";
|
||||
|
||||
export default function Send(props: I.SVG) {
|
||||
return (
|
||||
<SVGWrap {...props} viewBox="0 0 24 24">
|
||||
<g fill="none">
|
||||
<path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022m-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m21.433 4.861l-6 15.5a1 1 0 0 1-1.624.362l-3.382-3.235l-2.074 2.073a.5.5 0 0 1-.853-.354v-4.519L2.309 9.723a1 1 0 0 1 .442-1.691l17.5-4.5a1 1 0 0 1 1.181 1.329ZM19 6.001L8.032 13.152l1.735 1.66L19 6Z"
|
||||
/>
|
||||
</g>
|
||||
</SVGWrap>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import SVGWrap from "./SVGWrap";
|
||||
|
||||
export default function Setting(props: I.SVG) {
|
||||
return (
|
||||
<SVGWrap {...props} viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M3.082 13.945c-.529-.95-.793-1.426-.793-1.945c0-.519.264-.994.793-1.944L4.43 7.63l1.426-2.381c.559-.933.838-1.4 1.287-1.66c.45-.259.993-.267 2.08-.285L12 3.26l2.775.044c1.088.018 1.631.026 2.08.286c.45.26.73.726 1.288 1.659L19.57 7.63l1.35 2.426c.528.95.792 1.425.792 1.944c0 .519-.264.994-.793 1.944L19.57 16.37l-1.426 2.381c-.559.933-.838 1.4-1.287 1.66c-.45.259-.993.267-2.08.285L12 20.74l-2.775-.044c-1.088-.018-1.631-.026-2.08-.286c-.45-.26-.73-.726-1.288-1.659L4.43 16.37z" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</g>
|
||||
</SVGWrap>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import SVGWrap from "./SVGWrap";
|
||||
|
||||
export default function ThemeAuto(props: I.SVG) {
|
||||
return (
|
||||
<SVGWrap {...props} viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2S2 6.477 2 12s4.477 10 10 10m0-2V4a8 8 0 1 1 0 16"
|
||||
/>
|
||||
</SVGWrap>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import SVGWrap from "./SVGWrap";
|
||||
|
||||
export default function ThemeDark(props: I.SVG) {
|
||||
return (
|
||||
<SVGWrap {...props} viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 21q-3.775 0-6.387-2.613T3 12q0-3.45 2.25-5.988T11 3.05q.325-.05.575.088t.4.362t.163.525t-.188.575q-.425.65-.638 1.375T11.1 7.5q0 2.25 1.575 3.825T16.5 12.9q.775 0 1.538-.225t1.362-.625q.275-.175.563-.162t.512.137q.25.125.388.375t.087.6q-.35 3.45-2.937 5.725T12 21m0-2q2.2 0 3.95-1.213t2.55-3.162q-.5.125-1 .2t-1 .075q-3.075 0-5.238-2.163T9.1 7.5q0-.5.075-1t.2-1q-1.95.8-3.163 2.55T5 12q0 2.9 2.05 4.95T12 19m-.25-6.75"
|
||||
/>
|
||||
</SVGWrap>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import SVGWrap from "./SVGWrap";
|
||||
|
||||
export default function ThemeLight(props: I.SVG) {
|
||||
return (
|
||||
<SVGWrap {...props} viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 15q1.25 0 2.125-.875T15 12t-.875-2.125T12 9t-2.125.875T9 12t.875 2.125T12 15m0 2q-2.075 0-3.537-1.463T7 12t1.463-3.537T12 7t3.538 1.463T17 12t-1.463 3.538T12 17M2 13q-.425 0-.712-.288T1 12t.288-.712T2 11h2q.425 0 .713.288T5 12t-.288.713T4 13zm18 0q-.425 0-.712-.288T19 12t.288-.712T20 11h2q.425 0 .713.288T23 12t-.288.713T22 13zm-8-8q-.425 0-.712-.288T11 4V2q0-.425.288-.712T12 1t.713.288T13 2v2q0 .425-.288.713T12 5m0 18q-.425 0-.712-.288T11 22v-2q0-.425.288-.712T12 19t.713.288T13 20v2q0 .425-.288.713T12 23M5.65 7.05L4.575 6q-.3-.275-.288-.7t.288-.725q.3-.3.725-.3t.7.3L7.05 5.65q.275.3.275.7t-.275.7t-.687.288t-.713-.288M18 19.425l-1.05-1.075q-.275-.3-.275-.712t.275-.688q.275-.3.688-.287t.712.287L19.425 18q.3.275.288.7t-.288.725q-.3.3-.725.3t-.7-.3M16.95 7.05q-.3-.275-.288-.687t.288-.713L18 4.575q.275-.3.7-.288t.725.288q.3.3.3.725t-.3.7L18.35 7.05q-.3.275-.7.275t-.7-.275M4.575 19.425q-.3-.3-.3-.725t.3-.7l1.075-1.05q.3-.275.712-.275t.688.275q.3.275.288.688t-.288.712L6 19.425q-.275.3-.7.288t-.725-.288M12 12"
|
||||
/>
|
||||
</SVGWrap>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import SVGWrap from "./SVGWrap";
|
||||
|
||||
export default function UnPin(props: I.SVG) {
|
||||
return (
|
||||
<SVGWrap {...props} viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M14 4v5c0 1.12.37 2.16 1 3H9c.65-.86 1-1.9 1-3V4zm3-2H7c-.55 0-1 .45-1 1s.45 1 1 1h1v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1l1-1v-7H19v-2c-1.66 0-3-1.34-3-3V4h1c.55 0 1-.45 1-1s-.45-1-1-1"
|
||||
/>
|
||||
</SVGWrap>
|
||||
);
|
||||
}
|
||||
@@ -42,11 +42,6 @@
|
||||
"copyright": "©{{year}} INFINI Labs, All Rights Reserved."
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Advanced Settings",
|
||||
"endpoint": {
|
||||
"title": "API Endpoint",
|
||||
"description": "Domain name for interface and websocket"
|
||||
},
|
||||
"startup": {
|
||||
"title": "Startup Settings",
|
||||
"defaultStartupWindow": {
|
||||
|
||||
@@ -42,11 +42,6 @@
|
||||
"copyright": "©{{year}} INFINI Labs,保留所有权利。"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "高级设置",
|
||||
"endpoint": {
|
||||
"title": "API 接口",
|
||||
"description": "接口和 WebSocket 的域名"
|
||||
},
|
||||
"startup": {
|
||||
"title": "启动设置",
|
||||
"defaultStartupWindow": {
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
// mock
|
||||
export const res_search = {
|
||||
took: 2590,
|
||||
timed_out: false,
|
||||
_shards: {
|
||||
total: 1,
|
||||
successful: 1,
|
||||
skipped: 0,
|
||||
failed: 0
|
||||
},
|
||||
hits: {
|
||||
total: {
|
||||
value: 253,
|
||||
relation: "eq"
|
||||
},
|
||||
max_score: 32.709457,
|
||||
hits: [
|
||||
{
|
||||
_index: "coco_document",
|
||||
_type: "_doc",
|
||||
_id: "3ac857ef30d101b1e5880b53b1438b1a",
|
||||
_score: 32.709457,
|
||||
_source: {
|
||||
icon: "web",
|
||||
id: "3ac857ef30d101b1e5880b53b1438b1a",
|
||||
source: {
|
||||
name: "hugo_site",
|
||||
type: "connector"
|
||||
},
|
||||
type: "web_page",
|
||||
category: "Aggregation",
|
||||
subcategory: "Metric",
|
||||
title: "Avg aggregation",
|
||||
content: "",
|
||||
author: "liaosy",
|
||||
url: "https://pizza.rs/docs/references/aggregation/avg/",
|
||||
tags: [
|
||||
"avg",
|
||||
"aggregation"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
_index: "coco_document",
|
||||
_type: "_doc",
|
||||
_id: "2485a744c5dae1278a01c04d39bf60a6",
|
||||
_score: 32.37022,
|
||||
_source: {
|
||||
icon: "web",
|
||||
id: "2485a744c5dae1278a01c04d39bf60a6",
|
||||
source: {
|
||||
name: "hugo_site",
|
||||
type: "connector"
|
||||
},
|
||||
type: "web_page",
|
||||
title: "auto_generate_doc_id",
|
||||
content: "",
|
||||
author: "liaosy",
|
||||
url: "https://infinilabs.cn/docs/latest/gateway/references/filters/auto_generate_doc_id/"
|
||||
}
|
||||
},
|
||||
{
|
||||
_index: "coco_document",
|
||||
_type: "_doc",
|
||||
_id: "15aa340fa9ddfcfbf793b8707a4fa16b",
|
||||
_score: 21.983166,
|
||||
_source: {
|
||||
icon: "web",
|
||||
id: "15aa340fa9ddfcfbf793b8707a4fa16b",
|
||||
source: {
|
||||
name: "hugo_site",
|
||||
type: "connector"
|
||||
},
|
||||
type: "web_page",
|
||||
category: "Overview",
|
||||
subcategory: "Architecture",
|
||||
title: "Architecture",
|
||||
content: "",
|
||||
author: "yangfan",
|
||||
url: "https://pizza.rs/docs/overview/architecture/",
|
||||
tags: [
|
||||
"architecture"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
_index: "coco_document",
|
||||
_type: "_doc",
|
||||
_id: "f96b1af318d62a43a44f54731409ff52",
|
||||
_score: 21.887964,
|
||||
_source: {
|
||||
icon: "web",
|
||||
id: "f96b1af318d62a43a44f54731409ff52",
|
||||
source: {
|
||||
name: "hugo_site",
|
||||
type: "connector"
|
||||
},
|
||||
type: "web_page",
|
||||
title: "Introducing Coco AI in Two Minutes - A Quick Start Video 🥥",
|
||||
content: "",
|
||||
author: "yangfan",
|
||||
url: "https://blog.infinilabs.com/posts/2024/a-quick-start-viideo-to-introduce-coco-ai-in-two-minutes/",
|
||||
tags: [
|
||||
"Coco AI",
|
||||
"Search",
|
||||
"Gen-AI",
|
||||
"Enterprise"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
_index: "coco_document",
|
||||
_type: "_doc",
|
||||
_id: "3a806937f9e7fe55905a7f71d111e523",
|
||||
_score: 21.286049,
|
||||
_source: {
|
||||
icon: "web",
|
||||
id: "3a806937f9e7fe55905a7f71d111e523",
|
||||
source: {
|
||||
name: "hugo_site",
|
||||
type: "connector"
|
||||
},
|
||||
type: "web_page",
|
||||
category: "Catalog",
|
||||
subcategory: "Namespace",
|
||||
title: "Create a namespace",
|
||||
content: "",
|
||||
author: "yangfan",
|
||||
url: "https://pizza.rs/docs/references/namespace/create/",
|
||||
tags: [
|
||||
"create",
|
||||
"namespace"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
_index: "coco_document",
|
||||
_type: "_doc",
|
||||
_id: "04f48643c2c52b872c149e077765f8cb",
|
||||
_score: 21.286049,
|
||||
_source: {
|
||||
icon: "web",
|
||||
id: "04f48643c2c52b872c149e077765f8cb",
|
||||
source: {
|
||||
name: "hugo_site",
|
||||
type: "connector"
|
||||
},
|
||||
type: "web_page",
|
||||
category: "Document",
|
||||
subcategory: "Index",
|
||||
title: "Create a document",
|
||||
content: "",
|
||||
author: "zouwenan",
|
||||
url: "https://pizza.rs/docs/references/document/create/",
|
||||
tags: [
|
||||
"create",
|
||||
"index"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
_index: "coco_document",
|
||||
_type: "_doc",
|
||||
_id: "d101818b1e6d2eb23ca2f813ef3a9648",
|
||||
_score: 21.213268,
|
||||
_source: {
|
||||
icon: "web",
|
||||
id: "d101818b1e6d2eb23ca2f813ef3a9648",
|
||||
source: {
|
||||
name: "hugo_site",
|
||||
type: "connector"
|
||||
},
|
||||
type: "web_page",
|
||||
category: "Catalog",
|
||||
subcategory: "Collection",
|
||||
title: "Create a collection",
|
||||
content: " ",
|
||||
author: "zouwenan",
|
||||
url: "https://pizza.rs/docs/references/collection/create/",
|
||||
tags: [
|
||||
"create",
|
||||
"collection"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
_index: "coco_document",
|
||||
_type: "_doc",
|
||||
_id: "ee2228755039808c199e6812d09c745e",
|
||||
_score: 20.967154,
|
||||
_source: {
|
||||
icon: "web",
|
||||
id: "ee2228755039808c199e6812d09c745e",
|
||||
source: {
|
||||
name: "hugo_site",
|
||||
type: "connector"
|
||||
},
|
||||
type: "web_page",
|
||||
category: "Document",
|
||||
subcategory: "Index",
|
||||
title: "Delete a document",
|
||||
content: "",
|
||||
author: "zouwenan",
|
||||
url: "https://pizza.rs/docs/references/document/delete/",
|
||||
tags: [
|
||||
"delete",
|
||||
"index"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
_index: "coco_document",
|
||||
_type: "_doc",
|
||||
_id: "60584d12aba0ff569e7b79a7be168810",
|
||||
_score: 20.967154,
|
||||
_source: {
|
||||
icon: "web",
|
||||
id: "60584d12aba0ff569e7b79a7be168810",
|
||||
source: {
|
||||
name: "hugo_site",
|
||||
type: "connector"
|
||||
},
|
||||
type: "web_page",
|
||||
category: "Catalog",
|
||||
subcategory: "Namespace",
|
||||
title: "Delete a namespace",
|
||||
content: "",
|
||||
author: "zouwenan",
|
||||
url: "https://pizza.rs/docs/references/namespace/delete/",
|
||||
tags: [
|
||||
"delete",
|
||||
"namespace"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
_index: "coco_document",
|
||||
_type: "_doc",
|
||||
_id: "73e31d0feeb8d3d97e4b06a98de54672",
|
||||
_score: 20.934437,
|
||||
_source: {
|
||||
icon: "web",
|
||||
id: "73e31d0feeb8d3d97e4b06a98de54672",
|
||||
source: {
|
||||
name: "hugo_site",
|
||||
type: "connector"
|
||||
},
|
||||
type: "web_page",
|
||||
category: "Document",
|
||||
subcategory: "Index",
|
||||
title: "Replace a document",
|
||||
content: " ",
|
||||
author: "medcl",
|
||||
url: "https://pizza.rs/docs/references/document/replace/",
|
||||
tags: [
|
||||
"replace",
|
||||
"index"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ export type IAppStore = {
|
||||
|
||||
endpoint: AppEndpoint;
|
||||
endpoint_http: string;
|
||||
endpoint_websocket: string;
|
||||
setEndpoint: (endpoint: AppEndpoint) => void;
|
||||
language: string;
|
||||
setLanguage: (language: string) => void;
|
||||
@@ -78,20 +77,12 @@ export const useAppStore = create<IAppStore>()(
|
||||
|
||||
endpoint: "https://coco.infini.cloud/",
|
||||
endpoint_http: "https://coco.infini.cloud",
|
||||
endpoint_websocket: "wss://coco.infini.cloud/ws",
|
||||
setEndpoint: async (endpoint: AppEndpoint) => {
|
||||
const endpoint_http = endpoint;
|
||||
|
||||
const withoutProtocol = endpoint.split("//")[1];
|
||||
|
||||
const endpoint_websocket = endpoint?.includes("https")
|
||||
? `wss://${withoutProtocol}/ws`
|
||||
: `ws://${withoutProtocol}/ws`;
|
||||
|
||||
return set({
|
||||
endpoint,
|
||||
endpoint_http,
|
||||
endpoint_websocket,
|
||||
});
|
||||
},
|
||||
language: "en",
|
||||
@@ -128,7 +119,6 @@ export const useAppStore = create<IAppStore>()(
|
||||
ssoRequestID: state.ssoRequestID,
|
||||
endpoint: state.endpoint,
|
||||
endpoint_http: state.endpoint_http,
|
||||
endpoint_websocket: state.endpoint_websocket,
|
||||
language: state.language,
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ export interface EventPayloads {
|
||||
"endpoint-changed": {
|
||||
endpoint: string;
|
||||
endpoint_http: string;
|
||||
endpoint_websocket: string;
|
||||
};
|
||||
"showTooltip-changed": {
|
||||
showTooltip: boolean;
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export const clientEnv = {
|
||||
COCO_SERVER_URL: "https://coco.infini.cloud", // http://localhost:9000
|
||||
COCO_WEBSOCKET_URL: "wss://coco.infini.cloud/ws", // ws://localhost:9000/ws
|
||||
};
|
||||
@@ -1,531 +0,0 @@
|
||||
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
|
||||
|
||||
/** user-defined commands **/
|
||||
export const commands = {
|
||||
async getRecordingOptions(): Promise<RecordingOptions> {
|
||||
return await TAURI_INVOKE("get_recording_options");
|
||||
},
|
||||
async setRecordingOptions(options: RecordingOptions): Promise<null> {
|
||||
return await TAURI_INVOKE("set_recording_options", { options });
|
||||
},
|
||||
async startRecording(): Promise<null> {
|
||||
return await TAURI_INVOKE("start_recording");
|
||||
},
|
||||
async stopRecording(): Promise<null> {
|
||||
return await TAURI_INVOKE("stop_recording");
|
||||
},
|
||||
async pauseRecording(): Promise<null> {
|
||||
return await TAURI_INVOKE("pause_recording");
|
||||
},
|
||||
async resumeRecording(): Promise<null> {
|
||||
return await TAURI_INVOKE("resume_recording");
|
||||
},
|
||||
async listCameras(): Promise<string[]> {
|
||||
return await TAURI_INVOKE("list_cameras");
|
||||
},
|
||||
async listCaptureWindows(): Promise<CaptureWindow[]> {
|
||||
return await TAURI_INVOKE("list_capture_windows");
|
||||
},
|
||||
async listCaptureScreens(): Promise<CaptureScreen[]> {
|
||||
return await TAURI_INVOKE("list_capture_screens");
|
||||
},
|
||||
async takeScreenshot(): Promise<null> {
|
||||
return await TAURI_INVOKE("take_screenshot");
|
||||
},
|
||||
async listAudioDevices(): Promise<string[]> {
|
||||
return await TAURI_INVOKE("list_audio_devices");
|
||||
},
|
||||
async closePreviousRecordingsWindow(): Promise<void> {
|
||||
await TAURI_INVOKE("close_previous_recordings_window");
|
||||
},
|
||||
async setFakeWindowBounds(name: string, bounds: Bounds): Promise<null> {
|
||||
return await TAURI_INVOKE("set_fake_window_bounds", { name, bounds });
|
||||
},
|
||||
async removeFakeWindow(name: string): Promise<null> {
|
||||
return await TAURI_INVOKE("remove_fake_window", { name });
|
||||
},
|
||||
async focusCapturesPanel(): Promise<void> {
|
||||
await TAURI_INVOKE("focus_captures_panel");
|
||||
},
|
||||
async getCurrentRecording(): Promise<JsonValue<RecordingInfo | null>> {
|
||||
return await TAURI_INVOKE("get_current_recording");
|
||||
},
|
||||
async exportVideo(
|
||||
videoId: string,
|
||||
project: ProjectConfiguration,
|
||||
progress: TAURI_CHANNEL<RenderProgress>,
|
||||
force: boolean
|
||||
): Promise<string> {
|
||||
return await TAURI_INVOKE("export_video", {
|
||||
videoId,
|
||||
project,
|
||||
progress,
|
||||
force,
|
||||
});
|
||||
},
|
||||
async copyFileToPath(src: string, dst: string): Promise<null> {
|
||||
return await TAURI_INVOKE("copy_file_to_path", { src, dst });
|
||||
},
|
||||
async copyVideoToClipboard(path: string): Promise<null> {
|
||||
return await TAURI_INVOKE("copy_video_to_clipboard", { path });
|
||||
},
|
||||
async copyScreenshotToClipboard(path: string): Promise<null> {
|
||||
return await TAURI_INVOKE("copy_screenshot_to_clipboard", { path });
|
||||
},
|
||||
async openFilePath(path: string): Promise<null> {
|
||||
return await TAURI_INVOKE("open_file_path", { path });
|
||||
},
|
||||
async getVideoMetadata(
|
||||
videoId: string,
|
||||
videoType: VideoType | null
|
||||
): Promise<VideoRecordingMetadata> {
|
||||
return await TAURI_INVOKE("get_video_metadata", { videoId, videoType });
|
||||
},
|
||||
async createEditorInstance(
|
||||
videoId: string
|
||||
): Promise<SerializedEditorInstance> {
|
||||
return await TAURI_INVOKE("create_editor_instance", { videoId });
|
||||
},
|
||||
async startPlayback(videoId: string): Promise<void> {
|
||||
await TAURI_INVOKE("start_playback", { videoId });
|
||||
},
|
||||
async stopPlayback(videoId: string): Promise<void> {
|
||||
await TAURI_INVOKE("stop_playback", { videoId });
|
||||
},
|
||||
async setPlayheadPosition(
|
||||
videoId: string,
|
||||
frameNumber: number
|
||||
): Promise<void> {
|
||||
await TAURI_INVOKE("set_playhead_position", { videoId, frameNumber });
|
||||
},
|
||||
async setProjectConfig(
|
||||
videoId: string,
|
||||
config: ProjectConfiguration
|
||||
): Promise<void> {
|
||||
await TAURI_INVOKE("set_project_config", { videoId, config });
|
||||
},
|
||||
async openEditor(id: string): Promise<void> {
|
||||
await TAURI_INVOKE("open_editor", { id });
|
||||
},
|
||||
async openMainWindow(): Promise<void> {
|
||||
await TAURI_INVOKE("open_main_window");
|
||||
},
|
||||
async openPermissionSettings(permission: OSPermission): Promise<void> {
|
||||
await TAURI_INVOKE("open_permission_settings", { permission });
|
||||
},
|
||||
async doPermissionsCheck(initialCheck: boolean): Promise<OSPermissionsCheck> {
|
||||
return await TAURI_INVOKE("do_permissions_check", { initialCheck });
|
||||
},
|
||||
async requestPermission(permission: OSPermission): Promise<void> {
|
||||
await TAURI_INVOKE("request_permission", { permission });
|
||||
},
|
||||
async uploadExportedVideo(
|
||||
videoId: string,
|
||||
mode: UploadMode
|
||||
): Promise<UploadResult> {
|
||||
return await TAURI_INVOKE("upload_exported_video", { videoId, mode });
|
||||
},
|
||||
async uploadScreenshot(screenshotPath: string): Promise<UploadResult> {
|
||||
return await TAURI_INVOKE("upload_screenshot", { screenshotPath });
|
||||
},
|
||||
async getRecordingMeta(id: string, fileType: string): Promise<RecordingMeta> {
|
||||
return await TAURI_INVOKE("get_recording_meta", { id, fileType });
|
||||
},
|
||||
async saveFileDialog(
|
||||
fileName: string,
|
||||
fileType: string
|
||||
): Promise<string | null> {
|
||||
return await TAURI_INVOKE("save_file_dialog", { fileName, fileType });
|
||||
},
|
||||
async listRecordings(): Promise<[string, string, RecordingMeta][]> {
|
||||
return await TAURI_INVOKE("list_recordings");
|
||||
},
|
||||
async listScreenshots(): Promise<[string, string, RecordingMeta][]> {
|
||||
return await TAURI_INVOKE("list_screenshots");
|
||||
},
|
||||
async checkUpgradedAndUpdate(): Promise<boolean> {
|
||||
return await TAURI_INVOKE("check_upgraded_and_update");
|
||||
},
|
||||
async openExternalLink(url: string): Promise<null> {
|
||||
return await TAURI_INVOKE("open_external_link", { url });
|
||||
},
|
||||
async setHotkey(action: HotkeyAction, hotkey: Hotkey | null): Promise<null> {
|
||||
return await TAURI_INVOKE("set_hotkey", { action, hotkey });
|
||||
},
|
||||
async deleteAuthOpenSignin(): Promise<null> {
|
||||
return await TAURI_INVOKE("delete_auth_open_signin");
|
||||
},
|
||||
async resetCameraPermissions(): Promise<null> {
|
||||
return await TAURI_INVOKE("reset_camera_permissions");
|
||||
},
|
||||
async resetMicrophonePermissions(): Promise<null> {
|
||||
return await TAURI_INVOKE("reset_microphone_permissions");
|
||||
},
|
||||
async isCameraWindowOpen(): Promise<boolean> {
|
||||
return await TAURI_INVOKE("is_camera_window_open");
|
||||
},
|
||||
async seekTo(videoId: string, frameNumber: number): Promise<void> {
|
||||
await TAURI_INVOKE("seek_to", { videoId, frameNumber });
|
||||
},
|
||||
async sendFeedbackRequest(feedback: string): Promise<null> {
|
||||
return await TAURI_INVOKE("send_feedback_request", { feedback });
|
||||
},
|
||||
async positionTrafficLights(
|
||||
controlsInset: [number, number] | null
|
||||
): Promise<void> {
|
||||
await TAURI_INVOKE("position_traffic_lights", { controlsInset });
|
||||
},
|
||||
async setTheme(theme: AppTheme): Promise<void> {
|
||||
await TAURI_INVOKE("set_theme", { theme });
|
||||
},
|
||||
async globalMessageDialog(message: string): Promise<void> {
|
||||
await TAURI_INVOKE("global_message_dialog", { message });
|
||||
},
|
||||
async showWindow(window: ShowCapWindow): Promise<void> {
|
||||
await TAURI_INVOKE("show_window", { window });
|
||||
},
|
||||
async writeClipboardString(text: string): Promise<null> {
|
||||
return await TAURI_INVOKE("write_clipboard_string", { text });
|
||||
},
|
||||
};
|
||||
|
||||
/** user-defined events **/
|
||||
|
||||
export const events = __makeEvents__<{
|
||||
audioInputLevelChange: AudioInputLevelChange;
|
||||
authenticationInvalid: AuthenticationInvalid;
|
||||
currentRecordingChanged: CurrentRecordingChanged;
|
||||
editorStateChanged: EditorStateChanged;
|
||||
newNotification: NewNotification;
|
||||
newRecordingAdded: NewRecordingAdded;
|
||||
newScreenshotAdded: NewScreenshotAdded;
|
||||
recordingMetaChanged: RecordingMetaChanged;
|
||||
recordingOptionsChanged: RecordingOptionsChanged;
|
||||
recordingStarted: RecordingStarted;
|
||||
recordingStopped: RecordingStopped;
|
||||
renderFrameEvent: RenderFrameEvent;
|
||||
requestNewScreenshot: RequestNewScreenshot;
|
||||
requestOpenSettings: RequestOpenSettings;
|
||||
requestRestartRecording: RequestRestartRecording;
|
||||
requestStartRecording: RequestStartRecording;
|
||||
requestStopRecording: RequestStopRecording;
|
||||
uploadProgress: UploadProgress;
|
||||
}>({
|
||||
audioInputLevelChange: "audio-input-level-change",
|
||||
authenticationInvalid: "authentication-invalid",
|
||||
currentRecordingChanged: "current-recording-changed",
|
||||
editorStateChanged: "editor-state-changed",
|
||||
newNotification: "new-notification",
|
||||
newRecordingAdded: "new-recording-added",
|
||||
newScreenshotAdded: "new-screenshot-added",
|
||||
recordingMetaChanged: "recording-meta-changed",
|
||||
recordingOptionsChanged: "recording-options-changed",
|
||||
recordingStarted: "recording-started",
|
||||
recordingStopped: "recording-stopped",
|
||||
renderFrameEvent: "render-frame-event",
|
||||
requestNewScreenshot: "request-new-screenshot",
|
||||
requestOpenSettings: "request-open-settings",
|
||||
requestRestartRecording: "request-restart-recording",
|
||||
requestStartRecording: "request-start-recording",
|
||||
requestStopRecording: "request-stop-recording",
|
||||
uploadProgress: "upload-progress",
|
||||
});
|
||||
|
||||
/** user-defined constants **/
|
||||
|
||||
/** user-defined types **/
|
||||
export type AppEndpoint = string;
|
||||
export type AppTheme = "auto" | "light" | "dark";
|
||||
export type WindowTheme = "light" | "dark";
|
||||
export type AspectRatio = "wide" | "vertical" | "square" | "classic" | "tall";
|
||||
export type Audio = { duration: number; sample_rate: number; channels: number };
|
||||
export type AudioConfiguration = { mute: boolean; improve: boolean };
|
||||
export type AudioInputLevelChange = number;
|
||||
export type AudioMeta = { path: string };
|
||||
export type AuthStore = {
|
||||
token: string;
|
||||
user_id: string | null;
|
||||
expires: number;
|
||||
plan: Plan | null;
|
||||
};
|
||||
export type AuthenticationInvalid = null;
|
||||
export type BackgroundConfiguration = {
|
||||
source: BackgroundSource;
|
||||
blur: number;
|
||||
padding: number;
|
||||
rounding: number;
|
||||
inset: number;
|
||||
crop: Crop | null;
|
||||
};
|
||||
export type BackgroundSource =
|
||||
| { type: "wallpaper"; id: number }
|
||||
| { type: "image"; path: string | null }
|
||||
| { type: "color"; value: [number, number, number] }
|
||||
| {
|
||||
type: "gradient";
|
||||
from: [number, number, number];
|
||||
to: [number, number, number];
|
||||
angle?: number;
|
||||
};
|
||||
export type Bounds = { x: number; y: number; width: number; height: number };
|
||||
export type Camera = {
|
||||
hide: boolean;
|
||||
mirror: boolean;
|
||||
position: CameraPosition;
|
||||
size: number;
|
||||
zoom_size: number | null;
|
||||
rounding: number;
|
||||
shadow: number;
|
||||
};
|
||||
export type CameraMeta = { path: string };
|
||||
export type CameraPosition = { x: CameraXPosition; y: CameraYPosition };
|
||||
export type CameraXPosition = "left" | "center" | "right";
|
||||
export type CameraYPosition = "top" | "bottom";
|
||||
export type CaptureScreen = { id: number; name: string };
|
||||
export type CaptureWindow = {
|
||||
id: number;
|
||||
owner_name: string;
|
||||
name: string;
|
||||
bounds: Bounds;
|
||||
};
|
||||
export type Crop = { position: XY<number>; size: XY<number> };
|
||||
export type CurrentRecordingChanged = null;
|
||||
export type CursorAnimationStyle = "regular" | "slow" | "fast";
|
||||
export type CursorConfiguration = {
|
||||
hideWhenIdle: boolean;
|
||||
size: number;
|
||||
type: CursorType;
|
||||
animationStyle: CursorAnimationStyle;
|
||||
};
|
||||
export type CursorType = "pointer" | "circle";
|
||||
export type Display = { path: string };
|
||||
export type EditorStateChanged = { playhead_position: number };
|
||||
export type Flags = {
|
||||
recordMouse: boolean;
|
||||
split: boolean;
|
||||
pauseResume: boolean;
|
||||
zoom: boolean;
|
||||
customS3: boolean;
|
||||
};
|
||||
export type GeneralSettingsStore = {
|
||||
uploadIndividualFiles?: boolean;
|
||||
openEditorAfterRecording?: boolean;
|
||||
hideDockIcon?: boolean;
|
||||
autoCreateShareableLink?: boolean;
|
||||
enableNotifications?: boolean;
|
||||
disableAutoOpenLinks?: boolean;
|
||||
hasCompletedStartup?: boolean;
|
||||
theme?: AppTheme;
|
||||
};
|
||||
export type Hotkey = {
|
||||
code: string;
|
||||
meta: boolean;
|
||||
ctrl: boolean;
|
||||
alt: boolean;
|
||||
shift: boolean;
|
||||
};
|
||||
export type HotkeyAction =
|
||||
| "startRecording"
|
||||
| "stopRecording"
|
||||
| "restartRecording"
|
||||
| "takeScreenshot";
|
||||
export type HotkeysConfiguration = { show: boolean };
|
||||
export type HotkeysStore = { hotkeys: { [key in HotkeyAction]: Hotkey } };
|
||||
export type JsonValue<T> = [T];
|
||||
export type MultipleSegment = {
|
||||
display: Display;
|
||||
camera?: CameraMeta | null;
|
||||
audio?: AudioMeta | null;
|
||||
cursor?: string | null;
|
||||
};
|
||||
export type MultipleSegments = {
|
||||
segments: MultipleSegment[];
|
||||
cursors: { [key in string]: string };
|
||||
};
|
||||
export type NewNotification = {
|
||||
title: string;
|
||||
body: string;
|
||||
is_error: boolean;
|
||||
};
|
||||
export type NewRecordingAdded = { path: string };
|
||||
export type NewScreenshotAdded = { path: string };
|
||||
export type OSPermission =
|
||||
| "screenRecording"
|
||||
| "camera"
|
||||
| "microphone"
|
||||
| "accessibility";
|
||||
export type OSPermissionStatus = "notNeeded" | "empty" | "granted" | "denied";
|
||||
export type OSPermissionsCheck = {
|
||||
screenRecording: OSPermissionStatus;
|
||||
microphone: OSPermissionStatus;
|
||||
camera: OSPermissionStatus;
|
||||
accessibility: OSPermissionStatus;
|
||||
};
|
||||
export type Plan = { upgraded: boolean; last_checked: number };
|
||||
export type PreCreatedVideo = {
|
||||
id: string;
|
||||
link: string;
|
||||
config: S3UploadMeta;
|
||||
};
|
||||
export type ProjectConfiguration = {
|
||||
aspectRatio: AspectRatio | null;
|
||||
background: BackgroundConfiguration;
|
||||
camera: Camera;
|
||||
audio: AudioConfiguration;
|
||||
cursor: CursorConfiguration;
|
||||
hotkeys: HotkeysConfiguration;
|
||||
timeline?: TimelineConfiguration | null;
|
||||
motionBlur: number | null;
|
||||
};
|
||||
export type ProjectRecordings = { segments: SegmentRecordings[] };
|
||||
export type RecordingInfo = { captureTarget: ScreenCaptureTarget };
|
||||
export type RecordingMeta = (
|
||||
| { segment: SingleSegment }
|
||||
| { inner: MultipleSegments }
|
||||
) & { pretty_name: string; sharing?: SharingMeta | null };
|
||||
export type RecordingMetaChanged = { id: string };
|
||||
export type RecordingOptions = {
|
||||
captureTarget: ScreenCaptureTarget;
|
||||
cameraLabel: string | null;
|
||||
audioInputName: string | null;
|
||||
};
|
||||
export type RecordingOptionsChanged = null;
|
||||
export type RecordingStarted = null;
|
||||
export type RecordingStopped = { path: string };
|
||||
export type RenderFrameEvent = { frame_number: number };
|
||||
export type RenderProgress =
|
||||
| { type: "Starting"; total_frames: number }
|
||||
| { type: "EstimatedTotalFrames"; total_frames: number }
|
||||
| { type: "FrameRendered"; current_frame: number };
|
||||
export type RequestNewScreenshot = null;
|
||||
export type RequestOpenSettings = { page: string };
|
||||
export type RequestRestartRecording = null;
|
||||
export type RequestStartRecording = null;
|
||||
export type RequestStopRecording = null;
|
||||
export type S3UploadMeta = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
aws_region?: string;
|
||||
aws_bucket?: string;
|
||||
};
|
||||
export type ScreenCaptureTarget =
|
||||
| ({ variant: "window" } & CaptureWindow)
|
||||
| ({ variant: "screen" } & CaptureScreen);
|
||||
export type SegmentRecordings = {
|
||||
display: Video;
|
||||
camera: Video | null;
|
||||
audio: Audio | null;
|
||||
};
|
||||
export type SerializedEditorInstance = {
|
||||
framesSocketUrl: string;
|
||||
recordingDuration: number;
|
||||
savedProjectConfig: ProjectConfiguration;
|
||||
recordings: ProjectRecordings;
|
||||
path: string;
|
||||
prettyName: string;
|
||||
};
|
||||
export type SharingMeta = { id: string; link: string };
|
||||
export type ShowCapWindow =
|
||||
| "Setup"
|
||||
| "Main"
|
||||
| { Settings: { page: string | null } }
|
||||
| { Editor: { project_id: string } }
|
||||
| "PrevRecordings"
|
||||
| "WindowCaptureOccluder"
|
||||
| { Camera: { ws_port: number } }
|
||||
| { InProgressRecording: { position: [number, number] | null } }
|
||||
| "Upgrade";
|
||||
export type SingleSegment = {
|
||||
display: Display;
|
||||
camera?: CameraMeta | null;
|
||||
audio?: AudioMeta | null;
|
||||
cursor?: string | null;
|
||||
};
|
||||
export type TimelineConfiguration = {
|
||||
segments: TimelineSegment[];
|
||||
zoomSegments?: ZoomSegment[];
|
||||
};
|
||||
export type TimelineSegment = {
|
||||
recordingSegment: number | null;
|
||||
timescale: number;
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
export type UploadMode =
|
||||
| { Initial: { pre_created_video: PreCreatedVideo | null } }
|
||||
| "Reupload";
|
||||
export type UploadProgress = {
|
||||
stage: string;
|
||||
progress: number;
|
||||
message: string;
|
||||
};
|
||||
export type UploadResult =
|
||||
| { Success: string }
|
||||
| "NotAuthenticated"
|
||||
| "PlanCheckFailed"
|
||||
| "UpgradeRequired";
|
||||
export type Video = { duration: number; width: number; height: number };
|
||||
export type VideoRecordingMetadata = { duration: number; size: number };
|
||||
export type VideoType = "screen" | "output";
|
||||
export type XY<T> = { x: T; y: T };
|
||||
export type ZoomSegment = { start: number; end: number; amount: number };
|
||||
|
||||
/** tauri-specta globals **/
|
||||
|
||||
import {
|
||||
invoke as TAURI_INVOKE,
|
||||
Channel as TAURI_CHANNEL,
|
||||
} from "@tauri-apps/api/core";
|
||||
import * as TAURI_API_EVENT from "@tauri-apps/api/event";
|
||||
import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow";
|
||||
|
||||
type __EventObj__<T> = {
|
||||
listen: (
|
||||
cb: TAURI_API_EVENT.EventCallback<T>
|
||||
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
|
||||
once: (
|
||||
cb: TAURI_API_EVENT.EventCallback<T>
|
||||
) => ReturnType<typeof TAURI_API_EVENT.once<T>>;
|
||||
emit: null extends T
|
||||
? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
|
||||
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
|
||||
};
|
||||
|
||||
export type Result<T, E> =
|
||||
| { status: "ok"; data: T }
|
||||
| { status: "error"; error: E };
|
||||
|
||||
function __makeEvents__<T extends Record<string, any>>(
|
||||
mappings: Record<keyof T, string>
|
||||
) {
|
||||
return new Proxy(
|
||||
{} as unknown as {
|
||||
[K in keyof T]: __EventObj__<T[K]> & {
|
||||
(handle: __WebviewWindow__): __EventObj__<T[K]>;
|
||||
};
|
||||
},
|
||||
{
|
||||
get: (_, event) => {
|
||||
const name = mappings[event as keyof T];
|
||||
|
||||
return new Proxy((() => {}) as any, {
|
||||
apply: (_, __, [window]: [__WebviewWindow__]) => ({
|
||||
listen: (arg: any) => window.listen(name, arg),
|
||||
once: (arg: any) => window.once(name, arg),
|
||||
emit: (arg: any) => window.emit(name, arg),
|
||||
}),
|
||||
get: (_, command: keyof __EventObj__<any>) => {
|
||||
switch (command) {
|
||||
case "listen":
|
||||
return (arg: any) => TAURI_API_EVENT.listen(name, arg);
|
||||
case "once":
|
||||
return (arg: any) => TAURI_API_EVENT.once(name, arg);
|
||||
case "emit":
|
||||
return (arg: any) => TAURI_API_EVENT.emit(name, arg);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@@ -2,7 +2,6 @@
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly COCO_SERVER_URL: string;
|
||||
readonly COCO_WEBSOCKET_URL: string;
|
||||
// more env variables...
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user