Web Interface ok
This commit is contained in:
parent
86adf41308
commit
04ccd488f8
@ -148,7 +148,7 @@ class DB():
|
||||
sender=row['sender'],
|
||||
recipient=row['recipient'],
|
||||
time=int(datetime.strptime(row['time'], self._DB_TIME_FORMAT).timestamp()),
|
||||
message=AgentMessage.model_validate_json(row['json']),
|
||||
message=AgentMessage.model_validate_json(row['json'], context={"require_file_exists":False}),
|
||||
processed=row['processed']
|
||||
)
|
||||
|
||||
|
@ -18,7 +18,8 @@ from fastapi.templating import Jinja2Templates
|
||||
|
||||
from ums.management.db import DB
|
||||
|
||||
from ums.utils import AgentMessage
|
||||
from ums.utils import (AgentMessage, RiddleDataType,
|
||||
list_shared_data, list_shared_schema, SHARE_PATH)
|
||||
|
||||
class Interface():
|
||||
|
||||
@ -97,5 +98,9 @@ class Interface():
|
||||
def new(request: Request):
|
||||
return self.template.TemplateResponse(
|
||||
'new.html',
|
||||
{"request" : request, "AgentMessage" : AgentMessage}
|
||||
{"request" : request,
|
||||
"AgentMessage" : AgentMessage, "RiddleDataType": RiddleDataType,
|
||||
"shared_data" : list_shared_data(), "shared_schema" : list_shared_schema(),
|
||||
"SHARE_PATH" : SHARE_PATH
|
||||
}
|
||||
)
|
@ -22,4 +22,6 @@ from ums.utils.types import (
|
||||
|
||||
from ums.utils.const import *
|
||||
|
||||
from ums.utils.request import ManagementRequest
|
||||
from ums.utils.request import ManagementRequest
|
||||
|
||||
from ums.utils.functions import list_shared_data, list_shared_schema
|
46
ums/utils/functions.py
Normal file
46
ums/utils/functions.py
Normal file
@ -0,0 +1,46 @@
|
||||
import os
|
||||
|
||||
from typing import List, Callable
|
||||
|
||||
from ums.utils.const import SHARE_PATH
|
||||
|
||||
def list_path(path:str) -> List[str]:
|
||||
if os.path.isdir(path):
|
||||
items = []
|
||||
for item in os.listdir(path):
|
||||
full = os.path.join(path, item)
|
||||
if os.path.isdir(full):
|
||||
items.extend(list_path(full))
|
||||
elif os.path.isfile(full):
|
||||
items.append(full)
|
||||
return items
|
||||
else:
|
||||
return []
|
||||
|
||||
def list_shared(filter:Callable=lambda p,n,e: True) -> List[str]:
|
||||
files = []
|
||||
for f in list_path(SHARE_PATH):
|
||||
r = f[len(SHARE_PATH)+1:]
|
||||
|
||||
if r[0] == '.' or '/.' in r:
|
||||
# hidden files
|
||||
continue
|
||||
|
||||
if '/' in r and '.' in r:
|
||||
path, name, ending = r[:r.rfind('/')], r[r.rfind('/')+1:r.rfind('.')], r[r.rfind('.')+1:]
|
||||
elif '/' in r:
|
||||
path, name, ending = r[:r.rfind('/')], r[r.rfind('/')+1:], ""
|
||||
elif '.' in r:
|
||||
path, name, ending = "", r[:r.rfind('.')], r[r.rfind('.')+1:]
|
||||
else:
|
||||
path, name, ending = "", r, ""
|
||||
|
||||
if filter(path, name, ending):
|
||||
files.append(r)
|
||||
return files
|
||||
|
||||
def list_shared_data():
|
||||
return list_shared(lambda p,n,e: e != "json")
|
||||
|
||||
def list_shared_schema():
|
||||
return list_shared(lambda p,n,e: e == "json")
|
@ -92,11 +92,15 @@ import os
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from typing import List
|
||||
from typing import List, Any
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic.functional_validators import AfterValidator
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
ValidationError, ValidationInfo,
|
||||
ValidatorFunctionWrapHandler
|
||||
)
|
||||
from pydantic.functional_validators import WrapValidator, AfterValidator
|
||||
|
||||
from ums.utils.const import SHARE_PATH
|
||||
|
||||
@ -127,7 +131,22 @@ def _check_data_file(file_name:str) -> str:
|
||||
|
||||
return file_name
|
||||
|
||||
def _ignore_file_missing(
|
||||
v: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
|
||||
) -> str:
|
||||
try:
|
||||
return handler(v)
|
||||
except ValidationError:
|
||||
if not info.context is None and \
|
||||
"require_file_exists" in info.context and \
|
||||
info.context["require_file_exists"] == False and \
|
||||
isinstance(v, str):
|
||||
return "missing:{}".format(v)
|
||||
else:
|
||||
raise
|
||||
|
||||
class RiddleData(RiddleInformation):
|
||||
|
||||
"""
|
||||
A data item to be used to solve the riddle
|
||||
"""
|
||||
@ -137,7 +156,7 @@ class RiddleData(RiddleInformation):
|
||||
The type of the data item.
|
||||
"""
|
||||
|
||||
file_plain: Annotated[str, AfterValidator(_check_data_file)]
|
||||
file_plain: Annotated[str, AfterValidator(_check_data_file), WrapValidator(_ignore_file_missing)]
|
||||
"""
|
||||
The plain file (as path to file system) without any processing.
|
||||
|
||||
@ -145,7 +164,7 @@ class RiddleData(RiddleInformation):
|
||||
The file must exist.
|
||||
"""
|
||||
|
||||
file_extracted: Annotated[str, AfterValidator(_check_data_file)] | None = None
|
||||
file_extracted: Annotated[str, AfterValidator(_check_data_file), WrapValidator(_ignore_file_missing)] | None = None
|
||||
"""
|
||||
The processed files (as path to file system), i.e., a schematic file containing all extracted informations.
|
||||
|
||||
@ -153,6 +172,8 @@ class RiddleData(RiddleInformation):
|
||||
The file must exist.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class RiddleSolution(RiddleInformation):
|
||||
"""
|
||||
A solution of a riddle.
|
||||
|
117
web/public/static/new.js
Normal file
117
web/public/static/new.js
Normal file
@ -0,0 +1,117 @@
|
||||
var fileLast = 0;
|
||||
function renderFile(cnt, type="", file_extracted="", file_plain=""){
|
||||
var template = $("#filesTemplate").html();
|
||||
template = template.replaceAll('{cnt}', cnt)
|
||||
.replaceAll('{file_extracted}', file_extracted)
|
||||
.replaceAll('{file_extracted}', file_extracted)
|
||||
.replaceAll('{file_plain}', file_plain);
|
||||
|
||||
['text', 'image', 'audio'].forEach((t)=>{
|
||||
template = template.replaceAll('{'+t+'_selected}', t === type ? 'selected' : '' );
|
||||
});
|
||||
$("#filesRender").append(template);
|
||||
|
||||
$("button#removeFile"+cnt).click( () => {
|
||||
$("#filesRow" + cnt).remove();
|
||||
update_json_message()
|
||||
});
|
||||
|
||||
fileLast = cnt;
|
||||
}
|
||||
$("#addFile").click(() => renderFile(++fileLast));
|
||||
|
||||
function update_json_message(){
|
||||
let message = JSON.parse(basic_message);
|
||||
|
||||
var store_values = {}
|
||||
$(".message-attribute").each((_,v)=>{
|
||||
let el = $(v);
|
||||
let name = el.attr('name');
|
||||
let val = el.attr("type") == "checkbox" ? el.prop('checked') : el.val();
|
||||
|
||||
// optional fields (empty => null)
|
||||
if( name.endsWith("file_extracted") && val.length == 0 ){
|
||||
val = null;
|
||||
}
|
||||
|
||||
store_values[name] = val;
|
||||
|
||||
let curr_msg = message;
|
||||
let last_ob = {};
|
||||
let last_name = "";
|
||||
name.split('.').forEach((e)=>{
|
||||
last_ob = curr_msg;
|
||||
last_name = e;
|
||||
|
||||
if( !curr_msg.hasOwnProperty(e) ){
|
||||
curr_msg[e] = {}
|
||||
}
|
||||
|
||||
curr_msg = curr_msg[e];
|
||||
});
|
||||
last_ob[last_name] = val;
|
||||
});
|
||||
localStorage.setItem("new_riddle", JSON.stringify(store_values))
|
||||
|
||||
// fix array for data
|
||||
if(message.hasOwnProperty('data')){
|
||||
var data_items = message['data'];
|
||||
message['data'] = [];
|
||||
Object.keys(data_items).forEach((v)=>{
|
||||
if( v !== null){
|
||||
message['data'].push(data_items[v]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$("#message_content").val(JSON.stringify(message, null, 2));
|
||||
}
|
||||
function load_last_values(){
|
||||
if(localStorage.getItem("new_riddle") !== null){
|
||||
var items = JSON.parse(localStorage.getItem("new_riddle"))
|
||||
console.log(items)
|
||||
Object.keys(items).forEach((k)=>{
|
||||
|
||||
// if data, create the file select for this id
|
||||
if(k.startsWith('data.') && $("[name='"+k+"']").length == 0){
|
||||
renderFile(parseInt(k.substring(5, k.indexOf('.',5))))
|
||||
}
|
||||
|
||||
let el = $("[name='"+k+"']");
|
||||
if(el.attr("type") == "checkbox"){
|
||||
el.prop('checked', items[k]);
|
||||
}
|
||||
else{
|
||||
el.val(items[k]);
|
||||
}
|
||||
});
|
||||
|
||||
update_json_message()
|
||||
}
|
||||
}
|
||||
load_last_values();
|
||||
|
||||
$( document ).on( "change", ".message-attribute", update_json_message)
|
||||
$( document ).on( "click", ".message-attribute[type=checkbox]", update_json_message)
|
||||
|
||||
function send_json_message(json_str){
|
||||
$.ajax(
|
||||
"/message",
|
||||
{
|
||||
contentType : 'application/json',
|
||||
type : 'POST',
|
||||
data: json_str,
|
||||
},
|
||||
).then((d) => {
|
||||
$("#message_sent .modal-title").text("Message Sent");
|
||||
$("#message_sent .modal-body").html('<pre>'+JSON.stringify(d, null, 2)+'</pre>');
|
||||
new bootstrap.Modal('#message_sent').show();
|
||||
}).fail((d)=>{
|
||||
$("#message_sent .modal-title").text("Error Sending Message");
|
||||
$("#message_sent .modal-body").html('<pre>'+JSON.stringify(d, null, 2)+'</pre>');
|
||||
new bootstrap.Modal('#message_sent').show();
|
||||
});
|
||||
}
|
||||
$("#send_message").click(
|
||||
() => send_json_message($("#message_content").val())
|
||||
);
|
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
function pagination_link(name, value){
|
||||
let link_args = db_args;
|
||||
if (name && value){
|
||||
@ -35,53 +33,6 @@ if(sessionStorage.hasOwnProperty('auto_refresh') && sessionStorage.getItem('auto
|
||||
enable_auto_refresh()
|
||||
}
|
||||
|
||||
function update_json_message(){
|
||||
let message = JSON.parse(basic_message);
|
||||
|
||||
var store_values = {}
|
||||
$(".message-attribute").each((_,v)=>{
|
||||
let el = $(v);
|
||||
let name = el.attr('name');
|
||||
let val = el.attr("type") == "checkbox" ? el.prop('checked') : el.val();
|
||||
|
||||
store_values[name] = val;
|
||||
|
||||
let curr_msg = message;
|
||||
let last_ob = {};
|
||||
let last_name = "";
|
||||
name.split('.').forEach((e)=>{
|
||||
last_ob = curr_msg;
|
||||
last_name = e;
|
||||
curr_msg = curr_msg[e];
|
||||
});
|
||||
last_ob[last_name] = val;
|
||||
});
|
||||
localStorage.setItem("new_riddle", JSON.stringify(store_values))
|
||||
|
||||
$("#message_content").val(JSON.stringify(message, null, 2));
|
||||
}
|
||||
function load_last_values(){
|
||||
if(localStorage.getItem("new_riddle") !== null){
|
||||
var items = JSON.parse(localStorage.getItem("new_riddle"))
|
||||
Object.keys(items).forEach((k)=>{
|
||||
let el = $("[name='"+k+"']");
|
||||
if(el.attr("type") == "checkbox"){
|
||||
el.prop('checked', items[k]);
|
||||
}
|
||||
else{
|
||||
el.val(items[k]);
|
||||
}
|
||||
});
|
||||
|
||||
update_json_message()
|
||||
}
|
||||
}
|
||||
if(typeof basic_message != 'undefined'){
|
||||
load_last_values();
|
||||
}
|
||||
$(".message-attribute").change(update_json_message);
|
||||
$(".message-attribute[type=checkbox]").click(update_json_message);
|
||||
|
||||
function send_json_message(json_str){
|
||||
$.ajax(
|
||||
"/message",
|
||||
@ -94,12 +45,12 @@ function send_json_message(json_str){
|
||||
$("#message_sent .modal-title").text("Message Sent");
|
||||
$("#message_sent .modal-body").html('<pre>'+JSON.stringify(d, null, 2)+'</pre>');
|
||||
new bootstrap.Modal('#message_sent').show();
|
||||
}).fail((d)=>{
|
||||
$("#message_sent .modal-title").text("Error Sending Message");
|
||||
$("#message_sent .modal-body").html('<pre>'+JSON.stringify(d, null, 2)+'</pre>');
|
||||
new bootstrap.Modal('#message_sent').show();
|
||||
});
|
||||
}
|
||||
$("#send_message").click(
|
||||
() => send_json_message($("#message_content").val())
|
||||
);
|
||||
|
||||
$(".send_message_again").click(
|
||||
(e) => send_json_message( $("pre#row_message_raw_"+$(e.target).attr('idx')).text() )
|
||||
);
|
||||
);
|
@ -43,6 +43,7 @@
|
||||
{% block maincontent %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<script src="/static/main.js"></script>
|
||||
{% block morefoot %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
@ -33,6 +33,13 @@
|
||||
<textarea class="form-control message-attribute" name="riddle.context" id="message_context" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="message_context" class="form-label">Riddle Context</label>
|
||||
<textarea class="form-control message-attribute" name="riddle.context" id="message_context" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<h3>Steps</h3>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input message-attribute" name="status.extract.required" type="checkbox" id="message_extract" value="true" checked>
|
||||
@ -48,6 +55,61 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Files</h3>
|
||||
<p>
|
||||
Manually add files to the shared directory and then select them here to assign them to a riddle.
|
||||
Shared directory is <code>{{SHARE_PATH}}</code> which should be linked to the host using docker to, e.g., <code>./data/share</code>.
|
||||
</p>
|
||||
|
||||
<datalist id="dataFiles">
|
||||
{% for f in shared_data %}
|
||||
<option value="{{f}}">
|
||||
{% endfor %}
|
||||
</datalist>
|
||||
<datalist id="schemaFiles">
|
||||
{% for f in shared_schema %}
|
||||
<option value="{{f}}">
|
||||
{% endfor %}
|
||||
</datalist>
|
||||
|
||||
<template id="filesTemplate">
|
||||
<div class="mb-3 row" id="filesRow{cnt}">
|
||||
<div class="col col-sm-1">
|
||||
{cnt}
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="fileType{cnt}" class="form-label">File Type</label>
|
||||
<select id="fileType{cnt}" name="data.{cnt}.type" class="form-select message-attribute">
|
||||
<option>Select Type</option>
|
||||
{% for t in RiddleDataType %}
|
||||
<option value="{{t.value}}" { {{- t.value -}} _selected}>{{t.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="inputFile{cnt}" class="form-label">Input File</label>
|
||||
<input class="form-control message-attribute" name="data.{cnt}.file_plain"
|
||||
list="dataFiles" id="inputFile{cnt}" value="{file_plain}" placeholder="Type to search files">
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="schemaFile{cnt}" class="form-label">Schema File</label>
|
||||
<input class="form-control message-attribute" name="data.{cnt}.file_extracted"
|
||||
list="schemaFiles" id="schemaFile{cnt}" value="{file_extracted}" placeholder="Type to search files">
|
||||
</div>
|
||||
<div class="col col-sm-1">
|
||||
<button type="button" id="removeFile{cnt}" class="btn btn-danger">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div id="filesRender">
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<div class="col col-sm-1">
|
||||
<button type="button" id="addFile" class="btn btn-secondary">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>JSON Representation</h2>
|
||||
|
||||
<div class="mb-3">
|
||||
@ -67,4 +129,7 @@
|
||||
<script>
|
||||
const basic_message = '{{ AgentMessage(id="",riddle={"context":"","question":""}).model_dump_json()|safe }}';
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block morefoot %}
|
||||
<script src="/static/new.js"></script>
|
||||
{% endblock %}
|
@ -138,4 +138,7 @@
|
||||
const db_args = JSON.parse('{{ db_args|tojson }}');
|
||||
const db_total = {{ db.len(**db_args) }};
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block morefoot %}
|
||||
<script src="/static/table.js"></script>
|
||||
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user