Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ exclude: 'build/'

default_language_version:
python: python3.12
node: "22"

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
Expand Down Expand Up @@ -145,6 +146,20 @@ repos:
pass_filenames: false
require_serial: true
files: ^.github/workflows/.*$
- id: ui-prettier
name: Format UI code with Prettier
entry: bash -c 'cd llama_stack/ui && npm run format'
language: system
files: ^llama_stack/ui/.*\.(ts|tsx)$
pass_filenames: false
require_serial: true
- id: ui-eslint
name: Lint UI code with ESLint
entry: bash -c 'cd llama_stack/ui && npm run lint -- --fix --quiet'
language: system
files: ^llama_stack/ui/.*\.(ts|tsx)$
pass_filenames: false
require_serial: true

ci:
autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
Expand Down
1 change: 1 addition & 0 deletions llama_stack/ui/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22.5.1
9 changes: 9 additions & 0 deletions llama_stack/ui/.prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# Ignore artifacts:
build
coverage
.next
node_modules
dist
*.lock
*.log

# Generated files
*.min.js
*.min.css
11 changes: 10 additions & 1 deletion llama_stack/ui/.prettierrc
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
{}
{
"semi": true,
"trailingComma": "es5",
"singleQuote": false,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "avoid"
}
4 changes: 2 additions & 2 deletions llama_stack/ui/app/api/v1/[...path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -74,7 +74,7 @@ async function proxyRequest(request: NextRequest, method: string) {
backend_url: BACKEND_URL,
timestamp: new Date().toISOString(),
},
{ status: 500 },
{ status: 500 }
);
}
}
Expand Down
4 changes: 2 additions & 2 deletions llama_stack/ui/app/auth/signin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
194 changes: 110 additions & 84 deletions llama_stack/ui/app/chat-playground/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;

// Add user message to chat
const userMessage: Message = {
id: Date.now().toString(),
role: "user",
content: input.trim(),
createdAt: new Date(),
};
const handleSubmit = async (event?: { preventDefault?: () => void }) => {
event?.preventDefault?.();
if (!input.trim()) return;

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;

flushSync(() => {
setMessages(prev => {
const newMessages = [...prev];
const lastMessage = newMessages[newMessages.length - 1];
if (lastMessage.role === "assistant") {
lastMessage.content = fullContent;
}
return newMessages;
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: "",
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?",
Expand All @@ -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);
};

Expand All @@ -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>
Expand Down
4 changes: 2 additions & 2 deletions llama_stack/ui/app/logs/chat-completions/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading