mirror of
https://github.com/meta-llama/llama-stack.git
synced 2025-08-16 06:27:58 +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
|
@ -47,7 +47,7 @@ async function proxyRequest(request: NextRequest, method: string) {
|
|||
const responseText = await response.text();
|
||||
|
||||
console.log(
|
||||
`Response from FastAPI: ${response.status} ${response.statusText}`,
|
||||
`Response from FastAPI: ${response.status} ${response.statusText}`
|
||||
);
|
||||
|
||||
// Create response with same status and headers
|
||||
|
@ -74,7 +74,7 @@ async function proxyRequest(request: NextRequest, method: string) {
|
|||
backend_url: BACKEND_URL,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
{ status: 500 },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,9 +51,9 @@ export default function SignInPage() {
|
|||
onClick={() => {
|
||||
console.log("Signing in with GitHub...");
|
||||
signIn("github", { callbackUrl: "/auth/signin" }).catch(
|
||||
(error) => {
|
||||
error => {
|
||||
console.error("Sign in error:", error);
|
||||
},
|
||||
}
|
||||
);
|
||||
}}
|
||||
className="w-full"
|
||||
|
|
|
@ -29,14 +29,13 @@ export default function ChatPlaygroundPage() {
|
|||
|
||||
const isModelsLoading = modelsLoading ?? true;
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const fetchModels = async () => {
|
||||
try {
|
||||
setModelsLoading(true);
|
||||
setModelsError(null);
|
||||
const modelList = await client.models.list();
|
||||
const llmModels = modelList.filter(model => model.model_type === 'llm');
|
||||
const llmModels = modelList.filter(model => model.model_type === "llm");
|
||||
setModels(llmModels);
|
||||
if (llmModels.length > 0) {
|
||||
setSelectedModel(llmModels[0].identifier);
|
||||
|
@ -53,103 +52,122 @@ export default function ChatPlaygroundPage() {
|
|||
}, [client]);
|
||||
|
||||
const extractTextContent = (content: unknown): string => {
|
||||
if (typeof content === 'string') {
|
||||
if (typeof content === "string") {
|
||||
return content;
|
||||
}
|
||||
if (Array.isArray(content)) {
|
||||
return content
|
||||
.filter(item => item && typeof item === 'object' && 'type' in item && item.type === 'text')
|
||||
.map(item => (item && typeof item === 'object' && 'text' in item) ? String(item.text) : '')
|
||||
.join('');
|
||||
.filter(
|
||||
item =>
|
||||
item &&
|
||||
typeof item === "object" &&
|
||||
"type" in item &&
|
||||
item.type === "text"
|
||||
)
|
||||
.map(item =>
|
||||
item && typeof item === "object" && "text" in item
|
||||
? String(item.text)
|
||||
: ""
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
if (content && typeof content === 'object' && 'type' in content && content.type === 'text' && 'text' in content) {
|
||||
return String(content.text) || '';
|
||||
if (
|
||||
content &&
|
||||
typeof content === "object" &&
|
||||
"type" in content &&
|
||||
content.type === "text" &&
|
||||
"text" in content
|
||||
) {
|
||||
return String(content.text) || "";
|
||||
}
|
||||
return '';
|
||||
return "";
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setInput(e.target.value);
|
||||
};
|
||||
|
||||
const handleSubmit = async (event?: { preventDefault?: () => void }) => {
|
||||
event?.preventDefault?.();
|
||||
if (!input.trim()) return;
|
||||
const handleSubmit = async (event?: { preventDefault?: () => void }) => {
|
||||
event?.preventDefault?.();
|
||||
if (!input.trim()) return;
|
||||
|
||||
// Add user message to chat
|
||||
const userMessage: Message = {
|
||||
id: Date.now().toString(),
|
||||
role: "user",
|
||||
content: input.trim(),
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
setMessages(prev => [...prev, userMessage]);
|
||||
setInput("");
|
||||
|
||||
// Use the helper function with the content
|
||||
await handleSubmitWithContent(userMessage.content);
|
||||
};
|
||||
|
||||
const handleSubmitWithContent = async (content: string) => {
|
||||
setIsGenerating(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const messageParams: CompletionCreateParams["messages"] = [
|
||||
...messages.map(msg => {
|
||||
const msgContent = typeof msg.content === 'string' ? msg.content : extractTextContent(msg.content);
|
||||
if (msg.role === "user") {
|
||||
return { role: "user" as const, content: msgContent };
|
||||
} else if (msg.role === "assistant") {
|
||||
return { role: "assistant" as const, content: msgContent };
|
||||
} else {
|
||||
return { role: "system" as const, content: msgContent };
|
||||
}
|
||||
}),
|
||||
{ role: "user" as const, content }
|
||||
];
|
||||
|
||||
const response = await client.chat.completions.create({
|
||||
model: selectedModel,
|
||||
messages: messageParams,
|
||||
stream: true,
|
||||
});
|
||||
|
||||
const assistantMessage: Message = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
role: "assistant",
|
||||
content: "",
|
||||
// Add user message to chat
|
||||
const userMessage: Message = {
|
||||
id: Date.now().toString(),
|
||||
role: "user",
|
||||
content: input.trim(),
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
setMessages(prev => [...prev, assistantMessage]);
|
||||
let fullContent = "";
|
||||
for await (const chunk of response) {
|
||||
if (chunk.choices && chunk.choices[0]?.delta?.content) {
|
||||
const deltaContent = chunk.choices[0].delta.content;
|
||||
fullContent += deltaContent;
|
||||
setMessages(prev => [...prev, userMessage]);
|
||||
setInput("");
|
||||
|
||||
flushSync(() => {
|
||||
setMessages(prev => {
|
||||
const newMessages = [...prev];
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (lastMessage.role === "assistant") {
|
||||
lastMessage.content = fullContent;
|
||||
}
|
||||
return newMessages;
|
||||
// Use the helper function with the content
|
||||
await handleSubmitWithContent(userMessage.content);
|
||||
};
|
||||
|
||||
const handleSubmitWithContent = async (content: string) => {
|
||||
setIsGenerating(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const messageParams: CompletionCreateParams["messages"] = [
|
||||
...messages.map(msg => {
|
||||
const msgContent =
|
||||
typeof msg.content === "string"
|
||||
? msg.content
|
||||
: extractTextContent(msg.content);
|
||||
if (msg.role === "user") {
|
||||
return { role: "user" as const, content: msgContent };
|
||||
} else if (msg.role === "assistant") {
|
||||
return { role: "assistant" as const, content: msgContent };
|
||||
} else {
|
||||
return { role: "system" as const, content: msgContent };
|
||||
}
|
||||
}),
|
||||
{ role: "user" as const, content },
|
||||
];
|
||||
|
||||
const response = await client.chat.completions.create({
|
||||
model: selectedModel,
|
||||
messages: messageParams,
|
||||
stream: true,
|
||||
});
|
||||
|
||||
const assistantMessage: Message = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
role: "assistant",
|
||||
content: "",
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
setMessages(prev => [...prev, assistantMessage]);
|
||||
let fullContent = "";
|
||||
for await (const chunk of response) {
|
||||
if (chunk.choices && chunk.choices[0]?.delta?.content) {
|
||||
const deltaContent = chunk.choices[0].delta.content;
|
||||
fullContent += deltaContent;
|
||||
|
||||
flushSync(() => {
|
||||
setMessages(prev => {
|
||||
const newMessages = [...prev];
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (lastMessage.role === "assistant") {
|
||||
lastMessage.content = fullContent;
|
||||
}
|
||||
return newMessages;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error sending message:", err);
|
||||
setError("Failed to send message. Please try again.");
|
||||
setMessages(prev => prev.slice(0, -1));
|
||||
} finally {
|
||||
setIsGenerating(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error sending message:", err);
|
||||
setError("Failed to send message. Please try again.");
|
||||
setMessages(prev => prev.slice(0, -1));
|
||||
} finally {
|
||||
setIsGenerating(false);
|
||||
}
|
||||
};
|
||||
};
|
||||
const suggestions = [
|
||||
"Write a Python function that prints 'Hello, World!'",
|
||||
"Explain step-by-step how to solve this math problem: If x² + 6x + 9 = 25, what is x?",
|
||||
|
@ -163,7 +181,7 @@ const handleSubmitWithContent = async (content: string) => {
|
|||
content: message.content,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
setMessages(prev => [...prev, newMessage])
|
||||
setMessages(prev => [...prev, newMessage]);
|
||||
handleSubmitWithContent(newMessage.content);
|
||||
};
|
||||
|
||||
|
@ -177,12 +195,20 @@ const handleSubmitWithContent = async (content: string) => {
|
|||
<div className="mb-4 flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold">Chat Playground (Completions)</h1>
|
||||
<div className="flex gap-2">
|
||||
<Select value={selectedModel} onValueChange={setSelectedModel} disabled={isModelsLoading || isGenerating}>
|
||||
<Select
|
||||
value={selectedModel}
|
||||
onValueChange={setSelectedModel}
|
||||
disabled={isModelsLoading || isGenerating}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder={isModelsLoading ? "Loading models..." : "Select Model"} />
|
||||
<SelectValue
|
||||
placeholder={
|
||||
isModelsLoading ? "Loading models..." : "Select Model"
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{models.map((model) => (
|
||||
{models.map(model => (
|
||||
<SelectItem key={model.identifier} value={model.identifier}>
|
||||
{model.identifier}
|
||||
</SelectItem>
|
||||
|
|
|
@ -33,12 +33,12 @@ export default function ChatCompletionDetailPage() {
|
|||
} catch (err) {
|
||||
console.error(
|
||||
`Error fetching chat completion detail for ID ${id}:`,
|
||||
err,
|
||||
err
|
||||
);
|
||||
setError(
|
||||
err instanceof Error
|
||||
? err
|
||||
: new Error("Failed to fetch completion detail"),
|
||||
: new Error("Failed to fetch completion detail")
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
|
|
@ -13,10 +13,10 @@ export default function ResponseDetailPage() {
|
|||
const client = useAuthClient();
|
||||
|
||||
const [responseDetail, setResponseDetail] = useState<OpenAIResponse | null>(
|
||||
null,
|
||||
null
|
||||
);
|
||||
const [inputItems, setInputItems] = useState<InputItemListResponse | null>(
|
||||
null,
|
||||
null
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [isLoadingInputItems, setIsLoadingInputItems] = useState<boolean>(true);
|
||||
|
@ -25,7 +25,7 @@ export default function ResponseDetailPage() {
|
|||
|
||||
// Helper function to convert ResponseObject to OpenAIResponse
|
||||
const convertResponseObject = (
|
||||
responseData: ResponseObject,
|
||||
responseData: ResponseObject
|
||||
): OpenAIResponse => {
|
||||
return {
|
||||
id: responseData.id,
|
||||
|
@ -73,12 +73,12 @@ export default function ResponseDetailPage() {
|
|||
} else {
|
||||
console.error(
|
||||
`Error fetching response detail for ID ${id}:`,
|
||||
responseResult.reason,
|
||||
responseResult.reason
|
||||
);
|
||||
setError(
|
||||
responseResult.reason instanceof Error
|
||||
? responseResult.reason
|
||||
: new Error("Failed to fetch response detail"),
|
||||
: new Error("Failed to fetch response detail")
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -90,18 +90,18 @@ export default function ResponseDetailPage() {
|
|||
} else {
|
||||
console.error(
|
||||
`Error fetching input items for response ID ${id}:`,
|
||||
inputItemsResult.reason,
|
||||
inputItemsResult.reason
|
||||
);
|
||||
setInputItemsError(
|
||||
inputItemsResult.reason instanceof Error
|
||||
? inputItemsResult.reason
|
||||
: new Error("Failed to fetch input items"),
|
||||
: new Error("Failed to fetch input items")
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Unexpected error fetching data for ID ${id}:`, err);
|
||||
setError(
|
||||
err instanceof Error ? err : new Error("Unexpected error occurred"),
|
||||
err instanceof Error ? err : new Error("Unexpected error occurred")
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
|
|
@ -18,7 +18,10 @@ import {
|
|||
PropertiesCard,
|
||||
PropertyItem,
|
||||
} from "@/components/layout/detail-layout";
|
||||
import { PageBreadcrumb, BreadcrumbSegment } from "@/components/layout/page-breadcrumb";
|
||||
import {
|
||||
PageBreadcrumb,
|
||||
BreadcrumbSegment,
|
||||
} from "@/components/layout/page-breadcrumb";
|
||||
|
||||
export default function ContentDetailPage() {
|
||||
const params = useParams();
|
||||
|
@ -28,13 +31,13 @@ export default function ContentDetailPage() {
|
|||
const contentId = params.contentId as string;
|
||||
const client = useAuthClient();
|
||||
|
||||
const getTextFromContent = (content: any): string => {
|
||||
if (typeof content === 'string') {
|
||||
const getTextFromContent = (content: unknown): string => {
|
||||
if (typeof content === "string") {
|
||||
return content;
|
||||
} else if (content && content.type === 'text') {
|
||||
} else if (content && content.type === "text") {
|
||||
return content.text;
|
||||
}
|
||||
return '';
|
||||
return "";
|
||||
};
|
||||
|
||||
const [store, setStore] = useState<VectorStore | null>(null);
|
||||
|
@ -44,7 +47,9 @@ export default function ContentDetailPage() {
|
|||
const [error, setError] = useState<Error | null>(null);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editedContent, setEditedContent] = useState("");
|
||||
const [editedMetadata, setEditedMetadata] = useState<Record<string, any>>({});
|
||||
const [editedMetadata, setEditedMetadata] = useState<Record<string, unknown>>(
|
||||
{}
|
||||
);
|
||||
const [isEditingEmbedding, setIsEditingEmbedding] = useState(false);
|
||||
const [editedEmbedding, setEditedEmbedding] = useState<number[]>([]);
|
||||
|
||||
|
@ -64,8 +69,13 @@ export default function ContentDetailPage() {
|
|||
setFile(fileResponse as VectorStoreFile);
|
||||
|
||||
const contentsAPI = new ContentsAPI(client);
|
||||
const contentsResponse = await contentsAPI.listContents(vectorStoreId, fileId);
|
||||
const targetContent = contentsResponse.data.find(c => c.id === contentId);
|
||||
const contentsResponse = await contentsAPI.listContents(
|
||||
vectorStoreId,
|
||||
fileId
|
||||
);
|
||||
const targetContent = contentsResponse.data.find(
|
||||
c => c.id === contentId
|
||||
);
|
||||
|
||||
if (targetContent) {
|
||||
setContent(targetContent);
|
||||
|
@ -76,7 +86,9 @@ export default function ContentDetailPage() {
|
|||
throw new Error(`Content ${contentId} not found`);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err : new Error("Failed to load content."));
|
||||
setError(
|
||||
err instanceof Error ? err : new Error("Failed to load content.")
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
@ -88,7 +100,8 @@ export default function ContentDetailPage() {
|
|||
if (!content) return;
|
||||
|
||||
try {
|
||||
const updates: { content?: string; metadata?: Record<string, any> } = {};
|
||||
const updates: { content?: string; metadata?: Record<string, unknown> } =
|
||||
{};
|
||||
|
||||
if (editedContent !== getTextFromContent(content.content)) {
|
||||
updates.content = editedContent;
|
||||
|
@ -100,25 +113,32 @@ export default function ContentDetailPage() {
|
|||
|
||||
if (Object.keys(updates).length > 0) {
|
||||
const contentsAPI = new ContentsAPI(client);
|
||||
const updatedContent = await contentsAPI.updateContent(vectorStoreId, fileId, contentId, updates);
|
||||
const updatedContent = await contentsAPI.updateContent(
|
||||
vectorStoreId,
|
||||
fileId,
|
||||
contentId,
|
||||
updates
|
||||
);
|
||||
setContent(updatedContent);
|
||||
}
|
||||
|
||||
setIsEditing(false);
|
||||
} catch (err) {
|
||||
console.error('Failed to update content:', err);
|
||||
console.error("Failed to update content:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!confirm('Are you sure you want to delete this content?')) return;
|
||||
if (!confirm("Are you sure you want to delete this content?")) return;
|
||||
|
||||
try {
|
||||
const contentsAPI = new ContentsAPI(client);
|
||||
await contentsAPI.deleteContent(vectorStoreId, fileId, contentId);
|
||||
router.push(`/logs/vector-stores/${vectorStoreId}/files/${fileId}/contents`);
|
||||
router.push(
|
||||
`/logs/vector-stores/${vectorStoreId}/files/${fileId}/contents`
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Failed to delete content:', err);
|
||||
console.error("Failed to delete content:", err);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -134,10 +154,19 @@ export default function ContentDetailPage() {
|
|||
|
||||
const breadcrumbSegments: BreadcrumbSegment[] = [
|
||||
{ label: "Vector Stores", href: "/logs/vector-stores" },
|
||||
{ label: store?.name || vectorStoreId, href: `/logs/vector-stores/${vectorStoreId}` },
|
||||
{
|
||||
label: store?.name || vectorStoreId,
|
||||
href: `/logs/vector-stores/${vectorStoreId}`,
|
||||
},
|
||||
{ label: "Files", href: `/logs/vector-stores/${vectorStoreId}` },
|
||||
{ label: fileId, href: `/logs/vector-stores/${vectorStoreId}/files/${fileId}` },
|
||||
{ label: "Contents", href: `/logs/vector-stores/${vectorStoreId}/files/${fileId}/contents` },
|
||||
{
|
||||
label: fileId,
|
||||
href: `/logs/vector-stores/${vectorStoreId}/files/${fileId}`,
|
||||
},
|
||||
{
|
||||
label: "Contents",
|
||||
href: `/logs/vector-stores/${vectorStoreId}/files/${fileId}/contents`,
|
||||
},
|
||||
{ label: contentId },
|
||||
];
|
||||
|
||||
|
@ -186,7 +215,7 @@ export default function ContentDetailPage() {
|
|||
{isEditing ? (
|
||||
<textarea
|
||||
value={editedContent}
|
||||
onChange={(e) => setEditedContent(e.target.value)}
|
||||
onChange={e => setEditedContent(e.target.value)}
|
||||
className="w-full h-64 p-3 border rounded-md resize-none font-mono text-sm"
|
||||
placeholder="Enter content..."
|
||||
/>
|
||||
|
@ -206,16 +235,23 @@ export default function ContentDetailPage() {
|
|||
<div className="flex gap-2">
|
||||
{isEditingEmbedding ? (
|
||||
<>
|
||||
<Button size="sm" onClick={() => {
|
||||
setIsEditingEmbedding(false);
|
||||
}}>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setIsEditingEmbedding(false);
|
||||
}}
|
||||
>
|
||||
<Save className="h-4 w-4 mr-1" />
|
||||
Save
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={() => {
|
||||
setEditedEmbedding(content?.embedding || []);
|
||||
setIsEditingEmbedding(false);
|
||||
}}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setEditedEmbedding(content?.embedding || []);
|
||||
setIsEditingEmbedding(false);
|
||||
}}
|
||||
>
|
||||
<X className="h-4 w-4 mr-1" />
|
||||
Cancel
|
||||
</Button>
|
||||
|
@ -237,14 +273,16 @@ export default function ContentDetailPage() {
|
|||
</p>
|
||||
<textarea
|
||||
value={JSON.stringify(editedEmbedding, null, 2)}
|
||||
onChange={(e) => {
|
||||
onChange={e => {
|
||||
try {
|
||||
const parsed = JSON.parse(e.target.value);
|
||||
if (Array.isArray(parsed) && parsed.every(v => typeof v === 'number')) {
|
||||
if (
|
||||
Array.isArray(parsed) &&
|
||||
parsed.every(v => typeof v === "number")
|
||||
) {
|
||||
setEditedEmbedding(parsed);
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
} catch {}
|
||||
}}
|
||||
className="w-full h-32 p-3 border rounded-md resize-none font-mono text-xs"
|
||||
placeholder="Enter embedding as JSON array..."
|
||||
|
@ -259,8 +297,15 @@ export default function ContentDetailPage() {
|
|||
</div>
|
||||
<div className="p-3 bg-gray-50 dark:bg-gray-800 rounded-md max-h-32 overflow-y-auto">
|
||||
<pre className="whitespace-pre-wrap font-mono text-xs text-gray-900 dark:text-gray-100">
|
||||
[{content.embedding.slice(0, 20).map(v => v.toFixed(6)).join(', ')}
|
||||
{content.embedding.length > 20 ? `\n... and ${content.embedding.length - 20} more values` : ''}]
|
||||
[
|
||||
{content.embedding
|
||||
.slice(0, 20)
|
||||
.map(v => v.toFixed(6))
|
||||
.join(", ")}
|
||||
{content.embedding.length > 20
|
||||
? `\n... and ${content.embedding.length - 20} more values`
|
||||
: ""}
|
||||
]
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -284,7 +329,7 @@ export default function ContentDetailPage() {
|
|||
<div key={key} className="flex gap-2">
|
||||
<Input
|
||||
value={key}
|
||||
onChange={(e) => {
|
||||
onChange={e => {
|
||||
const newMetadata = { ...editedMetadata };
|
||||
delete newMetadata[key];
|
||||
newMetadata[e.target.value] = value;
|
||||
|
@ -294,11 +339,13 @@ export default function ContentDetailPage() {
|
|||
className="flex-1"
|
||||
/>
|
||||
<Input
|
||||
value={typeof value === 'string' ? value : JSON.stringify(value)}
|
||||
onChange={(e) => {
|
||||
value={
|
||||
typeof value === "string" ? value : JSON.stringify(value)
|
||||
}
|
||||
onChange={e => {
|
||||
setEditedMetadata({
|
||||
...editedMetadata,
|
||||
[key]: e.target.value
|
||||
[key]: e.target.value,
|
||||
});
|
||||
}}
|
||||
placeholder="Value"
|
||||
|
@ -312,7 +359,7 @@ export default function ContentDetailPage() {
|
|||
onClick={() => {
|
||||
setEditedMetadata({
|
||||
...editedMetadata,
|
||||
['']: ''
|
||||
[""]: "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
@ -325,7 +372,7 @@ export default function ContentDetailPage() {
|
|||
<div key={key} className="flex justify-between py-1">
|
||||
<span className="font-medium text-gray-600">{key}:</span>
|
||||
<span className="font-mono text-sm">
|
||||
{typeof value === 'string' ? value : JSON.stringify(value)}
|
||||
{typeof value === "string" ? value : JSON.stringify(value)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
|
@ -351,15 +398,15 @@ export default function ContentDetailPage() {
|
|||
value={`${getTextFromContent(content.content).length} chars`}
|
||||
/>
|
||||
{content.metadata.chunk_window && (
|
||||
<PropertyItem
|
||||
label="Position"
|
||||
value={content.metadata.chunk_window}
|
||||
/>
|
||||
<PropertyItem label="Position" value={content.metadata.chunk_window} />
|
||||
)}
|
||||
{file && (
|
||||
<>
|
||||
<PropertyItem label="File Status" value={file.status} />
|
||||
<PropertyItem label="File Usage" value={`${file.usage_bytes} bytes`} />
|
||||
<PropertyItem
|
||||
label="File Usage"
|
||||
value={`${file.usage_bytes} bytes`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{store && (
|
||||
|
|
|
@ -18,7 +18,10 @@ import {
|
|||
PropertiesCard,
|
||||
PropertyItem,
|
||||
} from "@/components/layout/detail-layout";
|
||||
import { PageBreadcrumb, BreadcrumbSegment } from "@/components/layout/page-breadcrumb";
|
||||
import {
|
||||
PageBreadcrumb,
|
||||
BreadcrumbSegment,
|
||||
} from "@/components/layout/page-breadcrumb";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
@ -36,23 +39,21 @@ export default function ContentsListPage() {
|
|||
const fileId = params.fileId as string;
|
||||
const client = useAuthClient();
|
||||
|
||||
const getTextFromContent = (content: any): string => {
|
||||
if (typeof content === 'string') {
|
||||
const getTextFromContent = (content: unknown): string => {
|
||||
if (typeof content === "string") {
|
||||
return content;
|
||||
} else if (content && content.type === 'text') {
|
||||
} else if (content && content.type === "text") {
|
||||
return content.text;
|
||||
}
|
||||
return '';
|
||||
return "";
|
||||
};
|
||||
|
||||
const [store, setStore] = useState<VectorStore | null>(null);
|
||||
const [file, setFile] = useState<VectorStoreFile | null>(null);
|
||||
const [contents, setContents] = useState<VectorStoreContentItem[]>([]);
|
||||
const [isLoadingStore, setIsLoadingStore] = useState(true);
|
||||
const [isLoadingFile, setIsLoadingFile] = useState(true);
|
||||
const [isLoadingContents, setIsLoadingContents] = useState(true);
|
||||
const [errorStore, setErrorStore] = useState<Error | null>(null);
|
||||
const [errorFile, setErrorFile] = useState<Error | null>(null);
|
||||
const [errorContents, setErrorContents] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -65,7 +66,9 @@ export default function ContentsListPage() {
|
|||
const response = await client.vectorStores.retrieve(vectorStoreId);
|
||||
setStore(response as VectorStore);
|
||||
} catch (err) {
|
||||
setErrorStore(err instanceof Error ? err : new Error("Failed to load vector store."));
|
||||
setErrorStore(
|
||||
err instanceof Error ? err : new Error("Failed to load vector store.")
|
||||
);
|
||||
} finally {
|
||||
setIsLoadingStore(false);
|
||||
}
|
||||
|
@ -80,10 +83,15 @@ export default function ContentsListPage() {
|
|||
setIsLoadingFile(true);
|
||||
setErrorFile(null);
|
||||
try {
|
||||
const response = await client.vectorStores.files.retrieve(vectorStoreId, fileId);
|
||||
const response = await client.vectorStores.files.retrieve(
|
||||
vectorStoreId,
|
||||
fileId
|
||||
);
|
||||
setFile(response as VectorStoreFile);
|
||||
} catch (err) {
|
||||
setErrorFile(err instanceof Error ? err : new Error("Failed to load file."));
|
||||
setErrorFile(
|
||||
err instanceof Error ? err : new Error("Failed to load file.")
|
||||
);
|
||||
} finally {
|
||||
setIsLoadingFile(false);
|
||||
}
|
||||
|
@ -99,10 +107,16 @@ export default function ContentsListPage() {
|
|||
setErrorContents(null);
|
||||
try {
|
||||
const contentsAPI = new ContentsAPI(client);
|
||||
const contentsResponse = await contentsAPI.listContents(vectorStoreId, fileId, { limit: 100 });
|
||||
const contentsResponse = await contentsAPI.listContents(
|
||||
vectorStoreId,
|
||||
fileId,
|
||||
{ limit: 100 }
|
||||
);
|
||||
setContents(contentsResponse.data);
|
||||
} catch (err) {
|
||||
setErrorContents(err instanceof Error ? err : new Error("Failed to load contents."));
|
||||
setErrorContents(
|
||||
err instanceof Error ? err : new Error("Failed to load contents.")
|
||||
);
|
||||
} finally {
|
||||
setIsLoadingContents(false);
|
||||
}
|
||||
|
@ -116,26 +130,36 @@ export default function ContentsListPage() {
|
|||
await contentsAPI.deleteContent(vectorStoreId, fileId, contentId);
|
||||
setContents(contents.filter(content => content.id !== contentId));
|
||||
} catch (err) {
|
||||
console.error('Failed to delete content:', err);
|
||||
console.error("Failed to delete content:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewContent = (contentId: string) => {
|
||||
router.push(`/logs/vector-stores/${vectorStoreId}/files/${fileId}/contents/${contentId}`);
|
||||
router.push(
|
||||
`/logs/vector-stores/${vectorStoreId}/files/${fileId}/contents/${contentId}`
|
||||
);
|
||||
};
|
||||
|
||||
const title = `Contents in File: ${fileId}`;
|
||||
|
||||
const breadcrumbSegments: BreadcrumbSegment[] = [
|
||||
{ label: "Vector Stores", href: "/logs/vector-stores" },
|
||||
{ label: store?.name || vectorStoreId, href: `/logs/vector-stores/${vectorStoreId}` },
|
||||
{
|
||||
label: store?.name || vectorStoreId,
|
||||
href: `/logs/vector-stores/${vectorStoreId}`,
|
||||
},
|
||||
{ label: "Files", href: `/logs/vector-stores/${vectorStoreId}` },
|
||||
{ label: fileId, href: `/logs/vector-stores/${vectorStoreId}/files/${fileId}` },
|
||||
{
|
||||
label: fileId,
|
||||
href: `/logs/vector-stores/${vectorStoreId}/files/${fileId}`,
|
||||
},
|
||||
{ label: "Contents" },
|
||||
];
|
||||
|
||||
if (errorStore) {
|
||||
return <DetailErrorView title={title} id={vectorStoreId} error={errorStore} />;
|
||||
return (
|
||||
<DetailErrorView title={title} id={vectorStoreId} error={errorStore} />
|
||||
);
|
||||
}
|
||||
if (isLoadingStore) {
|
||||
return <DetailLoadingView title={title} />;
|
||||
|
@ -175,7 +199,7 @@ export default function ContentsListPage() {
|
|||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{contents.map((content) => (
|
||||
{contents.map(content => (
|
||||
<TableRow key={content.id}>
|
||||
<TableCell className="font-mono text-xs">
|
||||
<Button
|
||||
|
@ -189,7 +213,10 @@ export default function ContentsListPage() {
|
|||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="max-w-md">
|
||||
<p className="text-sm truncate" title={getTextFromContent(content.content)}>
|
||||
<p
|
||||
className="text-sm truncate"
|
||||
title={getTextFromContent(content.content)}
|
||||
>
|
||||
{getTextFromContent(content.content)}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -197,12 +224,25 @@ export default function ContentsListPage() {
|
|||
<TableCell className="text-xs text-gray-500">
|
||||
{content.embedding && content.embedding.length > 0 ? (
|
||||
<div className="max-w-xs">
|
||||
<span className="font-mono text-xs bg-gray-100 dark:bg-gray-800 rounded px-1 py-0.5" title={`${content.embedding.length}D vector: [${content.embedding.slice(0, 3).map(v => v.toFixed(3)).join(', ')}...]`}>
|
||||
[{content.embedding.slice(0, 3).map(v => v.toFixed(3)).join(', ')}...] ({content.embedding.length}D)
|
||||
<span
|
||||
className="font-mono text-xs bg-gray-100 dark:bg-gray-800 rounded px-1 py-0.5"
|
||||
title={`${content.embedding.length}D vector: [${content.embedding
|
||||
.slice(0, 3)
|
||||
.map(v => v.toFixed(3))
|
||||
.join(", ")}...]`}
|
||||
>
|
||||
[
|
||||
{content.embedding
|
||||
.slice(0, 3)
|
||||
.map(v => v.toFixed(3))
|
||||
.join(", ")}
|
||||
...] ({content.embedding.length}D)
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-gray-400 dark:text-gray-500 italic">No embedding</span>
|
||||
<span className="text-gray-400 dark:text-gray-500 italic">
|
||||
No embedding
|
||||
</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs text-gray-500">
|
||||
|
@ -211,7 +251,9 @@ export default function ContentsListPage() {
|
|||
: `${content.metadata.content_length || 0} chars`}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
{new Date(content.created_timestamp * 1000).toLocaleString()}
|
||||
{new Date(
|
||||
content.created_timestamp * 1000
|
||||
).toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex gap-1">
|
||||
|
|
|
@ -4,9 +4,12 @@ import { useEffect, useState } from "react";
|
|||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useAuthClient } from "@/hooks/use-auth-client";
|
||||
import type { VectorStore } from "llama-stack-client/resources/vector-stores/vector-stores";
|
||||
import type { VectorStoreFile, FileContentResponse } from "llama-stack-client/resources/vector-stores/files";
|
||||
import type {
|
||||
VectorStoreFile,
|
||||
FileContentResponse,
|
||||
} from "llama-stack-client/resources/vector-stores/files";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { List } from "lucide-react";
|
||||
import {
|
||||
|
@ -17,7 +20,10 @@ import {
|
|||
PropertiesCard,
|
||||
PropertyItem,
|
||||
} from "@/components/layout/detail-layout";
|
||||
import { PageBreadcrumb, BreadcrumbSegment } from "@/components/layout/page-breadcrumb";
|
||||
import {
|
||||
PageBreadcrumb,
|
||||
BreadcrumbSegment,
|
||||
} from "@/components/layout/page-breadcrumb";
|
||||
|
||||
export default function FileDetailPage() {
|
||||
const params = useParams();
|
||||
|
@ -46,7 +52,9 @@ export default function FileDetailPage() {
|
|||
const response = await client.vectorStores.retrieve(vectorStoreId);
|
||||
setStore(response as VectorStore);
|
||||
} catch (err) {
|
||||
setErrorStore(err instanceof Error ? err : new Error("Failed to load vector store."));
|
||||
setErrorStore(
|
||||
err instanceof Error ? err : new Error("Failed to load vector store.")
|
||||
);
|
||||
} finally {
|
||||
setIsLoadingStore(false);
|
||||
}
|
||||
|
@ -61,10 +69,15 @@ export default function FileDetailPage() {
|
|||
setIsLoadingFile(true);
|
||||
setErrorFile(null);
|
||||
try {
|
||||
const response = await client.vectorStores.files.retrieve(vectorStoreId, fileId);
|
||||
const response = await client.vectorStores.files.retrieve(
|
||||
vectorStoreId,
|
||||
fileId
|
||||
);
|
||||
setFile(response as VectorStoreFile);
|
||||
} catch (err) {
|
||||
setErrorFile(err instanceof Error ? err : new Error("Failed to load file."));
|
||||
setErrorFile(
|
||||
err instanceof Error ? err : new Error("Failed to load file.")
|
||||
);
|
||||
} finally {
|
||||
setIsLoadingFile(false);
|
||||
}
|
||||
|
@ -79,10 +92,15 @@ export default function FileDetailPage() {
|
|||
setIsLoadingContents(true);
|
||||
setErrorContents(null);
|
||||
try {
|
||||
const response = await client.vectorStores.files.content(vectorStoreId, fileId);
|
||||
const response = await client.vectorStores.files.content(
|
||||
vectorStoreId,
|
||||
fileId
|
||||
);
|
||||
setContents(response);
|
||||
} catch (err) {
|
||||
setErrorContents(err instanceof Error ? err : new Error("Failed to load contents."));
|
||||
setErrorContents(
|
||||
err instanceof Error ? err : new Error("Failed to load contents.")
|
||||
);
|
||||
} finally {
|
||||
setIsLoadingContents(false);
|
||||
}
|
||||
|
@ -91,20 +109,27 @@ export default function FileDetailPage() {
|
|||
}, [vectorStoreId, fileId, client]);
|
||||
|
||||
const handleViewContents = () => {
|
||||
router.push(`/logs/vector-stores/${vectorStoreId}/files/${fileId}/contents`);
|
||||
router.push(
|
||||
`/logs/vector-stores/${vectorStoreId}/files/${fileId}/contents`
|
||||
);
|
||||
};
|
||||
|
||||
const title = `File: ${fileId}`;
|
||||
|
||||
const breadcrumbSegments: BreadcrumbSegment[] = [
|
||||
{ label: "Vector Stores", href: "/logs/vector-stores" },
|
||||
{ label: store?.name || vectorStoreId, href: `/logs/vector-stores/${vectorStoreId}` },
|
||||
{
|
||||
label: store?.name || vectorStoreId,
|
||||
href: `/logs/vector-stores/${vectorStoreId}`,
|
||||
},
|
||||
{ label: "Files", href: `/logs/vector-stores/${vectorStoreId}` },
|
||||
{ label: fileId },
|
||||
];
|
||||
|
||||
if (errorStore) {
|
||||
return <DetailErrorView title={title} id={vectorStoreId} error={errorStore} />;
|
||||
return (
|
||||
<DetailErrorView title={title} id={vectorStoreId} error={errorStore} />
|
||||
);
|
||||
}
|
||||
if (isLoadingStore) {
|
||||
return <DetailLoadingView title={title} />;
|
||||
|
@ -136,19 +161,29 @@ export default function FileDetailPage() {
|
|||
<h3 className="text-lg font-medium mb-2">File Details</h3>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="font-medium text-gray-600 dark:text-gray-400">Status:</span>
|
||||
<span className="font-medium text-gray-600 dark:text-gray-400">
|
||||
Status:
|
||||
</span>
|
||||
<span className="ml-2">{file.status}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-gray-600 dark:text-gray-400">Size:</span>
|
||||
<span className="font-medium text-gray-600 dark:text-gray-400">
|
||||
Size:
|
||||
</span>
|
||||
<span className="ml-2">{file.usage_bytes} bytes</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-gray-600 dark:text-gray-400">Created:</span>
|
||||
<span className="ml-2">{new Date(file.created_at * 1000).toLocaleString()}</span>
|
||||
<span className="font-medium text-gray-600 dark:text-gray-400">
|
||||
Created:
|
||||
</span>
|
||||
<span className="ml-2">
|
||||
{new Date(file.created_at * 1000).toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-gray-600 dark:text-gray-400">Content Strategy:</span>
|
||||
<span className="font-medium text-gray-600 dark:text-gray-400">
|
||||
Content Strategy:
|
||||
</span>
|
||||
<span className="ml-2">{file.chunking_strategy.type}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -166,9 +201,7 @@ export default function FileDetailPage() {
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-500 italic text-sm">
|
||||
File not found.
|
||||
</p>
|
||||
<p className="text-gray-500 italic text-sm">File not found.</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
@ -192,16 +225,27 @@ export default function FileDetailPage() {
|
|||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="font-medium text-gray-600 dark:text-gray-400">Content Items:</span>
|
||||
<span className="font-medium text-gray-600 dark:text-gray-400">
|
||||
Content Items:
|
||||
</span>
|
||||
<span className="ml-2">{contents.content.length}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-gray-600 dark:text-gray-400">Total Characters:</span>
|
||||
<span className="ml-2">{contents.content.reduce((total, item) => total + item.text.length, 0)}</span>
|
||||
<span className="font-medium text-gray-600 dark:text-gray-400">
|
||||
Total Characters:
|
||||
</span>
|
||||
<span className="ml-2">
|
||||
{contents.content.reduce(
|
||||
(total, item) => total + item.text.length,
|
||||
0
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">Preview:</span>
|
||||
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Preview:
|
||||
</span>
|
||||
<div className="mt-1 bg-gray-50 dark:bg-gray-800 rounded-md p-3">
|
||||
<p className="text-sm text-gray-900 dark:text-gray-100 line-clamp-3">
|
||||
{contents.content[0]?.text.substring(0, 200)}...
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useAuthClient } from "@/hooks/use-auth-client";
|
||||
import type { VectorStore } from "llama-stack-client/resources/vector-stores/vector-stores";
|
||||
import type { VectorStoreFile } from "llama-stack-client/resources/vector-stores/files";
|
||||
|
@ -11,7 +11,6 @@ export default function VectorStoreDetailPage() {
|
|||
const params = useParams();
|
||||
const id = params.id as string;
|
||||
const client = useAuthClient();
|
||||
const router = useRouter();
|
||||
|
||||
const [store, setStore] = useState<VectorStore | null>(null);
|
||||
const [files, setFiles] = useState<VectorStoreFile[]>([]);
|
||||
|
@ -34,9 +33,7 @@ export default function VectorStoreDetailPage() {
|
|||
setStore(response as VectorStore);
|
||||
} catch (err) {
|
||||
setErrorStore(
|
||||
err instanceof Error
|
||||
? err
|
||||
: new Error("Failed to load vector store."),
|
||||
err instanceof Error ? err : new Error("Failed to load vector store.")
|
||||
);
|
||||
} finally {
|
||||
setIsLoadingStore(false);
|
||||
|
@ -55,18 +52,18 @@ export default function VectorStoreDetailPage() {
|
|||
setIsLoadingFiles(true);
|
||||
setErrorFiles(null);
|
||||
try {
|
||||
const result = await client.vectorStores.files.list(id as any);
|
||||
setFiles((result as any).data);
|
||||
const result = await client.vectorStores.files.list(id);
|
||||
setFiles((result as { data: VectorStoreFile[] }).data);
|
||||
} catch (err) {
|
||||
setErrorFiles(
|
||||
err instanceof Error ? err : new Error("Failed to load files."),
|
||||
err instanceof Error ? err : new Error("Failed to load files.")
|
||||
);
|
||||
} finally {
|
||||
setIsLoadingFiles(false);
|
||||
}
|
||||
};
|
||||
fetchFiles();
|
||||
}, [id]);
|
||||
}, [id, client.vectorStores.files]);
|
||||
|
||||
return (
|
||||
<VectorStoreDetailView
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { useAuthClient } from "@/hooks/use-auth-client";
|
||||
import type {
|
||||
ListVectorStoresResponse,
|
||||
VectorStore,
|
||||
|
@ -12,7 +11,6 @@ import { Button } from "@/components/ui/button";
|
|||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCaption,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
|
@ -21,7 +19,6 @@ import {
|
|||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
export default function VectorStoresPage() {
|
||||
const client = useAuthClient();
|
||||
const router = useRouter();
|
||||
const {
|
||||
data: stores,
|
||||
|
@ -37,7 +34,7 @@ export default function VectorStoresPage() {
|
|||
after: params.after,
|
||||
limit: params.limit,
|
||||
order: params.order,
|
||||
} as any);
|
||||
} as Parameters<typeof client.vectorStores.list>[0]);
|
||||
return response as ListVectorStoresResponse;
|
||||
},
|
||||
errorMessagePrefix: "vector stores",
|
||||
|
@ -53,11 +50,11 @@ export default function VectorStoresPage() {
|
|||
const renderContent = () => {
|
||||
if (status === "loading") {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-8 w-full"/>
|
||||
<Skeleton className="h-4 w-full"/>
|
||||
<Skeleton className="h-4 w-full"/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-8 w-full" />
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-full" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -70,72 +67,72 @@ export default function VectorStoresPage() {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-auto flex-1 min-h-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Created</TableHead>
|
||||
<TableHead>Completed</TableHead>
|
||||
<TableHead>Cancelled</TableHead>
|
||||
<TableHead>Failed</TableHead>
|
||||
<TableHead>In Progress</TableHead>
|
||||
<TableHead>Total</TableHead>
|
||||
<TableHead>Usage Bytes</TableHead>
|
||||
<TableHead>Provider ID</TableHead>
|
||||
<TableHead>Provider Vector DB ID</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{stores.map((store) => {
|
||||
const fileCounts = store.file_counts;
|
||||
const metadata = store.metadata || {};
|
||||
const providerId = metadata.provider_id ?? "";
|
||||
const providerDbId = metadata.provider_vector_db_id ?? "";
|
||||
<div className="overflow-auto flex-1 min-h-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Created</TableHead>
|
||||
<TableHead>Completed</TableHead>
|
||||
<TableHead>Cancelled</TableHead>
|
||||
<TableHead>Failed</TableHead>
|
||||
<TableHead>In Progress</TableHead>
|
||||
<TableHead>Total</TableHead>
|
||||
<TableHead>Usage Bytes</TableHead>
|
||||
<TableHead>Provider ID</TableHead>
|
||||
<TableHead>Provider Vector DB ID</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{stores.map(store => {
|
||||
const fileCounts = store.file_counts;
|
||||
const metadata = store.metadata || {};
|
||||
const providerId = metadata.provider_id ?? "";
|
||||
const providerDbId = metadata.provider_vector_db_id ?? "";
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={store.id}
|
||||
onClick={() => router.push(`/logs/vector-stores/${store.id}`)}
|
||||
className="cursor-pointer hover:bg-muted/50"
|
||||
return (
|
||||
<TableRow
|
||||
key={store.id}
|
||||
onClick={() => router.push(`/logs/vector-stores/${store.id}`)}
|
||||
className="cursor-pointer hover:bg-muted/50"
|
||||
>
|
||||
<TableCell>
|
||||
<Button
|
||||
variant="link"
|
||||
className="p-0 h-auto font-mono text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
||||
onClick={() =>
|
||||
router.push(`/logs/vector-stores/${store.id}`)
|
||||
}
|
||||
>
|
||||
<TableCell>
|
||||
<Button
|
||||
variant="link"
|
||||
className="p-0 h-auto font-mono text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
||||
onClick={() =>
|
||||
router.push(`/logs/vector-stores/${store.id}`)
|
||||
}
|
||||
>
|
||||
{store.id}
|
||||
</Button>
|
||||
</TableCell>
|
||||
<TableCell>{store.name}</TableCell>
|
||||
<TableCell>
|
||||
{new Date(store.created_at * 1000).toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell>{fileCounts.completed}</TableCell>
|
||||
<TableCell>{fileCounts.cancelled}</TableCell>
|
||||
<TableCell>{fileCounts.failed}</TableCell>
|
||||
<TableCell>{fileCounts.in_progress}</TableCell>
|
||||
<TableCell>{fileCounts.total}</TableCell>
|
||||
<TableCell>{store.usage_bytes}</TableCell>
|
||||
<TableCell>{providerId}</TableCell>
|
||||
<TableCell>{providerDbId}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
{store.id}
|
||||
</Button>
|
||||
</TableCell>
|
||||
<TableCell>{store.name}</TableCell>
|
||||
<TableCell>
|
||||
{new Date(store.created_at * 1000).toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell>{fileCounts.completed}</TableCell>
|
||||
<TableCell>{fileCounts.cancelled}</TableCell>
|
||||
<TableCell>{fileCounts.failed}</TableCell>
|
||||
<TableCell>{fileCounts.in_progress}</TableCell>
|
||||
<TableCell>{fileCounts.total}</TableCell>
|
||||
<TableCell>{store.usage_bytes}</TableCell>
|
||||
<TableCell>{providerId}</TableCell>
|
||||
<TableCell>{providerDbId}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-2xl font-semibold">Vector Stores</h1>
|
||||
{renderContent()}
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-2xl font-semibold">Vector Stores</h1>
|
||||
{renderContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue