style: 完成所有文件的lint

This commit is contained in:
claude-code-best
2026-05-01 21:39:30 +08:00
parent d136872cc9
commit 6182015005
1333 changed files with 68255 additions and 77882 deletions

View File

@@ -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'

View File

@@ -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 }
}

View File

@@ -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 }
}

View File

@@ -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,
};
}
}

View File

@@ -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,
};
}
}

View File

@@ -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])
}

View File

@@ -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,
};
}
}