feat: add not login tips (#244)

This commit is contained in:
BiggerRain
2025-03-05 21:36:05 +08:00
committed by GitHub
parent c7b201f068
commit a212d3bbb7
13 changed files with 262 additions and 356 deletions

View File

@@ -26,7 +26,6 @@
"@tauri-apps/plugin-websocket": "~2.3.0",
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
"ahooks": "^3.8.4",
"axios": "^1.8.1",
"clsx": "^2.1.1",
"dotenv": "^16.4.7",
"filesize": "^10.1.6",

157
pnpm-lock.yaml generated
View File

@@ -47,9 +47,6 @@ importers:
ahooks:
specifier: ^3.8.4
version: 3.8.4(react@18.3.1)
axios:
specifier: ^1.8.1
version: 1.8.1
clsx:
specifier: ^2.1.1
version: 2.1.1
@@ -1363,9 +1360,6 @@ packages:
async-retry@1.3.3:
resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
atomically@2.0.3:
resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==}
@@ -1376,9 +1370,6 @@ packages:
peerDependencies:
postcss: ^8.1.0
axios@1.8.1:
resolution: {integrity: sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==}
bail@2.0.2:
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
@@ -1419,10 +1410,6 @@ packages:
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
engines: {node: '>=18'}
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
@@ -1503,10 +1490,6 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
comma-separated-tokens@2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
@@ -1766,10 +1749,6 @@ packages:
delaunator@5.0.1:
resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@@ -1794,10 +1773,6 @@ packages:
resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==}
engines: {node: '>=12'}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
@@ -1824,22 +1799,6 @@ packages:
error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
es-errors@1.3.0:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
es-set-tostringtag@2.1.0:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
esbuild@0.21.5:
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
engines: {node: '>=12'}
@@ -1923,23 +1882,10 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
follow-redirects@1.15.9:
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
form-data@4.0.2:
resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
engines: {node: '>= 6'}
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
@@ -1962,14 +1908,6 @@ packages:
resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
engines: {node: '>=18'}
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
get-proto@1.0.1:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
get-stream@8.0.1:
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
engines: {node: '>=16'}
@@ -2023,10 +1961,6 @@ packages:
resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==}
engines: {node: '>=18'}
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
graceful-fs@4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
@@ -2036,14 +1970,6 @@ packages:
hachure-fill@0.5.2:
resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
has-tostringtag@1.0.2:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
engines: {node: '>= 0.4'}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
@@ -2409,10 +2335,6 @@ packages:
engines: {node: '>= 18'}
hasBin: true
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
mdast-util-find-and-replace@3.0.2:
resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
@@ -4519,8 +4441,6 @@ snapshots:
dependencies:
retry: 0.13.1
asynckit@0.4.0: {}
atomically@2.0.3:
dependencies:
stubborn-fs: 1.2.5
@@ -4536,14 +4456,6 @@ snapshots:
postcss: 8.5.3
postcss-value-parser: 4.2.0
axios@1.8.1:
dependencies:
follow-redirects: 1.15.9
form-data: 4.0.2
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
bail@2.0.2: {}
balanced-match@1.0.2: {}
@@ -4589,11 +4501,6 @@ snapshots:
dependencies:
run-applescript: 7.0.0
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
function-bind: 1.1.2
callsites@3.1.0: {}
camelcase-css@2.0.1: {}
@@ -4662,10 +4569,6 @@ snapshots:
color-name@1.1.4: {}
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
comma-separated-tokens@2.0.3: {}
commander@4.1.1: {}
@@ -4938,8 +4841,6 @@ snapshots:
dependencies:
robust-predicates: 3.0.2
delayed-stream@1.0.0: {}
dequal@2.0.3: {}
devlop@1.1.0:
@@ -4960,12 +4861,6 @@ snapshots:
dotenv@16.4.7: {}
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
es-errors: 1.3.0
gopd: 1.2.0
eastasianwidth@0.2.0: {}
electron-to-chromium@1.5.112: {}
@@ -4984,21 +4879,6 @@ snapshots:
dependencies:
is-arrayish: 0.2.1
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
es-set-tostringtag@2.1.0:
dependencies:
es-errors: 1.3.0
get-intrinsic: 1.3.0
has-tostringtag: 1.0.2
hasown: 2.0.2
esbuild@0.21.5:
optionalDependencies:
'@esbuild/aix-ppc64': 0.21.5
@@ -5136,20 +5016,11 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
follow-redirects@1.15.9: {}
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
signal-exit: 4.1.0
form-data@4.0.2:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
es-set-tostringtag: 2.1.0
mime-types: 2.1.35
fraction.js@4.3.7: {}
fs.realpath@1.0.0: {}
@@ -5163,24 +5034,6 @@ snapshots:
get-east-asian-width@1.3.0: {}
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
es-define-property: 1.0.1
es-errors: 1.3.0
es-object-atoms: 1.1.1
function-bind: 1.1.2
get-proto: 1.0.1
gopd: 1.2.0
has-symbols: 1.1.0
hasown: 2.0.2
math-intrinsics: 1.1.0
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
es-object-atoms: 1.1.1
get-stream@8.0.1: {}
get-stream@9.0.1:
@@ -5252,20 +5105,12 @@ snapshots:
slash: 5.1.0
unicorn-magic: 0.1.0
gopd@1.2.0: {}
graceful-fs@4.2.10: {}
graceful-fs@4.2.11: {}
hachure-fill@0.5.2: {}
has-symbols@1.1.0: {}
has-tostringtag@1.0.2:
dependencies:
has-symbols: 1.1.0
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
@@ -5625,8 +5470,6 @@ snapshots:
marked@13.0.3: {}
math-intrinsics@1.1.0: {}
mdast-util-find-and-replace@3.0.2:
dependencies:
'@types/mdast': 4.0.4

View File

@@ -1,89 +0,0 @@
import { isTauri } from "@tauri-apps/api/core";
import { fetch as tauriFetchModule } from "@tauri-apps/plugin-http";
import { clientEnv } from "@/utils/env";
// Use a conditional fetch depending on whether it's in a Tauri environment or web
let customFetch: typeof window.fetch | typeof tauriFetchModule = window.fetch;
if (isTauri()) {
customFetch = tauriFetchModule;
}
interface FetchRequestConfig {
url: string;
method?: "GET" | "POST" | "PUT" | "DELETE";
headers?: Record<string, string>;
body?: any;
timeout?: number;
parseAs?: "json" | "text" | "binary";
baseURL?: string;
}
interface FetchResponse<T = any> {
data: T;
status: number;
statusText: string;
headers: Headers;
}
// Timeout function to reject after the specified time (in ms)
const timeoutPromise = (ms: number) => {
return new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error(`Request timed out after ${ms} ms`)), ms)
);
};
// The main fetch function that switches between Tauri and web fetch based on environment
export const tauriFetch = async <T = any>({
url,
method = "GET",
headers = {},
body,
timeout = 30,
parseAs = "json",
baseURL = clientEnv.COCO_SERVER_URL
}: FetchRequestConfig): Promise<FetchResponse<T>> => {
try {
url = baseURL + url;
if (method !== "GET") {
headers["Content-Type"] = "application/json";
}
const fetchPromise = customFetch(url, {
method,
headers,
body: method !== "GET" ? JSON.stringify(body) : undefined,
});
const response = await Promise.race([
fetchPromise,
timeoutPromise(timeout * 1000),
]);
const statusText = response.ok ? "OK" : "Error";
// Handle empty or non-JSON responses gracefully
let data: any = null;
if (response.ok && response.headers.get("Content-Type")?.includes("application/json")) {
const text = await response.text();
data = text ? JSON.parse(text) : null;
} else if (parseAs === "text") {
data = await response.text();
} else if (parseAs === "binary") {
data = await response.arrayBuffer();
}
return {
data,
status: response.status,
statusText,
headers: response.headers,
};
} catch (error) {
console.error("Request failed:", error);
throw error;
}
};

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="80px" height="80px" viewBox="0 0 80 80" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 3</title>
<defs>
<rect id="path-1" x="0" y="0" width="80" height="80"></rect>
</defs>
<g id="不可用提示" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="对话-未登录" transform="translate(-300, -152)">
<g id="编组-3" transform="translate(300, 152)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="矩形"></g>
<g id="编组-7" mask="url(#mask-2)" stroke="#D8D8D8" stroke-linejoin="round" stroke-width="2">
<g transform="translate(40.1174, 42.9894) rotate(18) translate(-40.1174, -42.9894)translate(4.2836, -1.1681)">
<polyline id="路径" stroke-linecap="round" points="59.1875017 40.24271 51.0736701 32.24271 18.1473879 32.674758 11.1875017 39.6712322"></polyline>
<path d="M10.9295011,65.0735178 C15.4119608,73.3475841 24.1708112,78.9666151 34.2420566,78.9666151 C48.8754069,78.9666151 60.7380811,67.1039409 60.7380811,52.4705906 C60.7380811,39.7604963 51.788708,29.1406401 39.8476032,26.5686633" id="路径" stroke-linecap="round" transform="translate(35.8338, 52.7676) rotate(54) translate(-35.8338, -52.7676)"></path>
<path d="M11.7376647,39.1753202 C18.8464577,40.9618544 23.4439561,39.4110588 25.5301598,34.5229334 C28.0332335,38.3361178 31.3504939,40.24271 35.4819412,40.24271 C41.6791121,40.24271 44.3918829,34.8103094 44.6599103,34.24271 C47.794477,39.3790355 52.6370074,41.1912541 59.1875017,39.6793657" id="路径-16"></path>
<path d="M24.1812108,75.5251943 C22.2902174,72.5032493 20.9749339,65.6812238 23.9032268,56.24271 C24.8257299,66.8179552 27.9236731,73.4384846 33.1970563,76.1042981 C41.1071312,80.1030185 45.6140524,73.1021739 46.8031597,62.3735214 C47.4651356,68.7556799 47.2719901,72.9855883 46.2237233,75.0632467" id="路径-17" stroke-linecap="round"></path>
<path d="M30.228274,28.6819562 C30.228274,23.6648076 26.1610714,19.597605 21.1439227,19.597605 C16.1267741,19.597605 12.0595715,23.6648076 12.0595715,28.6819562" id="路径" transform="translate(21.1439, 24.1398) rotate(-21) translate(-21.1439, -24.1398)"></path>
<path d="M25.6860984,28.6819562 C25.6860984,23.6648076 23.6524971,19.597605 21.1439227,19.597605 C18.6353484,19.597605 16.6017471,23.6648076 16.6017471,28.6819562" id="路径" transform="translate(21.1439, 24.1398) rotate(-21) translate(-21.1439, -24.1398)"></path>
<line x1="14.1875017" y1="31.24271" x2="31.1875017" y2="25.24271" id="路径-18" stroke-linecap="round"></line>
<line x1="19.1875017" y1="20.24271" x2="24.1875017" y2="32.24271" id="路径-19" stroke-linecap="round"></line>
<polyline id="路径-20" stroke-linecap="round" transform="translate(53.1152, 14.1827) rotate(-7) translate(-53.1152, -14.1827)" points="42.6151971 27.1827281 51.6756128 1.93876599 63.6151971 1.18272808"></polyline>
<line x1="55.6702216" y1="18.6299671" x2="61.5910272" y2="18.2159441" id="路径-2" stroke-linecap="round"></line>
<line x1="35.7394234" y1="15.7021435" x2="39.2106463" y2="13.8564616" id="路径-2备份" stroke-linecap="round"></line>
<line x1="58.5383652" y1="15.3936027" x2="58.9596123" y2="21.417717" id="路径-3" stroke-linecap="round"></line>
<line x1="36.603056" y1="12.9710524" x2="38.4809422" y2="16.5028428" id="路径-3备份" stroke-linecap="round"></line>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="80px" height="80px" viewBox="0 0 80 80" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 3</title>
<defs>
<rect id="path-1" x="0" y="0" width="80" height="80"></rect>
</defs>
<g id="不可用提示" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="对话-未登录" transform="translate(-300, -152)">
<g id="编组-3" transform="translate(300, 152)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="矩形"></g>
<g id="编组-7" mask="url(#mask-2)" stroke="#333333" stroke-linejoin="round" stroke-width="2">
<g transform="translate(40.1174, 42.9894) rotate(18) translate(-40.1174, -42.9894)translate(4.2836, -1.1681)">
<polyline id="路径" stroke-linecap="round" points="59.1875017 40.24271 51.0736701 32.24271 18.1473879 32.674758 11.1875017 39.6712322"></polyline>
<path d="M10.9295011,65.0735178 C15.4119608,73.3475841 24.1708112,78.9666151 34.2420566,78.9666151 C48.8754069,78.9666151 60.7380811,67.1039409 60.7380811,52.4705906 C60.7380811,39.7604963 51.788708,29.1406401 39.8476032,26.5686633" id="路径" stroke-linecap="round" transform="translate(35.8338, 52.7676) rotate(54) translate(-35.8338, -52.7676)"></path>
<path d="M11.7376647,39.1753202 C18.8464577,40.9618544 23.4439561,39.4110588 25.5301598,34.5229334 C28.0332335,38.3361178 31.3504939,40.24271 35.4819412,40.24271 C41.6791121,40.24271 44.3918829,34.8103094 44.6599103,34.24271 C47.794477,39.3790355 52.6370074,41.1912541 59.1875017,39.6793657" id="路径-16"></path>
<path d="M24.1812108,75.5251943 C22.2902174,72.5032493 20.9749339,65.6812238 23.9032268,56.24271 C24.8257299,66.8179552 27.9236731,73.4384846 33.1970563,76.1042981 C41.1071312,80.1030185 45.6140524,73.1021739 46.8031597,62.3735214 C47.4651356,68.7556799 47.2719901,72.9855883 46.2237233,75.0632467" id="路径-17" stroke-linecap="round"></path>
<path d="M30.228274,28.6819562 C30.228274,23.6648076 26.1610714,19.597605 21.1439227,19.597605 C16.1267741,19.597605 12.0595715,23.6648076 12.0595715,28.6819562" id="路径" transform="translate(21.1439, 24.1398) rotate(-21) translate(-21.1439, -24.1398)"></path>
<path d="M25.6860984,28.6819562 C25.6860984,23.6648076 23.6524971,19.597605 21.1439227,19.597605 C18.6353484,19.597605 16.6017471,23.6648076 16.6017471,28.6819562" id="路径" transform="translate(21.1439, 24.1398) rotate(-21) translate(-21.1439, -24.1398)"></path>
<line x1="14.1875017" y1="31.24271" x2="31.1875017" y2="25.24271" id="路径-18" stroke-linecap="round"></line>
<line x1="19.1875017" y1="20.24271" x2="24.1875017" y2="32.24271" id="路径-19" stroke-linecap="round"></line>
<polyline id="路径-20" stroke-linecap="round" transform="translate(53.1152, 14.1827) rotate(-7) translate(-53.1152, -14.1827)" points="42.6151971 27.1827281 51.6756128 1.93876599 63.6151971 1.18272808"></polyline>
<line x1="55.6702216" y1="18.6299671" x2="61.5910272" y2="18.2159441" id="路径-2" stroke-linecap="round"></line>
<line x1="35.7394234" y1="15.7021435" x2="39.2106463" y2="13.8564616" id="路径-2备份" stroke-linecap="round"></line>
<line x1="58.5383652" y1="15.3936027" x2="58.9596123" y2="21.417717" id="路径-3" stroke-linecap="round"></line>
<line x1="36.603056" y1="12.9710524" x2="38.4809422" y2="16.5028428" id="路径-3备份" stroke-linecap="round"></line>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -24,6 +24,7 @@ import { useSearchStore } from "@/stores/searchStore";
import { IServer } from "@/stores/appStore";
import FileList from "@/components/Search/FileList";
import { Greetings } from "./Greetings";
import ConnectPrompt from "./ConnectPrompt";
interface ChatAIProps {
isTransitioned: boolean;
@@ -85,6 +86,8 @@ const ChatAI = memo(
const [activeChat, setActiveChat] = useState<Chat>();
const [timedoutShow, setTimedoutShow] = useState(false);
const [errorShow, setErrorShow] = useState(false);
const [IsLogin, setIsLogin] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
const curChatEndRef = useRef(curChatEnd);
@@ -106,7 +109,8 @@ const ChatAI = memo(
if (!server?.id) return;
try {
console.log("reconnect", server.id);
await invoke("connect_to_server", { id: server.id });
const res: any = await invoke("connect_to_server", { id: server.id });
console.log(1111, res)
setConnected(true);
} catch (error) {
console.error("Failed to connect:", error);
@@ -462,7 +466,11 @@ const ChatAI = memo(
console.error("Failed to fetch user data:", error);
}
},
[JSON.stringify(activeChat?.messages), isSearchActive, isDeepThinkActive]
[
JSON.stringify(activeChat?.messages),
isSearchActive,
isDeepThinkActive,
]
);
const handleSendMessage = useCallback(
@@ -539,7 +547,10 @@ const ChatAI = memo(
};
}, []);
const chatHistory = async (chat: Chat, callback?: (chat: Chat) => void) => {
const chatHistory = async (
chat: Chat,
callback?: (chat: Chat) => void
) => {
try {
let response: any = await invoke("session_chat_history", {
serverId: currentService?.id,
@@ -554,9 +565,9 @@ const ChatAI = memo(
...chat,
messages: hits,
};
console.log("id_history2", updatedChat)
console.log("id_history2", updatedChat);
setActiveChat(updatedChat);
callback && callback(updatedChat)
callback && callback(updatedChat);
} catch (error) {
console.error("Failed to fetch user data:", error);
}
@@ -671,85 +682,89 @@ const ChatAI = memo(
activeChat={activeChat}
reconnect={reconnect}
isChatPage={isChatPage}
setIsLogin={setIsLogin}
/>
{/* Chat messages */}
<div className="flex flex-col h-full justify-between overflow-hidden">
<div className="flex-1 w-full overflow-x-hidden overflow-y-auto border-t border-[rgba(0,0,0,0.1)] dark:border-[rgba(255,255,255,0.15)] custom-scrollbar relative">
<Greetings />
{activeChat?.messages?.map((message, index) => (
<ChatMessage
key={message._id + index}
message={message}
isTyping={false}
onResend={handleSendMessage}
/>
))}
{(query_intent ||
fetch_source ||
pick_source ||
deep_read ||
think ||
response) &&
activeChat?._id ? (
<ChatMessage
key={"current"}
message={{
_id: "current",
_source: {
type: "assistant",
message: "",
question: Question,
},
}}
onResend={handleSendMessage}
isTyping={!curChatEnd}
query_intent={query_intent}
fetch_source={fetch_source}
pick_source={pick_source}
deep_read={deep_read}
think={think}
response={response}
/>
) : null}
{timedoutShow ? (
<ChatMessage
key={"timedout"}
message={{
_id: "timedout",
_source: {
type: "assistant",
message: t("assistant.chat.timedout"),
question: Question,
},
}}
onResend={handleSendMessage}
isTyping={false}
/>
) : null}
{errorShow ? (
<ChatMessage
key={"error"}
message={{
_id: "error",
_source: {
type: "assistant",
message: t("assistant.chat.error"),
question: Question,
},
}}
onResend={handleSendMessage}
isTyping={false}
/>
) : null}
<div ref={messagesEndRef} />
</div>
{uploadFiles.length > 0 && (
<div className="max-h-[120px] overflow-auto p-2">
<FileList />
{IsLogin ? (
<div className="flex flex-col h-full justify-between overflow-hidden">
<div className="flex-1 w-full overflow-x-hidden overflow-y-auto border-t border-[rgba(0,0,0,0.1)] dark:border-[rgba(255,255,255,0.15)] custom-scrollbar relative">
<Greetings />
{activeChat?.messages?.map((message, index) => (
<ChatMessage
key={message._id + index}
message={message}
isTyping={false}
onResend={handleSendMessage}
/>
))}
{(query_intent ||
fetch_source ||
pick_source ||
deep_read ||
think ||
response) &&
activeChat?._id ? (
<ChatMessage
key={"current"}
message={{
_id: "current",
_source: {
type: "assistant",
message: "",
question: Question,
},
}}
onResend={handleSendMessage}
isTyping={!curChatEnd}
query_intent={query_intent}
fetch_source={fetch_source}
pick_source={pick_source}
deep_read={deep_read}
think={think}
response={response}
/>
) : null}
{timedoutShow ? (
<ChatMessage
key={"timedout"}
message={{
_id: "timedout",
_source: {
type: "assistant",
message: t("assistant.chat.timedout"),
question: Question,
},
}}
onResend={handleSendMessage}
isTyping={false}
/>
) : null}
{errorShow ? (
<ChatMessage
key={"error"}
message={{
_id: "error",
_source: {
type: "assistant",
message: t("assistant.chat.error"),
question: Question,
},
}}
onResend={handleSendMessage}
isTyping={false}
/>
) : null}
<div ref={messagesEndRef} />
</div>
)}
</div>
{uploadFiles.length > 0 && (
<div className="max-h-[120px] overflow-auto p-2">
<FileList />
</div>
)}
</div>
) : (
<ConnectPrompt />
)}
</div>
);
}

View File

@@ -39,6 +39,7 @@ interface ChatHeaderProps {
isSidebarOpen: boolean;
activeChat: Chat | undefined;
reconnect: (server?: IServer) => void;
setIsLogin: (isLogin: boolean) => void;
isChatPage?: boolean;
}
@@ -48,6 +49,7 @@ export function ChatHeader({
setIsSidebarOpen,
activeChat,
reconnect,
setIsLogin,
isChatPage = false,
}: ChatHeaderProps) {
const { t } = useTranslation();
@@ -70,7 +72,7 @@ export function ChatHeader({
const enabledServers = (res as IServer[]).filter(
(server) => server.enabled !== false
);
// console.log("list_coco_servers", enabledServers);
console.log("list_coco_servers", enabledServers);
setServerList(enabledServers);
if (resetSelection && enabledServers.length > 0) {
@@ -103,7 +105,8 @@ export function ChatHeader({
if (!connected) return;
try {
console.log("disconnect");
await invoke("disconnect");
const res: any = await invoke("disconnect");
console.log(121212213213123, res)
setConnected(false);
} catch (error) {
console.error("Failed to disconnect:", error);
@@ -119,6 +122,12 @@ export function ChatHeader({
setMessages(""); // Clear previous messages
onCreateNewChat();
//
if (!server.public && !server.profile) {
setIsLogin(false);
return;
}
setIsLogin(true);
//
if (!(isCurrent && connected)) {
await disconnect();
reconnect && reconnect(server);

View File

@@ -0,0 +1,42 @@
import { ExternalLink } from "lucide-react";
import { useTranslation } from "react-i18next";
import { emit } from "@tauri-apps/api/event";
import LoginDark from "@/assets/images/login-dark.svg";
import LoginLight from "@/assets/images/login-light.svg";
import { useTheme } from "@/contexts/ThemeContext";
const ConnectPrompt = () => {
const { t } = useTranslation();
const { theme } = useTheme();
const logo = theme === "dark" ? LoginDark : LoginLight;
const handleConnect = async () => {
emit("open_settings", "connect");
};
return (
<div className="flex flex-col items-center justify-center h-full w-full text-sm text-[#333] dark:text-[#d8d8d8]">
<div className="flex flex-col items-center text-center p-6">
<img
src={logo}
className="w-20 h-20 mb-4"
alt={t("assistant.chat.logo_alt")}
/>
<p>{t("assistant.chat.welcome")}</p>
<p className="mb-4 w-[388px]">{t("assistant.chat.connect_tip")}</p>
<button
className="flex items-center gap-2 px-6 py-2 rounded-md text-[#0072ff] transition-colors"
onClick={handleConnect}
>
<span>{t("assistant.chat.connect")}</span>
<ExternalLink size={16} />
</button>
</div>
</div>
);
};
export default ConnectPrompt;

View File

@@ -1,9 +1,8 @@
import { useEffect, useCallback } from "react";
import { listen} from "@tauri-apps/api/event";
import { listen, emit } from "@tauri-apps/api/event";
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
import { isMetaOrCtrlKey } from "@/utils/keyboardUtils";
import { useAppStore } from "@/stores/appStore";
interface CreateWindowOptions {
label?: string;
@@ -17,8 +16,6 @@ interface CreateWindowOptions {
}
export default function useSettingsWindow() {
const setTabIndex = useAppStore((state) => state.setTabIndex);
const openSettingsWindow = useCallback((tab?: string) => {
const url = tab ? `/ui/settings?tab=${tab}` : `/ui/settings`;
const options: CreateWindowOptions = {
@@ -37,6 +34,10 @@ export default function useSettingsWindow() {
resizable: false,
center: true,
url,
data: {
tab,
timestamp: Date.now(),
},
};
// Check if the window already exists
@@ -67,11 +68,11 @@ export default function useSettingsWindow() {
);
useEffect(() => {
const unlisten = listen("open_settings", (event) => {
const unlisten = listen("open_settings", async (event) => {
console.log("open_settings event received:", event);
const tab = event.payload as string | "";
setTabIndex(tab)
emit("tab_index", tab);
openSettingsWindow(tab);
});
window.addEventListener("keydown", handleKeyDown);

View File

@@ -119,7 +119,11 @@
"timedout": "Request timed out. Please try again later.",
"noServers": "No servers found",
"addServer": "Add Server",
"error": "Request error. Please try again later."
"error": "Request error. Please try again later.",
"logo_alt": "Login Logo",
"welcome": "Welcome to Coco AI",
"connect_tip": "To start a conversation, please connect to the service and log in to your account.",
"connect": "Connect"
},
"input": {
"stopMessage": "Stop message",

View File

@@ -119,7 +119,11 @@
"timedout": "请求超时,请稍后再试。",
"noServers": "暂无可用服务器",
"addServer": "添加服务器",
"error": "请求错误,请稍后再试。"
"error": "请求错误,请稍后再试。",
"logo_alt": "登录图标",
"welcome": "欢迎使用 Coco AI",
"connect_tip": "要开始对话,请连接服务并登录您的账户。",
"connect": "连接"
},
"input": {
"stopMessage": "停止生成",

View File

@@ -1,14 +1,22 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react";
import { Settings, Puzzle, Settings2, Info, Server } from "lucide-react";
import { useTranslation } from "react-i18next";
import { listen } from '@tauri-apps/api/event';
import SettingsPanel from "@/components/Settings/SettingsPanel";
import GeneralSettings from "@/components/Settings/GeneralSettings";
import AboutView from "@/components/Settings/AboutView";
import Cloud from "@/components/Cloud/Cloud.tsx";
import Footer from "@/components/Footer";
import { useAppStore } from "@/stores/appStore";
const tabIndexMap: { [key: string]: number } = {
'general': 0,
'extensions': 1,
'connect': 2,
'advanced': 3,
'about': 4
};
function SettingsPage() {
const { t } = useTranslation();
@@ -21,8 +29,21 @@ function SettingsPage() {
{ name: t("settings.tabs.about"), icon: Info },
];
const tabIndex = useAppStore((state) => state.tabIndex);
const [defaultIndex, setDefaultIndex] = useState<number>(tabIndex);
const [defaultIndex, setDefaultIndex] = useState<number>(0);
useEffect(() => {
const unlisten = listen("tab_index", (event) => {
const tabName = event.payload as string;
const index = tabIndexMap[tabName];
if (index !== -1) {
setDefaultIndex(index);
}
});
return () => {
unlisten.then(fn => fn());
};
}, []);
return (
<div>

View File

@@ -14,6 +14,8 @@ export interface IServer {
icon: string;
};
enabled: boolean;
public: boolean;
profile?: any;
available?: boolean;
health?: {
status: string;
@@ -40,8 +42,6 @@ export type IAppStore = {
setEndpoint: (endpoint: AppEndpoint) => void,
language: string;
setLanguage: (language: string) => void;
tabIndex: number;
setTabIndex: (tabName: string) => void;
isPinned: boolean,
setIsPinned: (isPinned: boolean) => void,
initializeListeners: () => void;
@@ -84,17 +84,6 @@ export const useAppStore = create<IAppStore>()(
},
language: "en",
setLanguage: (language: string) => set({ language }),
tabIndex: 0,
setTabIndex: (tabName: string) => {
const tabIndexMap: { [key: string]: number } = {
'general': 0,
'extensions': 1,
'connect': 2,
'advanced': 3,
'about': 4
};
set({ tabIndex: tabIndexMap[tabName || "general"] || 0 })
},
isPinned: false,
setIsPinned: (isPinned: boolean) => set({ isPinned }),
initializeListeners: () => {