feat: frontend

This commit is contained in:
Florian Hönicke
2023-03-20 00:34:25 +01:00
parent 45b709ed15
commit 0116449082
12 changed files with 414 additions and 198 deletions

23
main.py
View File

@@ -48,15 +48,16 @@ def get_all_executor_files_with_content():
return file_name_to_content
def main(
async def main(
executor_name,
input_executor_description,
executor_description,
input_modality,
input_doc_field,
output_modality,
output_doc_field,
input_test_in,
input_test_out
test_in,
test_out,
do_validation=True
):
recreate_folder(EXECUTOR_FOLDER)
system_definition = (
@@ -69,9 +70,9 @@ def main(
user_query = (
general_guidelines()
+ executor_file_task(executor_name, input_executor_description, input_modality, input_doc_field,
+ executor_file_task(executor_name, executor_description, input_modality, input_doc_field,
output_modality, output_doc_field)
+ test_executor_file_task(executor_name, input_test_in, input_test_out)
+ test_executor_file_task(executor_name, test_in, test_out)
+ requirements_file_task()
+ docker_file_task()
+ client_file_task()
@@ -85,9 +86,9 @@ def main(
jina_cloud.push_executor()
host = jina_cloud.deploy_flow(executor_name)
host = await jina_cloud.deploy_flow(executor_name, do_validation)
run_client_file(f'executor/{CLIENT_FILE_NAME}', host)
run_client_file(f'executor/{CLIENT_FILE_NAME}', host, do_validation)
return get_all_executor_files_with_content()
@@ -95,11 +96,11 @@ def main(
if __name__ == '__main__':
main(
executor_name='MyCoolOcrExecutor',
input_executor_description="OCR detector",
executor_description="OCR detector",
input_modality='image',
input_doc_field='uri',
output_modality='text',
output_doc_field='text',
input_test_in='https://miro.medium.com/v2/resize:fit:1024/0*4ty0Adbdg4dsVBo3.png',
input_test_out='> Hello, world!_',
test_in='https://miro.medium.com/v2/resize:fit:1024/0*4ty0Adbdg4dsVBo3.png',
test_out='> Hello, world!_',
)

View File

@@ -0,0 +1,183 @@
import React, {useState} from 'react';
import axios from 'axios';
import {Box, Button, Container, FormControl, InputLabel, MenuItem, Select, TextField, Typography,} from '@mui/material';
function App() {
const [executorName, setExecutorName] = useState('MyCoolOcrExecutor');
const [executorDescription, setExecutorDescription] = useState('OCR detector');
const [inputModality, setInputModality] = useState('image');
const [inputDocField, setInputDocField] = useState('uri');
const [outputModality, setOutputModality] = useState('text');
const [outputDocField, setOutputDocField] = useState('text');
const [testIn, settestIn] = useState('https://miro.medium.com/v2/resize:fit:1024/0*4ty0Adbdg4dsVBo3.png');
const [testOut, settestOut] = useState('> Hello, world!_');
const [responseText, setResponseText] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
const requestBody = {
executor_name: executorName,
executor_description: executorDescription,
input_modality: inputModality,
input_doc_field: inputDocField,
output_modality: outputModality,
output_doc_field: outputDocField,
test_in: testIn,
test_out: testOut,
};
try {
const response = await axios.post('http://0.0.0.0:8000/create', requestBody);
setResponseText(response.data);
} catch (error) {
console.error(error);
setResponseText('An error occurred while processing the request.');
}
};
return (
<Container maxWidth="md">
<Box sx={{my: 4}}>
<Typography variant="h4" component="h1" gutterBottom>
MicroChain
</Typography>
<Typography variant="body1" component="p" gutterBottom>
Magically create your microservice just by describing it.
</Typography>
<form onSubmit={handleSubmit}>
<Box sx={{my: 2}}>
<TextField
label="Executor Name"
value={executorName}
onChange={(e) => setExecutorName(e.target.value)}
fullWidth
/>
</Box>
<Box sx={{my: 2}}>
<TextField
label="Executor Description"
value={executorDescription}
onChange={(e) => setExecutorDescription(e.target.value)}
fullWidth
/>
</Box>
<Box sx={{my: 2}}>
<Typography variant="h6" component="h2" gutterBottom>
Input Interface
</Typography>
<FormControl fullWidth>
<InputLabel id="input-modality-label">Input Modality</InputLabel>
<Select
labelId="input-modality-label"
value={inputModality}
onChange={(e) => setInputModality(e.target.value)}
>
<MenuItem value="text">Text</MenuItem>
<MenuItem value="image">Image</MenuItem>
<MenuItem value="3d">3D</MenuItem>
<MenuItem value="audio">Audio</MenuItem>
<MenuItem value="video">Video</MenuItem>
<MenuItem value="pdf">PDF</MenuItem>
</Select>
</FormControl>
</Box>
<Box sx={{my: 2}}>
<FormControl fullWidth>
<InputLabel id="input-doc-field-label">Input Doc Field</InputLabel>
<Select
labelId="input-doc-field-label"
value={inputDocField}
onChange={(e) => setInputDocField(e.target.value)}
>
<MenuItem value="text">Text</MenuItem>
<MenuItem value="blob">Blob</MenuItem>
<MenuItem value="tensor">Tensor</MenuItem>
<MenuItem value="uri">URL</MenuItem>
</Select>
</FormControl>
</Box>
<Box sx={{my: 2}}>
<Typography variant="h6" component="h2" gutterBottom>
Output Interface
</Typography>
<FormControl fullWidth>
<InputLabel id="output-modality-label">Output Modality</InputLabel>
<Select
labelId="output-modality-label"
value={outputModality}
onChange={(e) => setOutputModality(e.target.value)}
>
<MenuItem value="text">Text</MenuItem>
<MenuItem value="image">Image</MenuItem>
<MenuItem value="3d">3D</MenuItem>
<MenuItem value="audio">Audio</MenuItem>
<MenuItem value="video">Video</MenuItem>
<MenuItem value="pdf">PDF</MenuItem>
</Select>
</FormControl>
</Box>
<Box sx={{my: 2}}>
<FormControl fullWidth>
<InputLabel id="output-doc-field-label">Output Doc Field</InputLabel>
<Select
labelId="output-doc-field-label"
value={outputDocField}
onChange={(e) => setOutputDocField(e.target.value)}
>
<MenuItem value="text">Text</MenuItem>
<MenuItem value="blob">Blob</MenuItem>
<MenuItem value="tensor">Tensor</MenuItem>
<MenuItem value="uri">URL</MenuItem>
</Select>
</FormControl>
</Box>
<Box sx={{my: 2}}>
<Typography variant="h6" component="h2" gutterBottom>
Test Parameters
</Typography>
<TextField
label="Input Test In"
value={testIn}
onChange={(e) => settestIn(e.target.value)}
fullWidth
/>
</Box>
<Box sx={{my: 2}}>
<TextField
label="Input Test Out"
value={testOut}
onChange={(e) => settestOut(e.target.value)}
fullWidth
/>
</Box>
<Box sx={{my: 2}}>
<Button type="submit" variant="contained" color="primary">
Submit
</Button>
</Box>
</form>
{responseText && (
<Box sx={{my: 4}}>
<Typography variant="h6" component="h2" gutterBottom>
Response
</Typography>
{Object.entries(responseText.result).map(([fileName, fileContent]) => (
<Box key={fileName} sx={{my: 2}}>
<Typography variant="subtitle1" gutterBottom>
{fileName}
</Typography>
<pre>{fileContent}</pre>
</Box>
))}
</Box>
)}
</Box>
</Container>
);
}
export default App;

View File

@@ -1,52 +0,0 @@
import React, { useState } from 'react';
import Microservice from './Microservice';
import MicroserviceModal from './MicroserviceModal';
import { Button, Grid } from '@material-ui/core';
const Graph = ({ onExport }) => {
const [microservices, setMicroservices] = useState([]);
const [selectedMicroservice, setSelectedMicroservice] = useState(null);
const [showModal, setShowModal] = useState(false);
const handleSave = (microservice) => {
if (selectedMicroservice) {
setMicroservices(
microservices.map((ms) =>
ms.id === selectedMicroservice.id ? microservice : ms
)
);
} else {
setMicroservices([...microservices, { ...microservice, id: Date.now() }]);
}
setSelectedMicroservice(null);
};
const handleEdit = (microservice) => {
setSelectedMicroservice(microservice);
setShowModal(true);
};
return (
<div>
<Button onClick={() => setShowModal(true)}>Add Microservice</Button>
<Button onClick={() => onExport(microservices)}>Export Graph</Button>
<Grid container spacing={2}>
{microservices.map((microservice) => (
<Grid key={microservice.id} item xs={12} sm={6} md={4}>
<div onClick={() => handleEdit(microservice)}>
<Microservice microservice={microservice} />
</div>
</Grid>
))}
</Grid>
<MicroserviceModal
open={showModal}
onClose={() => setShowModal(false)}
onSave={handleSave}
microservice={selectedMicroservice}
/>
</div>
);
};
export default Graph;

View File

@@ -1,17 +0,0 @@
import React from 'react';
import { Card, CardContent, Typography } from '@material-ui/core';
const Microservice = ({ microservice }) => {
return (
<Card>
<CardContent>
<Typography variant="h6">{microservice.name}</Typography>
<Typography>
Input: {microservice.input} | Output: {microservice.output}
</Typography>
</CardContent>
</Card>
);
};
export default Microservice;

View File

@@ -1,83 +0,0 @@
import React, { useState } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
TextField,
DialogActions,
Button,
FormControl,
InputLabel,
Select,
MenuItem
} from '@material-ui/core';
const modalities = ['image', 'audio', 'text', 'video', '3d'];
const MicroserviceModal = ({ open, onClose, onSave, microservice }) => {
const [name, setName] = useState(microservice ? microservice.name : '');
const [input, setInput] = useState(microservice ? microservice.input : '');
const [output, setOutput] = useState(microservice ? microservice.output : '');
const handleSubmit = () => {
onSave({ name, input, output });
setName('');
setInput('');
setOutput('');
onClose();
};
return (
<Dialog open={open} onClose={onClose}>
<DialogTitle>{microservice ? 'Edit' : 'Add'} Microservice</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
label="Name"
fullWidth
value={name}
onChange={(e) => setName(e.target.value)}
/>
<FormControl fullWidth margin="dense">
<InputLabel id="input-label">Input</InputLabel>
<Select
labelId="input-label"
value={input}
onChange={(e) => setInput(e.target.value)}
>
{modalities.map((modality) => (
<MenuItem key={modality} value={modality}>
{modality}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl fullWidth margin="dense">
<InputLabel id="output-label">Output</InputLabel>
<Select
labelId="output-label"
value={output}
onChange={(e) => setOutput(e.target.value)}
>
{modalities.map((modality) => (
<MenuItem key={modality} value={modality}>
{modality}
</MenuItem>
))}
</Select>
</FormControl>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary">
Cancel
</Button>
<Button onClick={handleSubmit} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
);
};
export default MicroserviceModal;

View File

@@ -0,0 +1,111 @@
import React, { useState } from 'react';
function CreateExecutorForm({ onCreateExecutor }) {
const [formData, setFormData] = useState({
executor_name: '',
executor_description: '',
input_modality: '',
input_doc_field: '',
output_modality: '',
output_doc_field: '',
test_in: '',
test_out: '',
});
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = (e) => {
e.preventDefault();
onCreateExecutor(formData);
};
return (
<form onSubmit={handleSubmit}>
<label>
Executor Name:
<input
type="text"
name="executor_name"
value={formData.executor_name}
onChange={handleChange}
required
/>
</label>
<label>
Input Executor Description:
<input
type="text"
name="executor_description"
value={formData.executor_description}
onChange={handleChange}
required
/>
</label>
<label>
Input Modality:
<input
type="text"
name="input_modality"
value={formData.input_modality}
onChange={handleChange}
required
/>
</label>
<label>
Input Doc Field:
<input
type="text"
name="input_doc_field"
value={formData.input_doc_field}
onChange={handleChange}
required
/>
</label>
<label>
Output Modality:
<input
type="text"
name="output_modality"
value={formData.output_modality}
onChange={handleChange}
required
/>
</label>
<label>
Output Doc Field:
<input
type="text"
name="output_doc_field"
value={formData.output_doc_field}
onChange={handleChange}
required
/>
</label>
<label>
Input Test URL:
<input
type="url"
name="test_in"
value={formData.test_in}
onChange={handleChange}
required
/>
</label>
<label>
Input Test Output:
<input
type="text"
name="test_out"
value={formData.test_out}
onChange={handleChange}
required
/>
</label>
<button type="submit">Create Executor</button>
</form>
);
}
export default CreateExecutorForm

View File

@@ -0,0 +1,30 @@
import React from 'react';
function ExecutorOutput({ response }) {
const { result, success, message } = response;
return (
<div>
<h2>Generated Executor Files</h2>
{success ? (
<div>
{Object.entries(result).map(([filename, content]) => (
<div key={filename}>
<h3>{filename}</h3>
<pre>
<code>{content}</code>
</pre>
</div>
))}
</div>
) : (
<div>
<h3>Error</h3>
<p>{message}</p>
</div>
)}
</div>
);
}
export default ExecutorOutput;

View File

@@ -2,3 +2,4 @@ jina[perf]==3.14.2.dev18
openai
ptest
jcloud
uvicorn

View File

@@ -1,7 +1,12 @@
from fastapi import FastAPI
from fastapi.exceptions import RequestValidationError
from pydantic import BaseModel, HttpUrl
from typing import Optional, Dict
from starlette.middleware.cors import CORSMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse
from main import main
app = FastAPI()
@@ -9,13 +14,13 @@ app = FastAPI()
# Define the request model
class CreateRequest(BaseModel):
executor_name: str
input_executor_description: str
executor_description: str
input_modality: str
input_doc_field: str
output_modality: str
output_doc_field: str
input_test_in: HttpUrl
input_test_out: str
test_in: str
test_out: str
# Define the response model
class CreateResponse(BaseModel):
@@ -25,17 +30,38 @@ class CreateResponse(BaseModel):
@app.post("/create", response_model=CreateResponse)
async def create_endpoint(request: CreateRequest):
try:
result = main(
executor_name=request.executor_name,
input_executor_description=request.input_executor_description,
input_modality=request.input_modality,
input_doc_field=request.input_doc_field,
output_modality=request.output_modality,
output_doc_field=request.output_doc_field,
input_test_in=request.input_test_in,
input_test_out=request.input_test_out,
)
return CreateResponse(result=result, success=True, message=None)
except Exception as e:
return CreateResponse(result=None, success=False, message=str(e))
result = await main(
executor_name=request.executor_name,
executor_description=request.executor_description,
input_modality=request.input_modality,
input_doc_field=request.input_doc_field,
output_modality=request.output_modality,
output_doc_field=request.output_doc_field,
test_in=request.test_in,
test_out=request.test_out,
do_validation=False
)
return CreateResponse(result=result, success=True, message=None)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Add a custom exception handler for RequestValidationError
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=422,
content={"detail": exc.errors()},
)
if __name__ == "__main__":
import uvicorn
uvicorn.run("server:app", host="0.0.0.0", port=8000, log_level="info")

View File

@@ -1,3 +1,4 @@
import asyncio
import os
from multiprocessing.connection import Client
@@ -18,7 +19,15 @@ def get_user_name():
return response['data']['name']
def deploy_flow(executor_name):
async def deploy_on_jcloud(flow_yaml):
cloud_flow = CloudFlow(path=flow_yaml)
await cloud_flow.__aenter__()
return cloud_flow.endpoints['gateway']
async def deploy_flow(executor_name, do_validation):
flow = f'''
jtype: Flow
with:
@@ -44,12 +53,14 @@ executors:
with open(full_flow_path, 'w') as f:
f.write(flow)
# try local first
flow = Flow.load_config(full_flow_path)
with flow:
pass
if do_validation:
print('try local execution')
flow = Flow.load_config(full_flow_path)
with flow:
pass
print('deploy flow on jcloud')
return await deploy_on_jcloud(flow_yaml=full_flow_path)
return CloudFlow(path=full_flow_path).__enter__().endpoints['gateway']
def replace_client_line(file_content: str, replacement: str) -> str:
lines = file_content.split('\n')
@@ -59,7 +70,7 @@ def replace_client_line(file_content: str, replacement: str) -> str:
break
return '\n'.join(lines)
def run_client_file(file_path, host):
def run_client_file(file_path, host, do_validation):
with open(file_path, 'r') as file:
content = file.read()
@@ -69,4 +80,5 @@ def run_client_file(file_path, host):
with open(file_path, 'w') as file:
file.write(replaced_content)
import executor.client # runs the client script for validation
if do_validation:
import executor.client # runs the client script for validation

View File

@@ -39,7 +39,7 @@ message DocumentProto {
string text = 4;
}
// a uri of the document could be: a local file path, a remote url starts with http or https or data URI scheme
// a uri of the document is a remote url starts with http or https or data URI scheme
string uri = 5;
// list of the sub-documents of this document (recursive structure)
@@ -65,11 +65,12 @@ d1 = Document(text='hello')
d2 = Document(blob=b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00\\x03L\\x00\\x00\\x01\\x18\\x08\\x06\\x00\\x00\\x00o...')
d3 = Document(tensor=numpy.array([1, 2, 3]), chunks=[Document(uri=/local/path/to/file)]
d4 = Document(
uri='https://docs.docarray.org',
uri='https://docs.docarray.org/img/logo.png',
tags={'foo': 'bar'},
)
d5 = Document()
d5.tensor = np.ones((2,4))
d5.uri = 'https://audio.com/audio.mp3'
d6 = Document()
d6.blob = b'RIFF\\x00\\x00\\x00\\x00WAVEfmt \\x10\\x00...'
docs = DocumentArray([

View File

@@ -7,7 +7,9 @@ def general_guidelines():
"General guidelines: "
"The code you write is production ready. "
"Every file starts with comments describing what the code is doing before the first import. "
"Then all imports are listed. It is important to import all modules that could be needed in the executor code."
"Then all imports are listed. "
"It is important to import all modules that could be needed in the executor code. "
"Always import BytesIO from io. "
"Comments can only be written between tags. "
"Start from top-level and then fully implement all methods. "
"\n"
@@ -18,13 +20,14 @@ def _task(task, tag_name, file_name):
return task + f"The code will go into {file_name}. Wrap the code in the string $$$start_{tag_name}$$$...$$$end_{tag_name}$$$ \n\n"
def executor_file_task(executor_name, input_executor_description, input_modality, input_doc_field,
def executor_file_task(executor_name, executor_description, input_modality, input_doc_field,
output_modality, output_doc_field):
return _task(
f"Write the executor called '{executor_name}'. "
f"It matches the following description: '{input_executor_description}'. "
f"It matches the following description: '{executor_description}'. "
f"It gets a DocumentArray as input where each document has the input modality '{input_modality}' that is stored in document.{input_doc_field}. "
f"It returns a DocumentArray as output where each document has the output modality '{output_modality}' that is stored in document.{output_doc_field}. ",
f"It returns a DocumentArray as output where each document has the output modality '{output_modality}' that is stored in document.{output_doc_field}. "
f"Have in mind that d.uri is never a path to a local file. It is always a url.",
'executor',
EXECUTOR_FILE_NAME
)
@@ -37,13 +40,13 @@ def requirements_file_task():
REQUIREMENTS_FILE_NAME)
def test_executor_file_task(executor_name, input_test_in, input_test_out):
def test_executor_file_task(executor_name, test_in, test_out):
return _task(
"Write a small unit test for the executor. "
"Start the test with an extensive comment about the test case. "
+ (
"Test that the executor converts the input '" + input_test_in + "' to the output '" + input_test_out + "'. "
) if input_test_in and input_test_out else ""
"Test that the executor converts the input '" + test_in + "' to the output '" + test_out + "'. "
) if test_in and test_out else ""
"Use the following import to import the executor: "
f"from executor import {executor_name} ",
'test_executor',