Build modern web applications that integrate with Kubiya workflow orchestration
// Server discovery client
class ServerDiscovery {
async discoverServers(): Promise<OrchestrationServer[]> {
const serverUrls = ['http://localhost:8001', 'http://localhost:8002'];
const discovered = [];
for (const url of serverUrls) {
try {
const response = await fetch(`${url}/discover`);
const data = await response.json();
discovered.push({
id: data.server.id,
name: data.server.name,
capabilities: data.server.capabilities,
isHealthy: data.health.status === 'healthy'
});
} catch (error) {
console.warn(`Server at ${url} not available`);
}
}
return discovered;
}
}
// Chat component with workflow integration
export function ChatInterface() {
const [selectedServer, setSelectedServer] = useState('');
const [workflowMode, setWorkflowMode] = useState<'plan' | 'act'>('plan');
const { messages, input, handleSubmit } = useChat({
api: '/api/chat',
body: { selectedServer, workflowMode }
});
// Automatically detect workflow requests
const isWorkflowRequest = selectedServer && (
input.includes('workflow') ||
input.includes('deploy') ||
input.includes('create')
);
return (
<div className="chat-interface">
{/* Server selection and mode controls */}
{/* Message display with streaming */}
{/* Input with smart workflow detection */}
</div>
);
}
// Streaming workflow execution
async function executeWorkflow(serverUrl: string, prompt: string) {
const response = await fetch(`${serverUrl}/compose`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt,
mode: 'act',
conversationId: `chat-${Date.now()}`
})
});
// Stream the response
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
// Process streaming workflow updates
processStreamingUpdate(chunk);
}
}
// Next.js API route for workflow execution
export async function POST(request: Request) {
const { messages, selectedServer, workflowMode } = await request.json();
if (selectedServer) {
// Execute workflow and return streaming response
const workflowResponse = await executeWorkflow(
'http://localhost:8001',
messages[messages.length - 1].content,
workflowMode
);
return new Response(workflowResponse.body, {
headers: { 'Content-Type': 'text/event-stream' }
});
}
// Regular AI chat
return streamText({ model: openai('gpt-4'), messages });
}
<template>
<div class="workflow-interface">
<ServerSelector v-model="selectedServer" />
<ChatMessages :messages="messages" />
<WorkflowInput @submit="executeWorkflow" />
</div>
</template>
<script setup>
import { ref } from 'vue'
const selectedServer = ref('')
const messages = ref([])
async function executeWorkflow(prompt) {
if (selectedServer.value) {
// Execute workflow
const response = await fetch(`${selectedServer.value}/compose`, {
method: 'POST',
body: JSON.stringify({ prompt, mode: 'act' })
})
// Handle streaming response
const reader = response.body.getReader()
// Process stream...
}
}
</script>
@Component({
selector: 'app-workflow-chat',
template: `
<div class="workflow-interface">
<app-server-selector [(selectedServer)]="selectedServer"></app-server-selector>
<app-chat-messages [messages]="messages"></app-chat-messages>
<app-workflow-input (submit)="executeWorkflow($event)"></app-workflow-input>
</div>
`
})
export class WorkflowChatComponent {
selectedServer = '';
messages: ChatMessage[] = [];
async executeWorkflow(prompt: string) {
if (this.selectedServer) {
const response = await fetch(`${this.selectedServer}/compose`, {
method: 'POST',
body: JSON.stringify({ prompt, mode: 'act' })
});
// Handle streaming response
this.processStreamingResponse(response);
}
}
}
// Server selection component
interface ServerOption {
id: string;
name: string;
provider: string;
isHealthy: boolean;
}
export function ServerSelector({ onServerChange }: { onServerChange: (id: string) => void }) {
const [servers, setServers] = useState<ServerOption[]>([]);
const [isDiscovering, setIsDiscovering] = useState(false);
const discoverServers = async () => {
setIsDiscovering(true);
// Discovery logic...
setIsDiscovering(false);
};
return (
<div className="server-selector">
<label>Orchestration Server</label>
<select onChange={(e) => onServerChange(e.target.value)}>
<option value="">None (AI Chat Only)</option>
{servers.map(server => (
<option key={server.id} value={server.id}>
{server.name} ({server.provider}) {server.isHealthy ? '✅' : '❌'}
</option>
))}
</select>
<button onClick={discoverServers} disabled={isDiscovering}>
{isDiscovering ? 'Discovering...' : 'Refresh'}
</button>
</div>
);
}
// Mode selection component
export function WorkflowModeSelector({
mode,
onModeChange
}: {
mode: 'plan' | 'act';
onModeChange: (mode: 'plan' | 'act') => void;
}) {
return (
<div className="mode-selector">
<label>Workflow Mode</label>
<div className="toggle-group">
<button
className={mode === 'plan' ? 'active' : ''}
onClick={() => onModeChange('plan')}
>
Plan (Generate Only)
</button>
<button
className={mode === 'act' ? 'active' : ''}
onClick={() => onModeChange('act')}
>
Act (Generate & Execute)
</button>
</div>
</div>
);
}
// Streaming message component
export function StreamingMessage({ message }: { message: ChatMessage }) {
const [displayText, setDisplayText] = useState('');
useEffect(() => {
if (message.isStreaming) {
// Animate text appearance for streaming messages
let index = 0;
const interval = setInterval(() => {
setDisplayText(message.content.substring(0, index));
index++;
if (index > message.content.length) {
clearInterval(interval);
}
}, 10);
return () => clearInterval(interval);
} else {
setDisplayText(message.content);
}
}, [message]);
return (
<div className={`message ${message.role}`}>
<div className="content">
<ReactMarkdown>{displayText}</ReactMarkdown>
</div>
{message.isStreaming && <div className="streaming-indicator">●</div>}
</div>
);
}
// Secure API key handling
export class ApiKeyManager {
private apiKey: string | null = null;
setApiKey(key: string) {
this.apiKey = key;
// Store securely (consider encryption for local storage)
sessionStorage.setItem('kubiya_api_key', key);
}
getApiKey(): string | null {
return this.apiKey || sessionStorage.getItem('kubiya_api_key');
}
clearApiKey() {
this.apiKey = null;
sessionStorage.removeItem('kubiya_api_key');
}
}
// Authenticated API requests
export async function authenticatedFetch(url: string, options: RequestInit = {}) {
const apiKey = apiKeyManager.getApiKey();
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
}
// Error handling with fallbacks
export function useWorkflowExecution() {
const [error, setError] = useState<string | null>(null);
const [isRetrying, setIsRetrying] = useState(false);
const executeWorkflow = async (prompt: string, serverUrl: string) => {
try {
setError(null);
const response = await authenticatedFetch(`${serverUrl}/compose`, {
method: 'POST',
body: JSON.stringify({ prompt, mode: 'act' })
});
if (!response.ok) {
throw new Error(`Server error: ${response.status}`);
}
return response;
} catch (err) {
setError(err.message);
// Attempt retry with exponential backoff
if (!isRetrying) {
setIsRetrying(true);
setTimeout(() => {
setIsRetrying(false);
executeWorkflow(prompt, serverUrl);
}, 2000);
}
throw err;
}
};
return { executeWorkflow, error, isRetrying };
}
// Cache workflow responses
class WorkflowCache {
private cache = new Map<string, any>();
private ttl = 5 * 60 * 1000; // 5 minutes
set(key: string, value: any) {
this.cache.set(key, {
value,
timestamp: Date.now()
});
}
get(key: string): any | null {
const cached = this.cache.get(key);
if (!cached) return null;
if (Date.now() - cached.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}
return cached.value;
}
}
// Reuse connections for better performance
class ConnectionManager {
private connections = new Map<string, AbortController>();
createConnection(serverId: string): AbortController {
// Cancel existing connection
const existing = this.connections.get(serverId);
if (existing) {
existing.abort();
}
// Create new connection
const controller = new AbortController();
this.connections.set(serverId, controller);
return controller;
}
closeConnection(serverId: string) {
const controller = this.connections.get(serverId);
if (controller) {
controller.abort();
this.connections.delete(serverId);
}
}
}
// Environment-specific configuration
export const config = {
development: {
serverUrls: ['http://localhost:8001', 'http://localhost:8002'],
apiTimeout: 30000,
retryAttempts: 3
},
production: {
serverUrls: [process.env.ORCHESTRATION_SERVER_URL],
apiTimeout: 10000,
retryAttempts: 5
}
};
export const getConfig = () => {
return config[process.env.NODE_ENV as keyof typeof config] || config.development;
};
# Frontend Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
Was this page helpful?