mirror of https://github.com/kortix-ai/suna.git
generateThreadName server action
This commit is contained in:
parent
613c0d634b
commit
8f3376cd0e
|
@ -4,7 +4,8 @@ import React, { useState, Suspense } from 'react';
|
|||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { ChatInput } from '@/components/thread/chat-input';
|
||||
import { createProject, addUserMessage, startAgent, createThread, generateThreadName } from "@/lib/api";
|
||||
import { createProject, addUserMessage, startAgent, createThread } from "@/lib/api";
|
||||
import { generateThreadName } from "@/lib/actions/threads";
|
||||
|
||||
function DashboardContent() {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
|
|
@ -1223,43 +1223,6 @@ export function ToolCallSidePanel({
|
|||
</Button>
|
||||
</div>
|
||||
<div className="flex-1 p-4 overflow-y-auto">
|
||||
{/* Navigation Controls - Conditionally Rendered */}
|
||||
{showNavigation && (
|
||||
<div className="mb-6 pb-4 border-b">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium text-muted-foreground">
|
||||
Step {currentIndex + 1} of {totalPairs}
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onNavigate(currentIndex - 1)}
|
||||
disabled={currentIndex === 0}
|
||||
className="h-7 w-7"
|
||||
>
|
||||
<SkipBack className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onNavigate(currentIndex + 1)}
|
||||
disabled={currentIndex === totalPairs - 1}
|
||||
className="h-7 w-7"
|
||||
>
|
||||
<SkipForward className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Slider
|
||||
value={[currentIndex]} // Slider value is an array
|
||||
max={totalPairs - 1}
|
||||
step={1}
|
||||
onValueChange={(value) => onNavigate(value[0])} // onValueChange gives an array
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{content ? (
|
||||
// ---- Render Historical Pair ----
|
||||
'type' in content && content.type === 'historical' ? (
|
||||
|
@ -1367,6 +1330,43 @@ export function ToolCallSidePanel({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Navigation Controls moved to bottom, just above TodoPanel */}
|
||||
{showNavigation && (
|
||||
<div className="px-4 pt-2 pb-2 border-t">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium text-muted-foreground">
|
||||
Step {currentIndex + 1} of {totalPairs}
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onNavigate(currentIndex - 1)}
|
||||
disabled={currentIndex === 0}
|
||||
className="h-7 w-7"
|
||||
>
|
||||
<SkipBack className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onNavigate(currentIndex + 1)}
|
||||
disabled={currentIndex === totalPairs - 1}
|
||||
className="h-7 w-7"
|
||||
>
|
||||
<SkipForward className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Slider
|
||||
value={[currentIndex]} // Slider value is an array
|
||||
max={totalPairs - 1}
|
||||
step={1}
|
||||
onValueChange={(value) => onNavigate(value[0])} // onValueChange gives an array
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Todo Panel at the bottom of side panel */}
|
||||
{sandboxId && (
|
||||
<TodoPanel
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
'use server'
|
||||
|
||||
export const generateThreadName = async (message: string): Promise<string> => {
|
||||
try {
|
||||
// Default name in case the API fails
|
||||
const defaultName = message.trim().length > 50
|
||||
? message.trim().substring(0, 47) + "..."
|
||||
: message.trim();
|
||||
|
||||
// OpenAI API key should be stored in an environment variable
|
||||
const apiKey = process.env.OPENAI_API_KEY;
|
||||
|
||||
if (!apiKey) {
|
||||
console.error('OpenAI API key not found');
|
||||
return defaultName;
|
||||
}
|
||||
|
||||
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'gpt-4o-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'You are a helpful assistant that generates extremely concise titles (2-4 words maximum) for chat threads based on the user\'s message. Respond with only the title, no other text or punctuation.'
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `Generate an extremely brief title (2-4 words only) for a chat thread that starts with this message: "${message}"`
|
||||
}
|
||||
],
|
||||
max_tokens: 20,
|
||||
temperature: 0.7
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
console.error('OpenAI API error:', errorData);
|
||||
return defaultName;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const generatedName = data.choices[0]?.message?.content?.trim();
|
||||
|
||||
// Return the generated name or default if empty
|
||||
return generatedName || defaultName;
|
||||
} catch (error) {
|
||||
console.error('Error generating thread name:', error);
|
||||
// Fall back to using a truncated version of the message
|
||||
return message.trim().length > 50
|
||||
? message.trim().substring(0, 47) + "..."
|
||||
: message.trim();
|
||||
}
|
||||
};
|
|
@ -848,62 +848,4 @@ export const getSandboxFileContent = async (sandboxId: string, path: string): Pr
|
|||
console.error('Failed to get sandbox file content:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const generateThreadName = async (message: string): Promise<string> => {
|
||||
try {
|
||||
// Default name in case the API fails
|
||||
const defaultName = message.trim().length > 50
|
||||
? message.trim().substring(0, 47) + "..."
|
||||
: message.trim();
|
||||
|
||||
// OpenAI API key should be stored in an environment variable
|
||||
const apiKey = process.env.NEXT_PUBLIC_OPENAI_API_KEY;
|
||||
|
||||
if (!apiKey) {
|
||||
console.error('OpenAI API key not found');
|
||||
return defaultName;
|
||||
}
|
||||
|
||||
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'gpt-4o-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'You are a helpful assistant that generates extremely concise titles (2-4 words maximum) for chat threads based on the user\'s message. Respond with only the title, no other text or punctuation.'
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `Generate an extremely brief title (2-4 words only) for a chat thread that starts with this message: "${message}"`
|
||||
}
|
||||
],
|
||||
max_tokens: 20,
|
||||
temperature: 0.7
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
console.error('OpenAI API error:', errorData);
|
||||
return defaultName;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const generatedName = data.choices[0]?.message?.content?.trim();
|
||||
|
||||
// Return the generated name or default if empty
|
||||
return generatedName || defaultName;
|
||||
} catch (error) {
|
||||
console.error('Error generating thread name:', error);
|
||||
// Fall back to using a truncated version of the message
|
||||
return message.trim().length > 50
|
||||
? message.trim().substring(0, 47) + "..."
|
||||
: message.trim();
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue