mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-19 06:45:50 +00:00
style: 完成所有文件的lint
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
export { useModels, type UseModelsResult } from "./useModels";
|
||||
export { useCommands, type UseCommandsResult } from "./useCommands";
|
||||
export { useQRScanner, type QRCodeData, type UseQRScannerOptions, type UseQRScannerResult } from "./useQRScanner";
|
||||
export { useModels, type UseModelsResult } from './useModels'
|
||||
export { useCommands, type UseCommandsResult } from './useCommands'
|
||||
export {
|
||||
useQRScanner,
|
||||
type QRCodeData,
|
||||
type UseQRScannerOptions,
|
||||
type UseQRScannerResult,
|
||||
} from './useQRScanner'
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { getUuid, setUuid } from "../api/client";
|
||||
import { useState, useCallback } from 'react'
|
||||
import { getUuid, setUuid } from '../api/client'
|
||||
|
||||
export function useAuth() {
|
||||
const [uuid] = useState(() => getUuid());
|
||||
const [uuid] = useState(() => getUuid())
|
||||
|
||||
const importUuid = useCallback((newUuid: string) => {
|
||||
setUuid(newUuid);
|
||||
}, []);
|
||||
setUuid(newUuid)
|
||||
}, [])
|
||||
|
||||
return { uuid, importUuid };
|
||||
return { uuid, importUuid }
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import type { ACPClient } from "../acp/client";
|
||||
import type { AvailableCommand } from "../acp/types";
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import type { ACPClient } from '../acp/client'
|
||||
import type { AvailableCommand } from '../acp/types'
|
||||
|
||||
export interface UseCommandsResult {
|
||||
/** List of available slash commands from the agent */
|
||||
commands: AvailableCommand[];
|
||||
commands: AvailableCommand[]
|
||||
/** Whether any commands are available */
|
||||
hasCommands: boolean;
|
||||
hasCommands: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -16,24 +16,21 @@ export interface UseCommandsResult {
|
||||
export function useCommands(client: ACPClient): UseCommandsResult {
|
||||
const [commands, setCommands] = useState<AvailableCommand[]>(
|
||||
client.availableCommands,
|
||||
);
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const handleCommandsChanged = (newCommands: AvailableCommand[]) => {
|
||||
setCommands(newCommands);
|
||||
};
|
||||
setCommands(newCommands)
|
||||
}
|
||||
|
||||
client.setAvailableCommandsChangedHandler(handleCommandsChanged);
|
||||
client.setAvailableCommandsChangedHandler(handleCommandsChanged)
|
||||
|
||||
return () => {
|
||||
client.setAvailableCommandsChangedHandler(() => {});
|
||||
};
|
||||
}, [client]);
|
||||
client.setAvailableCommandsChangedHandler(() => {})
|
||||
}
|
||||
}, [client])
|
||||
|
||||
const hasCommands = useMemo(
|
||||
() => commands.length > 0,
|
||||
[commands],
|
||||
);
|
||||
const hasCommands = useMemo(() => commands.length > 0, [commands])
|
||||
|
||||
return { commands, hasCommands };
|
||||
return { commands, hasCommands }
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { useState, useEffect, useMemo, useCallback } from "react";
|
||||
import type { ACPClient } from "../acp/client";
|
||||
import type { ModelInfo, SessionModelState } from "../acp/types";
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react'
|
||||
import type { ACPClient } from '../acp/client'
|
||||
import type { ModelInfo, SessionModelState } from '../acp/types'
|
||||
|
||||
export interface UseModelsResult {
|
||||
/** Whether model selection is supported by the current agent */
|
||||
supportsModelSelection: boolean;
|
||||
supportsModelSelection: boolean
|
||||
/** List of available models */
|
||||
availableModels: ModelInfo[];
|
||||
availableModels: ModelInfo[]
|
||||
/** The currently selected model ID */
|
||||
currentModelId: string | null;
|
||||
currentModelId: string | null
|
||||
/** The currently selected model info */
|
||||
currentModel: ModelInfo | null;
|
||||
currentModel: ModelInfo | null
|
||||
/** Set the model for the current session */
|
||||
setModel: (modelId: string) => Promise<void>;
|
||||
setModel: (modelId: string) => Promise<void>
|
||||
/** Whether a model change is in progress */
|
||||
isLoading: boolean;
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,78 +27,81 @@ export interface UseModelsResult {
|
||||
*/
|
||||
export function useModels(client: ACPClient): UseModelsResult {
|
||||
const [modelState, setModelState] = useState<SessionModelState | null>(
|
||||
client.modelState
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
client.modelState,
|
||||
)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
// Subscribe to model state changes (session created/destroyed)
|
||||
// This replaces the previous 500ms polling approach
|
||||
useEffect(() => {
|
||||
// Handler for when model state changes (session created or disconnected)
|
||||
const handleModelStateChanged = (state: SessionModelState | null) => {
|
||||
setModelState(state);
|
||||
setModelState(state)
|
||||
// Auto-restore previously selected model when a new session is created
|
||||
if (state && state.availableModels.length > 0) {
|
||||
const saved = localStorage.getItem("acp_model_id");
|
||||
if (saved && saved !== state.currentModelId && state.availableModels.some((m) => m.modelId === saved)) {
|
||||
client.setSessionModel(saved).catch(() => {});
|
||||
const saved = localStorage.getItem('acp_model_id')
|
||||
if (
|
||||
saved &&
|
||||
saved !== state.currentModelId &&
|
||||
state.availableModels.some(m => m.modelId === saved)
|
||||
) {
|
||||
client.setSessionModel(saved).catch(() => {})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Handler for when current model changes within a session
|
||||
const handleModelChanged = (modelId: string) => {
|
||||
setModelState((prev) => {
|
||||
if (!prev) return null;
|
||||
setModelState(prev => {
|
||||
if (!prev) return null
|
||||
return {
|
||||
...prev,
|
||||
currentModelId: modelId,
|
||||
};
|
||||
});
|
||||
setIsLoading(false);
|
||||
};
|
||||
}
|
||||
})
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
// Register handlers - setModelStateChangedHandler immediately calls with current state
|
||||
client.setModelStateChangedHandler(handleModelStateChanged);
|
||||
client.setModelChangedHandler(handleModelChanged);
|
||||
client.setModelStateChangedHandler(handleModelStateChanged)
|
||||
client.setModelChangedHandler(handleModelChanged)
|
||||
|
||||
return () => {
|
||||
// Clear handlers on unmount
|
||||
client.setModelStateChangedHandler(() => {});
|
||||
client.setModelChangedHandler(() => {});
|
||||
};
|
||||
}, [client]);
|
||||
client.setModelStateChangedHandler(() => {})
|
||||
client.setModelChangedHandler(() => {})
|
||||
}
|
||||
}, [client])
|
||||
|
||||
const availableModels = useMemo(
|
||||
() => modelState?.availableModels ?? [],
|
||||
[modelState]
|
||||
);
|
||||
[modelState],
|
||||
)
|
||||
|
||||
const currentModelId = modelState?.currentModelId ?? null;
|
||||
const currentModelId = modelState?.currentModelId ?? null
|
||||
|
||||
const currentModel = useMemo(
|
||||
() =>
|
||||
availableModels.find((m) => m.modelId === currentModelId) ?? null,
|
||||
[availableModels, currentModelId]
|
||||
);
|
||||
() => availableModels.find(m => m.modelId === currentModelId) ?? null,
|
||||
[availableModels, currentModelId],
|
||||
)
|
||||
|
||||
const setModel = useCallback(
|
||||
async (modelId: string) => {
|
||||
if (!modelState) {
|
||||
throw new Error("Model selection not supported");
|
||||
throw new Error('Model selection not supported')
|
||||
}
|
||||
setIsLoading(true);
|
||||
setIsLoading(true)
|
||||
try {
|
||||
await client.setSessionModel(modelId);
|
||||
localStorage.setItem("acp_model_id", modelId);
|
||||
await client.setSessionModel(modelId)
|
||||
localStorage.setItem('acp_model_id', modelId)
|
||||
// The model_changed event will update the state
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
throw error;
|
||||
setIsLoading(false)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
[client, modelState]
|
||||
);
|
||||
[client, modelState],
|
||||
)
|
||||
|
||||
return {
|
||||
supportsModelSelection: modelState !== null && availableModels.length > 0,
|
||||
@@ -107,5 +110,5 @@ export function useModels(client: ACPClient): UseModelsResult {
|
||||
currentModel,
|
||||
setModel,
|
||||
isLoading,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import QrScanner from "qr-scanner";
|
||||
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import QrScanner from 'qr-scanner'
|
||||
|
||||
/** QR code data format for scanning */
|
||||
export interface QRCodeData {
|
||||
url: string;
|
||||
token: string;
|
||||
url: string
|
||||
token: string
|
||||
}
|
||||
|
||||
export interface UseQRScannerOptions {
|
||||
/** Called when a valid QR code is scanned */
|
||||
onScan: (data: QRCodeData) => void;
|
||||
onScan: (data: QRCodeData) => void
|
||||
/** Called when an error occurs */
|
||||
onError?: (error: string) => void;
|
||||
onError?: (error: string) => void
|
||||
}
|
||||
|
||||
export interface UseQRScannerResult {
|
||||
/** Whether the scanner is currently active */
|
||||
isScanning: boolean;
|
||||
isScanning: boolean
|
||||
/** Ref to attach to the video element */
|
||||
videoRef: React.RefObject<HTMLVideoElement | null>;
|
||||
videoRef: React.RefObject<HTMLVideoElement | null>
|
||||
/** Start scanning */
|
||||
startScanning: () => void;
|
||||
startScanning: () => void
|
||||
/** Stop scanning */
|
||||
stopScanning: () => void;
|
||||
stopScanning: () => void
|
||||
/** Scan QR code from a file (e.g., from photo album) */
|
||||
scanFromFile: (file: File) => Promise<void>;
|
||||
scanFromFile: (file: File) => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,74 +35,74 @@ export function useQRScanner({
|
||||
onScan,
|
||||
onError,
|
||||
}: UseQRScannerOptions): UseQRScannerResult {
|
||||
const [isScanning, setIsScanning] = useState(false);
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||
const qrScannerRef = useRef<QrScanner | null>(null);
|
||||
const [isScanning, setIsScanning] = useState(false)
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null)
|
||||
const qrScannerRef = useRef<QrScanner | null>(null)
|
||||
|
||||
// Store callbacks in refs to avoid re-creating scanner when callbacks change
|
||||
// This allows callers to pass inline functions without causing re-renders
|
||||
const onScanRef = useRef(onScan);
|
||||
const onErrorRef = useRef(onError);
|
||||
const onScanRef = useRef(onScan)
|
||||
const onErrorRef = useRef(onError)
|
||||
|
||||
// Keep refs up to date
|
||||
useEffect(() => {
|
||||
onScanRef.current = onScan;
|
||||
onErrorRef.current = onError;
|
||||
}, [onScan, onError]);
|
||||
onScanRef.current = onScan
|
||||
onErrorRef.current = onError
|
||||
}, [onScan, onError])
|
||||
|
||||
const startScanning = useCallback(() => {
|
||||
setIsScanning(true);
|
||||
}, []);
|
||||
setIsScanning(true)
|
||||
}, [])
|
||||
|
||||
const stopScanning = useCallback(() => {
|
||||
if (qrScannerRef.current) {
|
||||
qrScannerRef.current.stop();
|
||||
qrScannerRef.current.destroy();
|
||||
qrScannerRef.current = null;
|
||||
qrScannerRef.current.stop()
|
||||
qrScannerRef.current.destroy()
|
||||
qrScannerRef.current = null
|
||||
}
|
||||
setIsScanning(false);
|
||||
}, []);
|
||||
setIsScanning(false)
|
||||
}, [])
|
||||
|
||||
// Scan QR code from a file (photo album)
|
||||
const scanFromFile = useCallback(async (file: File) => {
|
||||
try {
|
||||
const result = await QrScanner.scanImage(file, {
|
||||
returnDetailedScanResult: true,
|
||||
});
|
||||
})
|
||||
|
||||
const data = JSON.parse(result.data) as QRCodeData;
|
||||
const data = JSON.parse(result.data) as QRCodeData
|
||||
if (data.url && data.token) {
|
||||
onScanRef.current(data);
|
||||
onScanRef.current(data)
|
||||
} else {
|
||||
onErrorRef.current?.("Invalid QR code: missing url or token");
|
||||
onErrorRef.current?.('Invalid QR code: missing url or token')
|
||||
}
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : "No QR code found";
|
||||
onErrorRef.current?.(message);
|
||||
const message = e instanceof Error ? e.message : 'No QR code found'
|
||||
onErrorRef.current?.(message)
|
||||
}
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
// Initialize scanner when isScanning becomes true
|
||||
useEffect(() => {
|
||||
if (!isScanning || !videoRef.current) return;
|
||||
if (!isScanning || !videoRef.current) return
|
||||
|
||||
let isCancelled = false;
|
||||
let scanner: QrScanner | null = null;
|
||||
let isCancelled = false
|
||||
let scanner: QrScanner | null = null
|
||||
|
||||
const initScanner = async () => {
|
||||
try {
|
||||
const newScanner = new QrScanner(
|
||||
videoRef.current!,
|
||||
(result) => {
|
||||
result => {
|
||||
try {
|
||||
const data = JSON.parse(result.data) as QRCodeData;
|
||||
const data = JSON.parse(result.data) as QRCodeData
|
||||
if (data.url && data.token) {
|
||||
// Stop scanning and notify
|
||||
newScanner.stop();
|
||||
newScanner.destroy();
|
||||
qrScannerRef.current = null;
|
||||
setIsScanning(false);
|
||||
onScanRef.current(data);
|
||||
newScanner.stop()
|
||||
newScanner.destroy()
|
||||
qrScannerRef.current = null
|
||||
setIsScanning(false)
|
||||
onScanRef.current(data)
|
||||
}
|
||||
} catch {
|
||||
// Not valid JSON, ignore
|
||||
@@ -112,46 +112,46 @@ export function useQRScanner({
|
||||
returnDetailedScanResult: true,
|
||||
highlightScanRegion: true,
|
||||
highlightCodeOutline: true,
|
||||
}
|
||||
);
|
||||
},
|
||||
)
|
||||
|
||||
if (isCancelled) {
|
||||
newScanner.destroy();
|
||||
return;
|
||||
newScanner.destroy()
|
||||
return
|
||||
}
|
||||
|
||||
scanner = newScanner;
|
||||
qrScannerRef.current = newScanner;
|
||||
await newScanner.start();
|
||||
scanner = newScanner
|
||||
qrScannerRef.current = newScanner
|
||||
await newScanner.start()
|
||||
|
||||
if (isCancelled) {
|
||||
newScanner.stop();
|
||||
newScanner.destroy();
|
||||
qrScannerRef.current = null;
|
||||
newScanner.stop()
|
||||
newScanner.destroy()
|
||||
qrScannerRef.current = null
|
||||
}
|
||||
} catch (e) {
|
||||
if (!isCancelled) {
|
||||
onErrorRef.current?.(`Camera error: ${(e as Error).message}`);
|
||||
setIsScanning(false);
|
||||
onErrorRef.current?.(`Camera error: ${(e as Error).message}`)
|
||||
setIsScanning(false)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
initScanner();
|
||||
initScanner()
|
||||
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
isCancelled = true
|
||||
if (scanner) {
|
||||
scanner.stop();
|
||||
scanner.destroy();
|
||||
scanner.stop()
|
||||
scanner.destroy()
|
||||
}
|
||||
if (qrScannerRef.current) {
|
||||
qrScannerRef.current.stop();
|
||||
qrScannerRef.current.destroy();
|
||||
qrScannerRef.current = null;
|
||||
qrScannerRef.current.stop()
|
||||
qrScannerRef.current.destroy()
|
||||
qrScannerRef.current = null
|
||||
}
|
||||
};
|
||||
}, [isScanning]); // Only depend on isScanning, callbacks are accessed via refs
|
||||
}
|
||||
}, [isScanning]) // Only depend on isScanning, callbacks are accessed via refs
|
||||
|
||||
return {
|
||||
isScanning,
|
||||
@@ -159,5 +159,5 @@ export function useQRScanner({
|
||||
startScanning,
|
||||
stopScanning,
|
||||
scanFromFile,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { useEffect, useRef, useCallback } from "react";
|
||||
import { connectSSE, disconnectSSE } from "../api/sse";
|
||||
import type { SessionEvent } from "../types";
|
||||
import { useEffect, useRef, useCallback } from 'react'
|
||||
import { connectSSE, disconnectSSE } from '../api/sse'
|
||||
import type { SessionEvent } from '../types'
|
||||
|
||||
export function useSSE(
|
||||
sessionId: string | null,
|
||||
onEvent: (event: SessionEvent) => void,
|
||||
) {
|
||||
const onEventRef = useRef(onEvent);
|
||||
onEventRef.current = onEvent;
|
||||
const onEventRef = useRef(onEvent)
|
||||
onEventRef.current = onEvent
|
||||
|
||||
const stableCallback = useCallback((event: SessionEvent) => {
|
||||
onEventRef.current(event);
|
||||
}, []);
|
||||
onEventRef.current(event)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!sessionId) return;
|
||||
if (!sessionId) return
|
||||
|
||||
connectSSE(sessionId, stableCallback);
|
||||
connectSSE(sessionId, stableCallback)
|
||||
|
||||
return () => {
|
||||
disconnectSSE();
|
||||
};
|
||||
}, [sessionId, stableCallback]);
|
||||
disconnectSSE()
|
||||
}
|
||||
}, [sessionId, stableCallback])
|
||||
}
|
||||
|
||||
@@ -1,110 +1,126 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { useState, useCallback } from 'react'
|
||||
|
||||
export interface TokenEntry {
|
||||
id: string;
|
||||
token: string;
|
||||
label: string;
|
||||
id: string
|
||||
token: string
|
||||
label: string
|
||||
}
|
||||
|
||||
const TOKENS_KEY = "rcs_tokens";
|
||||
const ACTIVE_TOKEN_KEY = "rcs_uuid";
|
||||
const DEFAULT_ID = "__default__";
|
||||
const TOKENS_KEY = 'rcs_tokens'
|
||||
const ACTIVE_TOKEN_KEY = 'rcs_uuid'
|
||||
const DEFAULT_ID = '__default__'
|
||||
|
||||
function generateId(): string {
|
||||
return `tk_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
||||
return `tk_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`
|
||||
}
|
||||
|
||||
/** Ensure the existing rcs_uuid is present as the default token entry */
|
||||
function ensureDefault(tokens: TokenEntry[]): TokenEntry[] {
|
||||
if (tokens.some((t) => t.id === DEFAULT_ID)) return tokens;
|
||||
let uuid: string | null = null;
|
||||
if (tokens.some(t => t.id === DEFAULT_ID)) return tokens
|
||||
let uuid: string | null = null
|
||||
try {
|
||||
uuid = localStorage.getItem("rcs_uuid");
|
||||
uuid = localStorage.getItem('rcs_uuid')
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
if (!uuid) return tokens;
|
||||
return [{ id: DEFAULT_ID, token: uuid, label: "Default" }, ...tokens];
|
||||
if (!uuid) return tokens
|
||||
return [{ id: DEFAULT_ID, token: uuid, label: 'Default' }, ...tokens]
|
||||
}
|
||||
|
||||
function loadTokens(): TokenEntry[] {
|
||||
let tokens: TokenEntry[] = [];
|
||||
let tokens: TokenEntry[] = []
|
||||
try {
|
||||
const raw = localStorage.getItem(TOKENS_KEY);
|
||||
const raw = localStorage.getItem(TOKENS_KEY)
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (Array.isArray(parsed)) tokens = parsed;
|
||||
const parsed = JSON.parse(raw)
|
||||
if (Array.isArray(parsed)) tokens = parsed
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return ensureDefault(tokens);
|
||||
return ensureDefault(tokens)
|
||||
}
|
||||
|
||||
function loadActiveTokenId(tokens: TokenEntry[]): string {
|
||||
// Try saved active token
|
||||
try {
|
||||
const saved = localStorage.getItem(ACTIVE_TOKEN_KEY);
|
||||
if (saved && tokens.some((t) => t.id === saved)) return saved;
|
||||
const saved = localStorage.getItem(ACTIVE_TOKEN_KEY)
|
||||
if (saved && tokens.some(t => t.id === saved)) return saved
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
// Fall back to default (rcs_uuid) entry
|
||||
const defaultEntry = tokens.find((t) => t.id === DEFAULT_ID);
|
||||
if (defaultEntry) return defaultEntry.id;
|
||||
const defaultEntry = tokens.find(t => t.id === DEFAULT_ID)
|
||||
if (defaultEntry) return defaultEntry.id
|
||||
// Fall back to first entry
|
||||
return tokens[0]?.id ?? DEFAULT_ID;
|
||||
return tokens[0]?.id ?? DEFAULT_ID
|
||||
}
|
||||
|
||||
export function useTokens() {
|
||||
const [tokens, setTokens] = useState<TokenEntry[]>(loadTokens);
|
||||
const [activeTokenId, setActiveTokenIdState] = useState<string>(() => loadActiveTokenId(loadTokens()));
|
||||
const [tokens, setTokens] = useState<TokenEntry[]>(loadTokens)
|
||||
const [activeTokenId, setActiveTokenIdState] = useState<string>(() =>
|
||||
loadActiveTokenId(loadTokens()),
|
||||
)
|
||||
|
||||
const persistTokens = useCallback((next: TokenEntry[]) => {
|
||||
setTokens(next);
|
||||
setTokens(next)
|
||||
try {
|
||||
localStorage.setItem(TOKENS_KEY, JSON.stringify(next));
|
||||
localStorage.setItem(TOKENS_KEY, JSON.stringify(next))
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
const setActiveTokenId = useCallback((id: string) => {
|
||||
setActiveTokenIdState(id);
|
||||
setActiveTokenIdState(id)
|
||||
try {
|
||||
localStorage.setItem(ACTIVE_TOKEN_KEY, id);
|
||||
location.reload(); // Reload to ensure api client picks up new token from localStorage
|
||||
localStorage.setItem(ACTIVE_TOKEN_KEY, id)
|
||||
location.reload() // Reload to ensure api client picks up new token from localStorage
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
const addToken = useCallback((token: string, label: string): string | null => {
|
||||
const trimmed = token.trim();
|
||||
if (!trimmed) return "Token is required";
|
||||
const entry: TokenEntry = { id: generateId(), token: trimmed, label: label.trim() || trimmed.slice(0, 8) };
|
||||
const next = [...tokens, entry];
|
||||
persistTokens(next);
|
||||
return null;
|
||||
}, [tokens, persistTokens]);
|
||||
const addToken = useCallback(
|
||||
(token: string, label: string): string | null => {
|
||||
const trimmed = token.trim()
|
||||
if (!trimmed) return 'Token is required'
|
||||
const entry: TokenEntry = {
|
||||
id: generateId(),
|
||||
token: trimmed,
|
||||
label: label.trim() || trimmed.slice(0, 8),
|
||||
}
|
||||
const next = [...tokens, entry]
|
||||
persistTokens(next)
|
||||
return null
|
||||
},
|
||||
[tokens, persistTokens],
|
||||
)
|
||||
|
||||
const removeToken = useCallback((id: string) => {
|
||||
if (id === DEFAULT_ID) return; // Cannot remove default
|
||||
const next = tokens.filter((t) => t.id !== id);
|
||||
persistTokens(next);
|
||||
if (activeTokenId === id) {
|
||||
setActiveTokenId(DEFAULT_ID);
|
||||
}
|
||||
}, [tokens, persistTokens, activeTokenId, setActiveTokenId]);
|
||||
const removeToken = useCallback(
|
||||
(id: string) => {
|
||||
if (id === DEFAULT_ID) return // Cannot remove default
|
||||
const next = tokens.filter(t => t.id !== id)
|
||||
persistTokens(next)
|
||||
if (activeTokenId === id) {
|
||||
setActiveTokenId(DEFAULT_ID)
|
||||
}
|
||||
},
|
||||
[tokens, persistTokens, activeTokenId, setActiveTokenId],
|
||||
)
|
||||
|
||||
const updateToken = useCallback((id: string, label: string) => {
|
||||
const next = tokens.map((t) => t.id === id ? { ...t, label } : t);
|
||||
persistTokens(next);
|
||||
}, [tokens, persistTokens]);
|
||||
const updateToken = useCallback(
|
||||
(id: string, label: string) => {
|
||||
const next = tokens.map(t => (t.id === id ? { ...t, label } : t))
|
||||
persistTokens(next)
|
||||
},
|
||||
[tokens, persistTokens],
|
||||
)
|
||||
|
||||
const activeToken = tokens.find((t) => t.id === activeTokenId) ?? tokens[0] ?? null;
|
||||
const activeLabel = activeToken?.label ?? "Default";
|
||||
const activeTokenValue = activeToken?.token ?? null;
|
||||
const activeToken =
|
||||
tokens.find(t => t.id === activeTokenId) ?? tokens[0] ?? null
|
||||
const activeLabel = activeToken?.label ?? 'Default'
|
||||
const activeTokenValue = activeToken?.token ?? null
|
||||
|
||||
return {
|
||||
tokens,
|
||||
@@ -116,5 +132,5 @@ export function useTokens() {
|
||||
addToken,
|
||||
removeToken,
|
||||
updateToken,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user