mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 03:27:43 +01:00
feat: add debug api details show (#102)
* feat: add debug api details show
This commit is contained in:
@@ -60,6 +60,9 @@
|
||||
},
|
||||
{
|
||||
"url": "https://coco.infini.cloud"
|
||||
},
|
||||
{
|
||||
"url": "https://infini.tpddns.cn:27200"
|
||||
}
|
||||
],
|
||||
"deny": []
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
96
src/components/AppAI/ApiDetails.tsx
Normal file
96
src/components/AppAI/ApiDetails.tsx
Normal 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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
15
src/main.css
15
src/main.css
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
38
src/stores/logStore.ts
Normal 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
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -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"
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
Reference in New Issue
Block a user