mirror of
https://github.com/meta-llama/llama-stack.git
synced 2025-08-15 22:18:00 +00:00
feat(UI): Adding linter and prettier for UI (#3156)
This commit is contained in:
parent
61582f327c
commit
e69acbafbf
73 changed files with 1452 additions and 1226 deletions
|
@ -1,85 +1,85 @@
|
|||
import { useEffect, useRef, useState } from "react"
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { recordAudio } from "@/lib/audio-utils"
|
||||
import { recordAudio } from "@/lib/audio-utils";
|
||||
|
||||
interface UseAudioRecordingOptions {
|
||||
transcribeAudio?: (blob: Blob) => Promise<string>
|
||||
onTranscriptionComplete?: (text: string) => void
|
||||
transcribeAudio?: (blob: Blob) => Promise<string>;
|
||||
onTranscriptionComplete?: (text: string) => void;
|
||||
}
|
||||
|
||||
export function useAudioRecording({
|
||||
transcribeAudio,
|
||||
onTranscriptionComplete,
|
||||
}: UseAudioRecordingOptions) {
|
||||
const [isListening, setIsListening] = useState(false)
|
||||
const [isSpeechSupported, setIsSpeechSupported] = useState(!!transcribeAudio)
|
||||
const [isRecording, setIsRecording] = useState(false)
|
||||
const [isTranscribing, setIsTranscribing] = useState(false)
|
||||
const [audioStream, setAudioStream] = useState<MediaStream | null>(null)
|
||||
const activeRecordingRef = useRef<any>(null)
|
||||
const [isListening, setIsListening] = useState(false);
|
||||
const [isSpeechSupported, setIsSpeechSupported] = useState(!!transcribeAudio);
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [isTranscribing, setIsTranscribing] = useState(false);
|
||||
const [audioStream, setAudioStream] = useState<MediaStream | null>(null);
|
||||
const activeRecordingRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const checkSpeechSupport = async () => {
|
||||
const hasMediaDevices = !!(
|
||||
navigator.mediaDevices && navigator.mediaDevices.getUserMedia
|
||||
)
|
||||
setIsSpeechSupported(hasMediaDevices && !!transcribeAudio)
|
||||
}
|
||||
);
|
||||
setIsSpeechSupported(hasMediaDevices && !!transcribeAudio);
|
||||
};
|
||||
|
||||
checkSpeechSupport()
|
||||
}, [transcribeAudio])
|
||||
checkSpeechSupport();
|
||||
}, [transcribeAudio]);
|
||||
|
||||
const stopRecording = async () => {
|
||||
setIsRecording(false)
|
||||
setIsTranscribing(true)
|
||||
setIsRecording(false);
|
||||
setIsTranscribing(true);
|
||||
try {
|
||||
// First stop the recording to get the final blob
|
||||
recordAudio.stop()
|
||||
recordAudio.stop();
|
||||
// Wait for the recording promise to resolve with the final blob
|
||||
const recording = await activeRecordingRef.current
|
||||
const recording = await activeRecordingRef.current;
|
||||
if (transcribeAudio) {
|
||||
const text = await transcribeAudio(recording)
|
||||
onTranscriptionComplete?.(text)
|
||||
const text = await transcribeAudio(recording);
|
||||
onTranscriptionComplete?.(text);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error transcribing audio:", error)
|
||||
console.error("Error transcribing audio:", error);
|
||||
} finally {
|
||||
setIsTranscribing(false)
|
||||
setIsListening(false)
|
||||
setIsTranscribing(false);
|
||||
setIsListening(false);
|
||||
if (audioStream) {
|
||||
audioStream.getTracks().forEach((track) => track.stop())
|
||||
setAudioStream(null)
|
||||
audioStream.getTracks().forEach(track => track.stop());
|
||||
setAudioStream(null);
|
||||
}
|
||||
activeRecordingRef.current = null
|
||||
activeRecordingRef.current = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const toggleListening = async () => {
|
||||
if (!isListening) {
|
||||
try {
|
||||
setIsListening(true)
|
||||
setIsRecording(true)
|
||||
setIsListening(true);
|
||||
setIsRecording(true);
|
||||
// Get audio stream first
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: true,
|
||||
})
|
||||
setAudioStream(stream)
|
||||
});
|
||||
setAudioStream(stream);
|
||||
|
||||
// Start recording with the stream
|
||||
activeRecordingRef.current = recordAudio(stream)
|
||||
activeRecordingRef.current = recordAudio(stream);
|
||||
} catch (error) {
|
||||
console.error("Error recording audio:", error)
|
||||
setIsListening(false)
|
||||
setIsRecording(false)
|
||||
console.error("Error recording audio:", error);
|
||||
setIsListening(false);
|
||||
setIsRecording(false);
|
||||
if (audioStream) {
|
||||
audioStream.getTracks().forEach((track) => track.stop())
|
||||
setAudioStream(null)
|
||||
audioStream.getTracks().forEach(track => track.stop());
|
||||
setAudioStream(null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await stopRecording()
|
||||
await stopRecording();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
isListening,
|
||||
|
@ -89,5 +89,5 @@ export function useAudioRecording({
|
|||
audioStream,
|
||||
toggleListening,
|
||||
stopRecording,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,67 +1,67 @@
|
|||
import { useEffect, useRef, useState } from "react"
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
// How many pixels from the bottom of the container to enable auto-scroll
|
||||
const ACTIVATION_THRESHOLD = 50
|
||||
const ACTIVATION_THRESHOLD = 50;
|
||||
// Minimum pixels of scroll-up movement required to disable auto-scroll
|
||||
const MIN_SCROLL_UP_THRESHOLD = 10
|
||||
const MIN_SCROLL_UP_THRESHOLD = 10;
|
||||
|
||||
export function useAutoScroll(dependencies: React.DependencyList) {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null)
|
||||
const previousScrollTop = useRef<number | null>(null)
|
||||
const [shouldAutoScroll, setShouldAutoScroll] = useState(true)
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const previousScrollTop = useRef<number | null>(null);
|
||||
const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
if (containerRef.current) {
|
||||
containerRef.current.scrollTop = containerRef.current.scrollHeight
|
||||
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
if (containerRef.current) {
|
||||
const { scrollTop, scrollHeight, clientHeight } = containerRef.current
|
||||
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
|
||||
|
||||
const distanceFromBottom = Math.abs(
|
||||
scrollHeight - scrollTop - clientHeight
|
||||
)
|
||||
);
|
||||
|
||||
const isScrollingUp = previousScrollTop.current
|
||||
? scrollTop < previousScrollTop.current
|
||||
: false
|
||||
: false;
|
||||
|
||||
const scrollUpDistance = previousScrollTop.current
|
||||
? previousScrollTop.current - scrollTop
|
||||
: 0
|
||||
: 0;
|
||||
|
||||
const isDeliberateScrollUp =
|
||||
isScrollingUp && scrollUpDistance > MIN_SCROLL_UP_THRESHOLD
|
||||
isScrollingUp && scrollUpDistance > MIN_SCROLL_UP_THRESHOLD;
|
||||
|
||||
if (isDeliberateScrollUp) {
|
||||
setShouldAutoScroll(false)
|
||||
setShouldAutoScroll(false);
|
||||
} else {
|
||||
const isScrolledToBottom = distanceFromBottom < ACTIVATION_THRESHOLD
|
||||
setShouldAutoScroll(isScrolledToBottom)
|
||||
const isScrolledToBottom = distanceFromBottom < ACTIVATION_THRESHOLD;
|
||||
setShouldAutoScroll(isScrolledToBottom);
|
||||
}
|
||||
|
||||
previousScrollTop.current = scrollTop
|
||||
previousScrollTop.current = scrollTop;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleTouchStart = () => {
|
||||
setShouldAutoScroll(false)
|
||||
}
|
||||
setShouldAutoScroll(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (containerRef.current) {
|
||||
previousScrollTop.current = containerRef.current.scrollTop
|
||||
previousScrollTop.current = containerRef.current.scrollTop;
|
||||
}
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldAutoScroll) {
|
||||
scrollToBottom()
|
||||
scrollToBottom();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, dependencies)
|
||||
}, dependencies);
|
||||
|
||||
return {
|
||||
containerRef,
|
||||
|
@ -69,5 +69,5 @@ export function useAutoScroll(dependencies: React.DependencyList) {
|
|||
handleScroll,
|
||||
shouldAutoScroll,
|
||||
handleTouchStart,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { useLayoutEffect, useRef } from "react"
|
||||
import { useLayoutEffect, useRef } from "react";
|
||||
|
||||
interface UseAutosizeTextAreaProps {
|
||||
ref: React.RefObject<HTMLTextAreaElement | null>
|
||||
maxHeight?: number
|
||||
borderWidth?: number
|
||||
dependencies: React.DependencyList
|
||||
ref: React.RefObject<HTMLTextAreaElement | null>;
|
||||
maxHeight?: number;
|
||||
borderWidth?: number;
|
||||
dependencies: React.DependencyList;
|
||||
}
|
||||
|
||||
export function useAutosizeTextArea({
|
||||
|
@ -13,27 +13,27 @@ export function useAutosizeTextArea({
|
|||
borderWidth = 0,
|
||||
dependencies,
|
||||
}: UseAutosizeTextAreaProps) {
|
||||
const originalHeight = useRef<number | null>(null)
|
||||
const originalHeight = useRef<number | null>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!ref.current) return
|
||||
if (!ref.current) return;
|
||||
|
||||
const currentRef = ref.current
|
||||
const borderAdjustment = borderWidth * 2
|
||||
const currentRef = ref.current;
|
||||
const borderAdjustment = borderWidth * 2;
|
||||
|
||||
if (originalHeight.current === null) {
|
||||
originalHeight.current = currentRef.scrollHeight - borderAdjustment
|
||||
originalHeight.current = currentRef.scrollHeight - borderAdjustment;
|
||||
}
|
||||
|
||||
currentRef.style.removeProperty("height")
|
||||
const scrollHeight = currentRef.scrollHeight
|
||||
currentRef.style.removeProperty("height");
|
||||
const scrollHeight = currentRef.scrollHeight;
|
||||
|
||||
// Make sure we don't go over maxHeight
|
||||
const clampedToMax = Math.min(scrollHeight, maxHeight)
|
||||
const clampedToMax = Math.min(scrollHeight, maxHeight);
|
||||
// Make sure we don't go less than the original height
|
||||
const clampedToMin = Math.max(clampedToMax, originalHeight.current)
|
||||
const clampedToMin = Math.max(clampedToMax, originalHeight.current);
|
||||
|
||||
currentRef.style.height = `${clampedToMin + borderAdjustment}px`
|
||||
currentRef.style.height = `${clampedToMin + borderAdjustment}px`;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [maxHeight, ref, ...dependencies])
|
||||
}, [maxHeight, ref, ...dependencies]);
|
||||
}
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
import { useCallback, useRef, useState } from "react"
|
||||
import { toast } from "sonner"
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
type UseCopyToClipboardProps = {
|
||||
text: string
|
||||
copyMessage?: string
|
||||
}
|
||||
text: string;
|
||||
copyMessage?: string;
|
||||
};
|
||||
|
||||
export function useCopyToClipboard({
|
||||
text,
|
||||
copyMessage = "Copied to clipboard!",
|
||||
}: UseCopyToClipboardProps) {
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const handleCopy = useCallback(() => {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
toast.success(copyMessage)
|
||||
setIsCopied(true)
|
||||
toast.success(copyMessage);
|
||||
setIsCopied(true);
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
timeoutRef.current = null
|
||||
clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = null;
|
||||
}
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setIsCopied(false)
|
||||
}, 2000)
|
||||
setIsCopied(false);
|
||||
}, 2000);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Failed to copy to clipboard.")
|
||||
})
|
||||
}, [text, copyMessage])
|
||||
toast.error("Failed to copy to clipboard.");
|
||||
});
|
||||
}, [text, copyMessage]);
|
||||
|
||||
return { isCopied, handleCopy }
|
||||
return { isCopied, handleCopy };
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ interface UseInfiniteScrollOptions {
|
|||
*/
|
||||
export function useInfiniteScroll(
|
||||
onLoadMore: (() => void) | undefined,
|
||||
options: UseInfiniteScrollOptions = {},
|
||||
options: UseInfiniteScrollOptions = {}
|
||||
) {
|
||||
const { enabled = true, threshold = 0.1, rootMargin = "100px" } = options;
|
||||
const sentinelRef = useRef<HTMLTableRowElement>(null);
|
||||
|
@ -29,7 +29,7 @@ export function useInfiniteScroll(
|
|||
if (!onLoadMore || !enabled) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries => {
|
||||
const [entry] = entries;
|
||||
if (entry.isIntersecting) {
|
||||
onLoadMore();
|
||||
|
@ -38,7 +38,7 @@ export function useInfiniteScroll(
|
|||
{
|
||||
threshold,
|
||||
rootMargin,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const sentinel = sentinelRef.current;
|
||||
|
|
|
@ -4,7 +4,7 @@ const MOBILE_BREAKPOINT = 768;
|
|||
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
|
|
|
@ -38,7 +38,7 @@ interface UsePaginationParams<T> extends UsePaginationOptions {
|
|||
limit: number;
|
||||
model?: string;
|
||||
order?: string;
|
||||
},
|
||||
}
|
||||
) => Promise<PaginationResponse<T>>;
|
||||
errorMessagePrefix: string;
|
||||
enabled?: boolean;
|
||||
|
@ -81,7 +81,7 @@ export function usePagination<T>({
|
|||
const fetchLimit = targetRows || limit;
|
||||
|
||||
try {
|
||||
setState((prev) => ({
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
status: isInitialLoad ? "loading" : "loading-more",
|
||||
error: null,
|
||||
|
@ -94,7 +94,7 @@ export function usePagination<T>({
|
|||
...(order && { order }),
|
||||
});
|
||||
|
||||
setState((prev) => ({
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
data: isInitialLoad
|
||||
? response.data
|
||||
|
@ -124,14 +124,14 @@ export function usePagination<T>({
|
|||
? new Error(`${errorMessage} ${err.message}`)
|
||||
: new Error(errorMessage);
|
||||
|
||||
setState((prev) => ({
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error,
|
||||
status: "error",
|
||||
}));
|
||||
}
|
||||
},
|
||||
[limit, model, order, fetchFunction, errorMessagePrefix, client, router],
|
||||
[limit, model, order, fetchFunction, errorMessagePrefix, client, router]
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue