+ half of ai prompt data source selection view, task #95117.
This commit is contained in:
@@ -32,3 +32,8 @@ foreach($config->ai->openai->contentTypeMapping as $contentType => $apis)
|
||||
|
||||
$config->ai->createprompt = new stdclass();
|
||||
$config->ai->createprompt->requiredFields = 'name';
|
||||
|
||||
$config->ai->dataSource = array();
|
||||
$config->ai->dataSource['story']['story'] = array('title', 'spec', 'verify', 'product', 'module', 'pri', 'category', 'estimate');
|
||||
$config->ai->dataSource['execution']['execution'] = array('name', 'desc', 'estimate');
|
||||
$config->ai->dataSource['execution']['tasks'] = array('name', 'pri', 'status', 'estimate', 'consumed', 'left', 'progress', 'estStarted', 'realStarted', 'finishedDate', 'closedReason');
|
||||
|
||||
@@ -177,6 +177,16 @@ class ai extends control
|
||||
*/
|
||||
public function promptSelectDataSource($promptID)
|
||||
{
|
||||
$prompt = $this->ai->getPromptByID($promptID);
|
||||
|
||||
if($_POST)
|
||||
{
|
||||
// TODO: do stuff.
|
||||
}
|
||||
|
||||
$this->view->activeMenu = 'story';
|
||||
$this->view->dataSource = $this->config->ai->dataSource;
|
||||
$this->view->prompt = $prompt;
|
||||
$this->view->promptID = $promptID;
|
||||
$this->view->title = $this->lang->ai->prompts->selectDataSource . " {$this->lang->colon} " . $this->lang->ai->prompts->common;
|
||||
$this->view->position[] = $this->lang->ai->prompts->common;
|
||||
|
||||
@@ -30,12 +30,14 @@ $lang->ai->prompts->createdDate = '创建时间';
|
||||
$lang->ai->prompts->targetForm = '表单';
|
||||
$lang->ai->prompts->funcDesc = '功能描述';
|
||||
|
||||
/* Steps of prompt creation. */
|
||||
$lang->ai->prompts->assignRole = '指定角色';
|
||||
$lang->ai->prompts->selectDataSource = '选择对象';
|
||||
$lang->ai->prompts->setPurpose = '确认操作';
|
||||
$lang->ai->prompts->setTargetForm = '结果处理';
|
||||
$lang->ai->prompts->finalize = '准备发布';
|
||||
|
||||
/* Role assigning. */
|
||||
$lang->ai->prompts->assignModel = '指定语言模型';
|
||||
$lang->ai->prompts->model = '语言模型';
|
||||
$lang->ai->prompts->role = '角色';
|
||||
@@ -43,6 +45,46 @@ $lang->ai->prompts->characterization = '角色描述';
|
||||
$lang->ai->prompts->rolePlaceholder = '“你来扮演 <一个什么角色>”';
|
||||
$lang->ai->prompts->charPlaceholder = '该角色的具体描述信息';
|
||||
|
||||
/* Data source selecting. */
|
||||
$lang->ai->prompts->selectData = '选择数据';
|
||||
$lang->ai->prompts->selectDataTip = '选择对象后,此处会展示已选对象的数据。';
|
||||
$lang->ai->prompts->selectedFormat = '已选对象为%s,已选 %d 条数据';
|
||||
$lang->ai->prompts->nonSelected = '暂无所选数据。';
|
||||
$lang->ai->prompts->sortTip = '可根据重要性给数据字段排序。';
|
||||
|
||||
/* Data source definition. */
|
||||
$lang->ai->dataSource = array();
|
||||
$lang->ai->dataSource['story']['common'] = '需求';
|
||||
$lang->ai->dataSource['execution']['common'] = '执行';
|
||||
|
||||
$lang->ai->dataSource['story']['story']['common'] = '需求';
|
||||
$lang->ai->dataSource['story']['story']['title'] = '需求标题';
|
||||
$lang->ai->dataSource['story']['story']['spec'] = '需求描述';
|
||||
$lang->ai->dataSource['story']['story']['verify'] = '验收标准';
|
||||
$lang->ai->dataSource['story']['story']['product'] = '产品';
|
||||
$lang->ai->dataSource['story']['story']['module'] = '模块';
|
||||
$lang->ai->dataSource['story']['story']['pri'] = '优先级';
|
||||
$lang->ai->dataSource['story']['story']['category'] = '需求类型';
|
||||
$lang->ai->dataSource['story']['story']['estimate'] = '预计工时';
|
||||
|
||||
$lang->ai->dataSource['execution']['execution']['common'] = '执行';
|
||||
$lang->ai->dataSource['execution']['execution']['name'] = '执行名称';
|
||||
$lang->ai->dataSource['execution']['execution']['desc'] = '执行描述';
|
||||
$lang->ai->dataSource['execution']['execution']['estimate'] = '预计工时';
|
||||
|
||||
$lang->ai->dataSource['execution']['tasks']['common'] = '任务列表';
|
||||
$lang->ai->dataSource['execution']['tasks']['name'] = '任务名称';
|
||||
$lang->ai->dataSource['execution']['tasks']['pri'] = '优先级';
|
||||
$lang->ai->dataSource['execution']['tasks']['status'] = '状态';
|
||||
$lang->ai->dataSource['execution']['tasks']['estimate'] = '预计工时';
|
||||
$lang->ai->dataSource['execution']['tasks']['consumed'] = '已消耗';
|
||||
$lang->ai->dataSource['execution']['tasks']['left'] = '剩余';
|
||||
$lang->ai->dataSource['execution']['tasks']['progress'] = '进度';
|
||||
$lang->ai->dataSource['execution']['tasks']['estStarted'] = '预计开始';
|
||||
$lang->ai->dataSource['execution']['tasks']['realStarted'] = '实际开始';
|
||||
$lang->ai->dataSource['execution']['tasks']['finishedDate']= '完成日期';
|
||||
$lang->ai->dataSource['execution']['tasks']['closedReason']= '关闭原因';
|
||||
|
||||
$lang->ai->prompts->statuses = array();
|
||||
$lang->ai->prompts->statuses[''] = '全部';
|
||||
$lang->ai->prompts->statuses['draft'] = '草稿';
|
||||
|
||||
@@ -10,4 +10,307 @@
|
||||
*/
|
||||
?>
|
||||
<?php include '../../common/view/header.html.php';?>
|
||||
|
||||
<?php js::set('dataSource', $dataSource);?>
|
||||
<?php js::set('dataSourceLang', $lang->ai->dataSource);?>
|
||||
|
||||
<style>
|
||||
.center-wrapper {display: flex; justify-content: center; height: 100%;}
|
||||
.center-content {width: 100%; height: 100%; display: flex; flex-direction: column;}
|
||||
#data-selector {display: flex; flex-direction: row; height: 100%; margin-bottom: 16px;}
|
||||
#data-sources {flex-grow: 2; display: flex; flex-direction: row; margin-right: 16px;}
|
||||
#data-selected {flex-grow: 1; display: flex; flex-direction: column;}
|
||||
.heading > * {display: inline-block; padding: 4px 0;}
|
||||
#data-category-select {flex-grow: 1; background-color: #fafafa; border: 1px solid #eee; border-right: none;}
|
||||
#data-category-select > ul {padding: 16px; margin: 0; list-style: none;}
|
||||
#data-category-select > ul > li > a.active {font-weight: bold;}
|
||||
#data-categories {flex-grow: 1; display: flex; flex-direction: column;}
|
||||
#data-properties {flex-grow: 3; display: flex; flex-direction: column;}
|
||||
#data-properties > .heading {padding: 4px 20px;}
|
||||
#data-properties > .heading > h4 {margin: 6px 0;}
|
||||
#data-property-select {flex-grow: 1; border: 1px solid #eee; border-left: none;}
|
||||
#data-property-selector {height: 100%; width: 100%;}
|
||||
#data-selected-items {flex-grow: 1; border: 1px solid #eee;}
|
||||
.obj-view {display: flex; flex-direction: column; padding: 20px;}
|
||||
.obj-view:not(:first-of-type) {border-top: 1px solid #eee;}
|
||||
.obj-view-body {display: grid; padding: 4px 20px; grid-template-columns: repeat(4, 1fr);}
|
||||
.obj-view-header.checkbox-inline, .obj-view-item.checkbox-inline {margin: 4px; cursor: unset;}
|
||||
.obj-view-item.checkbox-inline+.obj-view-item.checkbox-inline {margin-left: 4px; margin-top: 4px;}
|
||||
.checkbox-inline > label, .checkbox-inline > input {cursor: pointer;}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/* Store and sync value of datasource input, change by checkbox changes. */
|
||||
class DataSourceStore
|
||||
{
|
||||
value = {};
|
||||
listeners = [];
|
||||
|
||||
/* Get store value. */
|
||||
get value()
|
||||
{
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/* Set store value. */
|
||||
set value(value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/* Reset store value. */
|
||||
reset()
|
||||
{
|
||||
this.value = {};
|
||||
}
|
||||
|
||||
/* Sync value from form checkboxes. */
|
||||
sync()
|
||||
{
|
||||
const dataSources = document.querySelectorAll('#data-property-selector');
|
||||
const value = {};
|
||||
dataSources.forEach(dataSource =>
|
||||
{
|
||||
const group = dataSource.getAttribute('object-group');
|
||||
const items = dataSource.querySelectorAll('input[type="checkbox"]');
|
||||
items.forEach(item =>
|
||||
{
|
||||
if(item.checked) value[item.getAttribute('prop')] = true;
|
||||
});
|
||||
});
|
||||
this.value = value;
|
||||
document.querySelector('#datasource').value = JSON.stringify(this.value);
|
||||
|
||||
/* Notify listeners. */
|
||||
this.listeners.forEach(l => (l(this.value)));
|
||||
}
|
||||
|
||||
/* Subscribe for store value changes. */
|
||||
subscribe(listener)
|
||||
{
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
/* Unsubscribe for store value changes. */
|
||||
unsubscribe(listener)
|
||||
{
|
||||
this.listeners = this.listeners.filter(l => l !== listener);
|
||||
}
|
||||
}
|
||||
window.dataSourceStore = new DataSourceStore(); // Create store instance.
|
||||
|
||||
/* Data property selector component, declared as custom element. */
|
||||
class DataPropertySelector extends HTMLDivElement
|
||||
{
|
||||
/* Render with given options. */
|
||||
render(options)
|
||||
{
|
||||
this.innerHTML = '';
|
||||
|
||||
/* Get options. */
|
||||
const {group} = options;
|
||||
|
||||
/* Get data source. */
|
||||
const dataSource = window.dataSource[group];
|
||||
const dataSourceLang = window.dataSourceLang[group];
|
||||
|
||||
/* Render data source. */
|
||||
for(const [source, props] of Object.entries(dataSource))
|
||||
{
|
||||
const objView = document.createElement('div');
|
||||
objView.classList.add('obj-view');
|
||||
|
||||
const headerItem = new DataPropertyCheckbox();
|
||||
headerItem.classList.add('obj-view-header');
|
||||
headerItem.setAttribute('source', source);
|
||||
headerItem.setAttribute('prop', group);
|
||||
objView.appendChild(headerItem);
|
||||
|
||||
const objViewBody = document.createElement('div');
|
||||
objViewBody.classList.add('obj-view-body');
|
||||
props.forEach(prop =>
|
||||
{
|
||||
const item = new DataPropertyCheckbox();
|
||||
item.classList.add('obj-view-item');
|
||||
item.setAttribute('source', source);
|
||||
item.setAttribute('group', group);
|
||||
item.setAttribute('prop', prop);
|
||||
objViewBody.appendChild(item);
|
||||
});
|
||||
objView.appendChild(objViewBody);
|
||||
|
||||
this.appendChild(objView);
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle attr change, rerender. */
|
||||
attributeChangedCallback(name, oldValue, newValue)
|
||||
{
|
||||
if(name === 'object-group' && oldValue !== newValue) this.render({group: newValue});
|
||||
}
|
||||
|
||||
/* Define observed attr. */
|
||||
static get observedAttributes()
|
||||
{
|
||||
return ['object-group'];
|
||||
}
|
||||
}
|
||||
customElements.define('data-property-selector', DataPropertySelector, {extends: 'div'});
|
||||
|
||||
/*
|
||||
* Checkbox with label for data selection, declared as custom element.
|
||||
*
|
||||
* @prop {string} group - Data source group name (selected from left sidebar).
|
||||
* @prop {string} source - Data source category (sub-group).
|
||||
* @prop {string} prop - Data source property name.
|
||||
*/
|
||||
class DataPropertyCheckbox extends HTMLDivElement
|
||||
{
|
||||
/* Render checkbox with label, listen for events. */
|
||||
render()
|
||||
{
|
||||
this.innerHTML = '';
|
||||
|
||||
const prop = this.getAttribute('prop');
|
||||
const source = this.getAttribute('source');
|
||||
const group = this.getAttribute('group');
|
||||
|
||||
this.classList.add('checkbox-inline');
|
||||
const itemOption = document.createElement('input');
|
||||
itemOption.setAttribute('type', 'checkbox');
|
||||
itemOption.setAttribute('prop', !!group ? `${source}.${prop}` : source);
|
||||
this.appendChild(itemOption);
|
||||
const itemLabel = document.createElement('label');
|
||||
itemLabel.appendChild(document.createTextNode(!!group ? dataSourceLang[group][source][prop] : dataSourceLang[prop][source].common));
|
||||
|
||||
/* Setup event listeners. */
|
||||
itemLabel.addEventListener('click', (e =>
|
||||
{
|
||||
itemOption.checked = !itemOption.checked;
|
||||
this.handleCheck(itemOption.checked);
|
||||
}).bind(this));
|
||||
itemOption.addEventListener('change', (e =>
|
||||
{
|
||||
this.handleCheck(itemOption.checked);
|
||||
}).bind(this));
|
||||
|
||||
this.appendChild(itemLabel);
|
||||
}
|
||||
|
||||
/* Handle check all for checkbox group, bidirectionally. And push selections into datasource input. */
|
||||
handleCheck(newValue)
|
||||
{
|
||||
if(this.classList.contains('obj-view-header'))
|
||||
{
|
||||
const objViewBody = this.parentElement.querySelector('.obj-view-body');
|
||||
for(const item of objViewBody.querySelectorAll('.obj-view-item input[type="checkbox"]')) item.checked = newValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
const objViewBody = this.parentElement;
|
||||
const headerItem = objViewBody.parentElement.querySelector('.obj-view-header input[type="checkbox"]');
|
||||
const groupItems = objViewBody.querySelectorAll('.obj-view-item input[type="checkbox"]');
|
||||
let allChecked = true;
|
||||
for(const item of groupItems)
|
||||
{
|
||||
if(!item.checked) allChecked = false;
|
||||
}
|
||||
headerItem.checked = allChecked;
|
||||
}
|
||||
window.dataSourceStore.sync();
|
||||
}
|
||||
|
||||
/* Handle attr change, rerender. */
|
||||
attributeChangedCallback()
|
||||
{
|
||||
/* Skip rendering if props not complete. */
|
||||
if(!this.hasAttribute('prop') || !this.hasAttribute('source')) return;
|
||||
this.render();
|
||||
}
|
||||
|
||||
/* Define observed attr. */
|
||||
static get observedAttributes()
|
||||
{
|
||||
return ['group', 'source', 'prop'];
|
||||
}
|
||||
}
|
||||
customElements.define('data-property-checkbox', DataPropertyCheckbox, {extends: 'div'});
|
||||
</script>
|
||||
|
||||
<div id='mainMenu' class='clearfix' style='display: flex; flex-direction: row;'>
|
||||
<?php echo html::backButton("<i class='icon icon-back icon-sm'></i> $lang->goback", '', 'btn btn-info');?>
|
||||
<?php include 'promptdesignprogressbar.html.php';?>
|
||||
<?php echo html::submitButton("<i class='icon icon-save icon-sm'></i> $lang->save", '', 'btn btn-primary');?>
|
||||
</div>
|
||||
<div id='mainContent' class='main-content' style='height: calc(100vh - 120px);'>
|
||||
<form class='load-indicator main-form form-ajax' method='post' style='height: 100%;'>
|
||||
<div class='center-wrapper'>
|
||||
<div class='center-content'>
|
||||
<div id='data-selector'>
|
||||
<div id='data-sources'>
|
||||
<div id='data-categories'>
|
||||
<div class='heading text-center'>
|
||||
<h4><?php echo $lang->ai->prompts->selectDataSource;?></h4>
|
||||
</div>
|
||||
<div id='data-category-select'>
|
||||
<ul>
|
||||
<?php foreach(array_keys($dataSource) as $index => $objectGroupKey):?>
|
||||
<li data-group-key='<?php echo $objectGroupKey;?>'>
|
||||
<a class='btn btn-block <?php echo $index == 0 ? ' active btn-info' : ' btn-link';?>'>
|
||||
<?php echo $lang->ai->dataSource[$objectGroupKey]['common'];?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach;?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id='data-properties'>
|
||||
<div class='heading'>
|
||||
<h4><?php echo $lang->ai->prompts->selectData;?></h4>
|
||||
<small class='text-gray'><?php echo $lang->ai->prompts->selectDataTip;?></small>
|
||||
</div>
|
||||
<div id='data-property-select'>
|
||||
<div id='data-property-selector' is='data-property-selector' object-group='story'></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id='data-selected'>
|
||||
<div class='heading'>
|
||||
<h4><?php echo $lang->ai->prompts->selectedFormat;?></h4>
|
||||
</div>
|
||||
<div id='data-selected-items'>
|
||||
<?php echo html::hidden('datasource');?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style='display: flex; flex-grow: 1; flex-direction: column-reverse;'>
|
||||
<div style='display: flex; justify-content: center;'><?php echo html::submitButton($lang->ai->nextStep, 'disabled');?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
$(function()
|
||||
{
|
||||
/* Handle category switching. */
|
||||
$('#data-category-select > ul > li > a').click(function()
|
||||
{
|
||||
if($(this).hasClass('active')) return;
|
||||
|
||||
$('#data-category-select > ul > li > a').removeClass('active btn-info').addClass('btn-link');
|
||||
$(this).addClass('active btn-info').removeClass('btn-link');
|
||||
$('#data-property-selector').attr('object-group', $(this).parent().attr('data-group-key'));
|
||||
|
||||
/* Reset selected data. */
|
||||
window.dataSourceStore.reset();
|
||||
});
|
||||
|
||||
/* Disable checkboxes to prevent them getting posted as form data. */
|
||||
$('form').submit(function()
|
||||
{
|
||||
$('#data-property-selector input[type="checkbox"]').each(function() {$(this).attr('disabled', 'disabled');});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php include '../../common/view/footer.html.php';?>
|
||||
|
||||
Reference in New Issue
Block a user