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 return file_name_to_content
def main( async def main(
executor_name, executor_name,
input_executor_description, executor_description,
input_modality, input_modality,
input_doc_field, input_doc_field,
output_modality, output_modality,
output_doc_field, output_doc_field,
input_test_in, test_in,
input_test_out test_out,
do_validation=True
): ):
recreate_folder(EXECUTOR_FOLDER) recreate_folder(EXECUTOR_FOLDER)
system_definition = ( system_definition = (
@@ -69,9 +70,9 @@ def main(
user_query = ( user_query = (
general_guidelines() 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) 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() + requirements_file_task()
+ docker_file_task() + docker_file_task()
+ client_file_task() + client_file_task()
@@ -85,9 +86,9 @@ def main(
jina_cloud.push_executor() 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() return get_all_executor_files_with_content()
@@ -95,11 +96,11 @@ def main(
if __name__ == '__main__': if __name__ == '__main__':
main( main(
executor_name='MyCoolOcrExecutor', executor_name='MyCoolOcrExecutor',
input_executor_description="OCR detector", executor_description="OCR detector",
input_modality='image', input_modality='image',
input_doc_field='uri', input_doc_field='uri',
output_modality='text', output_modality='text',
output_doc_field='text', output_doc_field='text',
input_test_in='https://miro.medium.com/v2/resize:fit:1024/0*4ty0Adbdg4dsVBo3.png', test_in='https://miro.medium.com/v2/resize:fit:1024/0*4ty0Adbdg4dsVBo3.png',
input_test_out='> Hello, world!_', 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 openai
ptest ptest
jcloud jcloud
uvicorn

View File

@@ -1,7 +1,12 @@
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.exceptions import RequestValidationError
from pydantic import BaseModel, HttpUrl from pydantic import BaseModel, HttpUrl
from typing import Optional, Dict 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 from main import main
app = FastAPI() app = FastAPI()
@@ -9,13 +14,13 @@ app = FastAPI()
# Define the request model # Define the request model
class CreateRequest(BaseModel): class CreateRequest(BaseModel):
executor_name: str executor_name: str
input_executor_description: str executor_description: str
input_modality: str input_modality: str
input_doc_field: str input_doc_field: str
output_modality: str output_modality: str
output_doc_field: str output_doc_field: str
input_test_in: HttpUrl test_in: str
input_test_out: str test_out: str
# Define the response model # Define the response model
class CreateResponse(BaseModel): class CreateResponse(BaseModel):
@@ -25,17 +30,38 @@ class CreateResponse(BaseModel):
@app.post("/create", response_model=CreateResponse) @app.post("/create", response_model=CreateResponse)
async def create_endpoint(request: CreateRequest): async def create_endpoint(request: CreateRequest):
try:
result = main( result = await main(
executor_name=request.executor_name, executor_name=request.executor_name,
input_executor_description=request.input_executor_description, executor_description=request.executor_description,
input_modality=request.input_modality, input_modality=request.input_modality,
input_doc_field=request.input_doc_field, input_doc_field=request.input_doc_field,
output_modality=request.output_modality, output_modality=request.output_modality,
output_doc_field=request.output_doc_field, output_doc_field=request.output_doc_field,
input_test_in=request.input_test_in, test_in=request.test_in,
input_test_out=request.input_test_out, test_out=request.test_out,
do_validation=False
) )
return CreateResponse(result=result, success=True, message=None) return CreateResponse(result=result, success=True, message=None)
except Exception as e:
return CreateResponse(result=None, success=False, message=str(e))
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 import os
from multiprocessing.connection import Client from multiprocessing.connection import Client
@@ -18,7 +19,15 @@ def get_user_name():
return response['data']['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''' flow = f'''
jtype: Flow jtype: Flow
with: with:
@@ -44,12 +53,14 @@ executors:
with open(full_flow_path, 'w') as f: with open(full_flow_path, 'w') as f:
f.write(flow) f.write(flow)
# try local first if do_validation:
print('try local execution')
flow = Flow.load_config(full_flow_path) flow = Flow.load_config(full_flow_path)
with flow: with flow:
pass 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: def replace_client_line(file_content: str, replacement: str) -> str:
lines = file_content.split('\n') lines = file_content.split('\n')
@@ -59,7 +70,7 @@ def replace_client_line(file_content: str, replacement: str) -> str:
break break
return '\n'.join(lines) 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: with open(file_path, 'r') as file:
content = file.read() content = file.read()
@@ -69,4 +80,5 @@ def run_client_file(file_path, host):
with open(file_path, 'w') as file: with open(file_path, 'w') as file:
file.write(replaced_content) file.write(replaced_content)
if do_validation:
import executor.client # runs the client script for validation import executor.client # runs the client script for validation

View File

@@ -39,7 +39,7 @@ message DocumentProto {
string text = 4; 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; string uri = 5;
// list of the sub-documents of this document (recursive structure) // 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...') 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)] d3 = Document(tensor=numpy.array([1, 2, 3]), chunks=[Document(uri=/local/path/to/file)]
d4 = Document( d4 = Document(
uri='https://docs.docarray.org', uri='https://docs.docarray.org/img/logo.png',
tags={'foo': 'bar'}, tags={'foo': 'bar'},
) )
d5 = Document() d5 = Document()
d5.tensor = np.ones((2,4)) d5.tensor = np.ones((2,4))
d5.uri = 'https://audio.com/audio.mp3'
d6 = Document() d6 = Document()
d6.blob = b'RIFF\\x00\\x00\\x00\\x00WAVEfmt \\x10\\x00...' d6.blob = b'RIFF\\x00\\x00\\x00\\x00WAVEfmt \\x10\\x00...'
docs = DocumentArray([ docs = DocumentArray([

View File

@@ -7,7 +7,9 @@ def general_guidelines():
"General guidelines: " "General guidelines: "
"The code you write is production ready. " "The code you write is production ready. "
"Every file starts with comments describing what the code is doing before the first import. " "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. " "Comments can only be written between tags. "
"Start from top-level and then fully implement all methods. " "Start from top-level and then fully implement all methods. "
"\n" "\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" 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): output_modality, output_doc_field):
return _task( return _task(
f"Write the executor called '{executor_name}'. " 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 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',
EXECUTOR_FILE_NAME EXECUTOR_FILE_NAME
) )
@@ -37,13 +40,13 @@ def requirements_file_task():
REQUIREMENTS_FILE_NAME) 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( return _task(
"Write a small unit test for the executor. " "Write a small unit test for the executor. "
"Start the test with an extensive comment about the test case. " "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 + "'. " "Test that the executor converts the input '" + test_in + "' to the output '" + test_out + "'. "
) if input_test_in and input_test_out else "" ) if test_in and test_out else ""
"Use the following import to import the executor: " "Use the following import to import the executor: "
f"from executor import {executor_name} ", f"from executor import {executor_name} ",
'test_executor', 'test_executor',