mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-11-06 00:54:23 +00:00
Move import instance configuration to InstanceDialog component
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -12,6 +12,7 @@ import { BackendType, type CreateInstanceOptions, type Instance } from "@/types/
|
|||||||
import ParseCommandDialog from "@/components/ParseCommandDialog";
|
import ParseCommandDialog from "@/components/ParseCommandDialog";
|
||||||
import InstanceSettingsCard from "@/components/instance/InstanceSettingsCard";
|
import InstanceSettingsCard from "@/components/instance/InstanceSettingsCard";
|
||||||
import BackendConfigurationCard from "@/components/instance/BackendConfigurationCard";
|
import BackendConfigurationCard from "@/components/instance/BackendConfigurationCard";
|
||||||
|
import { Upload } from "lucide-react";
|
||||||
|
|
||||||
interface InstanceDialogProps {
|
interface InstanceDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -32,6 +33,7 @@ const InstanceDialog: React.FC<InstanceDialogProps> = ({
|
|||||||
const [formData, setFormData] = useState<CreateInstanceOptions>({});
|
const [formData, setFormData] = useState<CreateInstanceOptions>({});
|
||||||
const [nameError, setNameError] = useState("");
|
const [nameError, setNameError] = useState("");
|
||||||
const [showParseDialog, setShowParseDialog] = useState(false);
|
const [showParseDialog, setShowParseDialog] = useState(false);
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
|
||||||
// Reset form when dialog opens/closes or when instance changes
|
// Reset form when dialog opens/closes or when instance changes
|
||||||
@@ -153,6 +155,49 @@ const InstanceDialog: React.FC<InstanceDialogProps> = ({
|
|||||||
setShowParseDialog(false);
|
setShowParseDialog(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleImportFile = () => {
|
||||||
|
fileInputRef.current?.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = event.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
try {
|
||||||
|
const content = e.target?.result as string;
|
||||||
|
const importedData = JSON.parse(content);
|
||||||
|
|
||||||
|
// Validate that it's an instance export
|
||||||
|
if (!importedData.name || !importedData.options) {
|
||||||
|
alert('Invalid instance file: Missing required fields (name, options)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the instance name (only for new instances, not editing)
|
||||||
|
if (!isEditing) {
|
||||||
|
handleNameChange(importedData.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate all the options from the imported file
|
||||||
|
if (importedData.options) {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
...importedData.options,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the file input
|
||||||
|
event.target.value = '';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse instance file:', error);
|
||||||
|
alert(`Failed to parse instance file: ${error instanceof Error ? error.message : 'Invalid JSON'}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
};
|
||||||
|
|
||||||
// Save button label logic
|
// Save button label logic
|
||||||
let saveButtonLabel = "Create Instance";
|
let saveButtonLabel = "Create Instance";
|
||||||
@@ -168,6 +213,8 @@ const InstanceDialog: React.FC<InstanceDialogProps> = ({
|
|||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="sm:max-w-[600px] max-h-[80vh] overflow-hidden flex flex-col">
|
<DialogContent className="sm:max-w-[600px] max-h-[80vh] overflow-hidden flex flex-col">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex-1">
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{isEditing ? "Edit Instance" : "Create New Instance"}
|
{isEditing ? "Edit Instance" : "Create New Instance"}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
@@ -176,6 +223,28 @@ const InstanceDialog: React.FC<InstanceDialogProps> = ({
|
|||||||
? "Modify the instance configuration below."
|
? "Modify the instance configuration below."
|
||||||
: "Configure your new llama-server instance below."}
|
: "Configure your new llama-server instance below."}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
|
</div>
|
||||||
|
{!isEditing && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleImportFile}
|
||||||
|
title="Import instance configuration from JSON file"
|
||||||
|
className="ml-2"
|
||||||
|
>
|
||||||
|
<Upload className="h-4 w-4 mr-2" />
|
||||||
|
Import
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
accept=".json"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
className="hidden"
|
||||||
|
/>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import type { CreateInstanceOptions } from '@/types/instance'
|
import type { CreateInstanceOptions } from '@/types/instance'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Label } from '@/components/ui/label'
|
import { Label } from '@/components/ui/label'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import AutoRestartConfiguration from '@/components/instance/AutoRestartConfiguration'
|
import AutoRestartConfiguration from '@/components/instance/AutoRestartConfiguration'
|
||||||
import NumberInput from '@/components/form/NumberInput'
|
import NumberInput from '@/components/form/NumberInput'
|
||||||
import CheckboxInput from '@/components/form/CheckboxInput'
|
import CheckboxInput from '@/components/form/CheckboxInput'
|
||||||
import EnvironmentVariablesInput from '@/components/form/EnvironmentVariablesInput'
|
import EnvironmentVariablesInput from '@/components/form/EnvironmentVariablesInput'
|
||||||
import SelectInput from '@/components/form/SelectInput'
|
import SelectInput from '@/components/form/SelectInput'
|
||||||
import { nodesApi, type NodesMap } from '@/lib/api'
|
import { nodesApi, type NodesMap } from '@/lib/api'
|
||||||
import { Upload } from 'lucide-react'
|
|
||||||
|
|
||||||
interface InstanceSettingsCardProps {
|
interface InstanceSettingsCardProps {
|
||||||
instanceName: string
|
instanceName: string
|
||||||
@@ -31,7 +29,6 @@ const InstanceSettingsCard: React.FC<InstanceSettingsCardProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [nodes, setNodes] = useState<NodesMap>({})
|
const [nodes, setNodes] = useState<NodesMap>({})
|
||||||
const [loadingNodes, setLoadingNodes] = useState(true)
|
const [loadingNodes, setLoadingNodes] = useState(true)
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchNodes = async () => {
|
const fetchNodes = async () => {
|
||||||
@@ -70,79 +67,12 @@ const InstanceSettingsCard: React.FC<InstanceSettingsCardProps> = ({
|
|||||||
|
|
||||||
const selectedNode = formData.nodes && formData.nodes.length > 0 ? formData.nodes[0] : ''
|
const selectedNode = formData.nodes && formData.nodes.length > 0 ? formData.nodes[0] : ''
|
||||||
|
|
||||||
const handleImportFile = () => {
|
|
||||||
fileInputRef.current?.click()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const file = event.target.files?.[0]
|
|
||||||
if (!file) return
|
|
||||||
|
|
||||||
const reader = new FileReader()
|
|
||||||
reader.onload = (e) => {
|
|
||||||
try {
|
|
||||||
const content = e.target?.result as string
|
|
||||||
const importedData = JSON.parse(content)
|
|
||||||
|
|
||||||
// Validate that it's an instance export
|
|
||||||
if (!importedData.name || !importedData.options) {
|
|
||||||
alert('Invalid instance file: Missing required fields (name, options)')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the instance name (only for new instances, not editing)
|
|
||||||
if (!isEditing) {
|
|
||||||
onNameChange(importedData.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate all the options from the imported file
|
|
||||||
if (importedData.options) {
|
|
||||||
// Set all the options fields
|
|
||||||
Object.entries(importedData.options).forEach(([key, value]) => {
|
|
||||||
onChange(key as keyof CreateInstanceOptions, value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the file input
|
|
||||||
event.target.value = ''
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to parse instance file:', error)
|
|
||||||
alert(`Failed to parse instance file: ${error instanceof Error ? error.message : 'Invalid JSON'}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.readAsText(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Instance Settings</CardTitle>
|
<CardTitle>Instance Settings</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
{/* Import Instance Section - only show when creating new instance */}
|
|
||||||
{!isEditing && (
|
|
||||||
<div className="pb-4 border-b">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
onClick={handleImportFile}
|
|
||||||
size="sm"
|
|
||||||
title="Import instance configuration from JSON file"
|
|
||||||
>
|
|
||||||
<Upload className="h-4 w-4 mr-2" />
|
|
||||||
Import
|
|
||||||
</Button>
|
|
||||||
<input
|
|
||||||
ref={fileInputRef}
|
|
||||||
type="file"
|
|
||||||
accept=".json"
|
|
||||||
onChange={handleFileChange}
|
|
||||||
className="hidden"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Instance Name */}
|
{/* Instance Name */}
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="name">
|
<Label htmlFor="name">
|
||||||
|
|||||||
Reference in New Issue
Block a user