feat: add debug api details show (#102)

* feat: add debug api details show
This commit is contained in:
BiggerRain
2025-01-16 16:57:42 +08:00
committed by GitHub
parent 711f7cf57c
commit d47f28878f
11 changed files with 210 additions and 21 deletions

View File

@@ -60,6 +60,9 @@
},
{
"url": "https://coco.infini.cloud"
},
{
"url": "https://infini.tpddns.cn:27200"
}
],
"deny": []

View File

@@ -1,6 +1,7 @@
import { fetch } from "@tauri-apps/plugin-http";
import { clientEnv } from "@/utils/env";
import { useLogStore } from "@/stores/logStore";
interface FetchRequestConfig {
url: string;
@@ -41,14 +42,27 @@ export const tauriFetch = async <T = any>({
const { state: { auth } } = JSON.parse(localStorage.getItem("auth-store") || "")
console.log("auth", auth)
const addLog = useLogStore.getState().addLog;
try {
url = baseURL + url;
if (method !== "GET") {
headers["Content-Type"] = "application/json";
}
headers["X-API-TOKEN"] = auth?.token || "";
// debug API
const requestInfo = {
url,
method,
headers,
body,
timeout,
parseAs,
};
const fetchPromise = fetch(url, {
method,
headers,
@@ -71,6 +85,13 @@ export const tauriFetch = async <T = any>({
data = await response.arrayBuffer();
}
// debug API
const log = {
request: requestInfo,
response: response,
};
addLog(log);
return {
data,
status: response.status,
@@ -79,6 +100,21 @@ export const tauriFetch = async <T = any>({
};
} catch (error) {
console.error("Request failed:", error);
// debug API
const log = {
request: {
url,
method,
headers,
body,
timeout,
parseAs,
},
error,
};
addLog(log);
throw error;
}
};

View File

@@ -0,0 +1,96 @@
import React, { useState, useEffect, useCallback } from "react";
import { X } from "lucide-react";
import { useLogStore } from "@/stores/logStore";
const ApiDetails: React.FC = () => {
const logs = useLogStore((state) => state.logs);
const [showAPIDetails, setShowAPIDetails] = useState(false);
const toggleAPIDetails = useCallback(() => {
setShowAPIDetails((prev) => !prev);
}, []);
const handleKeyDown = useCallback(
(event: KeyboardEvent) => {
if (event.metaKey && event.key === "d") {
const handleKeyG = (e: KeyboardEvent) => {
if (e.metaKey && e.key === "g") {
console.log("Command + D + G Detected!");
toggleAPIDetails();
}
};
window.addEventListener("keydown", handleKeyG, { once: true });
}
},
[toggleAPIDetails]
);
useEffect(() => {
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [handleKeyDown]);
return (
<div
className={`fixed bottom-0 z-[2000] overflow-y-auto overflow-x-hidden transition-all duration-300 ease-in-out ${
showAPIDetails ? "h-[60vh] w-full bg-white shadow-lg rounded-t-lg" : ""
}`}
>
{showAPIDetails && (
<div className="p-6">
<h3 className="text-lg font-bold text-gray-800 border-b pb-2 cursor-pointer flex justify-between" onClick={toggleAPIDetails}>
API Logs (Latest 10)
<X />
</h3>
<div className="space-y-4 mt-4 ">
{logs.map((log, index) => (
<div
key={index}
className="p-4 border rounded-md shadow-sm bg-gray-50"
>
<h4 className="font-semibold text-gray-800">
Request {index + 1}:
</h4>
<div className="text-sm text-gray-700 mt-1">
<pre className="bg-gray-100 p-2 rounded-md whitespace-pre-wrap">
{JSON.stringify(log.request, null, 2)}
</pre>
</div>
{log.response && (
<>
<h4 className="font-semibold text-green-800 mt-4">
Response:
</h4>
<div className="text-sm text-gray-700 mt-1">
<pre className="bg-green-100 p-2 rounded-md text-green-700 whitespace-pre-wrap">
{JSON.stringify(log.response, null, 2)}
</pre>
</div>
</>
)}
{log.error && (
<>
<h4 className="font-semibold text-red-800 mt-4">Error:</h4>
<div className="text-sm text-gray-700 mt-1">
<pre className="bg-red-100 p-2 rounded-md text-red-700 whitespace-pre-wrap">
{JSON.stringify(log.error, null, 2)}
</pre>
</div>
</>
)}
</div>
))}
</div>
</div>
)}
</div>
);
};
export default ApiDetails;

View File

@@ -70,6 +70,7 @@ function Search({ isChatMode, input }: SearchProps) {
method: "GET",
baseURL: appStore.endpoint_http,
});
console.log("_suggest", input, response);
const data = response.data?.hits?.hits || [];
setSuggests(data);

View File

@@ -6,9 +6,10 @@ import { useAppStore } from "@/stores/appStore";
import { AppEndpoint } from "@/utils/tauri";
const ENDPOINTS = [
{ value: "coco.infini.cloud", label: "coco.infini.cloud" },
{ value: "localhost:2900", label: "localhost:2900" },
{ value: "localhost:9000", label: "localhost:9000" },
{ value: "https://coco.infini.cloud", label: "https://coco.infini.cloud" },
{ value: "http://localhost:2900", label: "http://localhost:2900" },
{ value: "http://localhost:9000", label: "http://localhost:9000" },
{ value: "https://infini.tpddns.cn:27200", label: "https://infini.tpddns.cn:27200" },
];
export default function AdvancedSettings() {

View File

@@ -24,12 +24,6 @@
body,
#root {
@apply text-gray-900 antialiased;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.dark body,
@@ -147,4 +141,13 @@
.error-content button:hover {
background-color: #f79c42;
}
.user-select{
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
}

View File

@@ -9,6 +9,7 @@ import ChatAI, { ChatAIRef } from "@/components/ChatAI/Chat";
import { useAppStore } from "@/stores/appStore";
import { useAuthStore } from "@/stores/authStore";
import { tauriFetch } from "@/api/tauriFetchClient";
import ApiDetails from "@/components/AppAI/ApiDetails";
export default function DesktopApp() {
const initializeListeners = useAppStore((state) => state.initializeListeners);
@@ -86,7 +87,7 @@ export default function DesktopApp() {
isTransitioned
? "top-[500px] h-[90px] border-t"
: "top-0 h-[90px] border-b"
} border-[#E6E6E6] dark:border-[#272626] `}
} border-[#E6E6E6] dark:border-[#272626]`}
>
<InputBox
isChatMode={isChatMode}
@@ -106,7 +107,7 @@ export default function DesktopApp() {
data-tauri-drag-region
className={`absolute w-full transition-opacity duration-500 ${
isTransitioned ? "opacity-0 pointer-events-none" : "opacity-100"
} bottom-0 h-[500px]`}
} bottom-0 h-[500px] user-select`}
>
<Search
key="Search"
@@ -134,6 +135,8 @@ export default function DesktopApp() {
/>
) : null}
</div>
<ApiDetails />
</div>
);
}

View File

@@ -27,16 +27,21 @@ export const useAppStore = create<IAppStore>()(
setShowTooltip: (showTooltip: boolean) => set({ showTooltip }),
app_uid: "",
setAppUid: (app_uid: string) => set({ app_uid }),
endpoint: "coco.infini.cloud",
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?.includes('localhost:')
? `http://${endpoint}`
: `https://${endpoint}`;
const endpoint_websocket = endpoint?.includes('localhost:')
? `ws://${endpoint}/ws`
: `wss://${endpoint}/ws`;
const endpoint_http = endpoint;
const withoutProtocol = endpoint.split("//")[1];
const endpoint_websocket = endpoint?.includes('https')
? `wss://${withoutProtocol}/ws`
: `ws://${withoutProtocol}/ws`;
console.log(111111, endpoint,
endpoint_http,
endpoint_websocket)
set({
endpoint,

38
src/stores/logStore.ts Normal file
View File

@@ -0,0 +1,38 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
export interface ApiLog {
request: any;
response?: any;
error?: any;
}
export type ILogStore = {
logs: ApiLog[];
addLog: (log: ApiLog) => void;
initializeListeners: () => void;
};
export const useLogStore = create<ILogStore>()(
persist(
(set) => ({
logs: [],
addLog: (log: ApiLog) =>
set((state) => {
const newLogs = [...state.logs, log];
if (newLogs.length > 10) {
newLogs.shift();
}
return { logs: newLogs };
}),
initializeListeners: () => {
},
}),
{
name: "log-store",
partialize: (state) => ({
logs: state.logs
}),
}
)
);

View File

@@ -2,8 +2,6 @@
// 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");
@@ -211,7 +209,7 @@ export const events = __makeEvents__<{
/** user-defined types **/
export type AppEndpoint = "coco.infini.cloud" | "localhost:2900" | "localhost:9000"
export type AppEndpoint = string
export type AppTheme = "auto" | "light" | "dark"
export type WindowTheme = "light" | "dark"
export type AspectRatio = "wide" | "vertical" | "square" | "classic" | "tall"

View File

@@ -33,6 +33,11 @@ export default {
boxShadow: {
"window-custom": "0px 16px 32px 0px rgba(0,0,0,0.3)",
},
zIndex: {
100: '100',
1000: '1000',
2000: '2000',
},
},
},
plugins: [],