dsh-form-fill-agent 0.2.14 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -2
- package/lib/brand.css +7 -0
- package/lib/client.js +1 -0
- package/lib/http.js +1 -0
- package/lib/ui.html +35 -22
- package/lib/workflow.js +1 -1
- package/package.json +38 -7
package/README.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# AI填表智能体
|
|
2
2
|
|
|
3
|
+
## 安装与三分钟上手
|
|
4
|
+
|
|
5
|
+
AI填表智能体:支持自动填表、表格填充、表格补全、Excel填表与 Excel回填;使用企查查 MCP 填写 XLSX 空白字段,预览确认后导出新副本。
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
dsh plugin --profile web add dsh-form-fill-agent@0.2.16
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
请先满足下文的 DSH、连接器及侧边栏依赖要求;安装后完整停止并重启对应 Profile。
|
|
12
|
+
|
|
13
|
+
打开“AI填表”并展开合成演示,选择客户台账模板,检查工作表、企业主体和空白字段。需要真实补全时确认数据来源及范围并手动发送指令;核对单元格预览后下载新 XLSX 副本。
|
|
14
|
+
|
|
15
|
+
**流程样例(示意,非真实调用结果):** 合成客户台账的空白字段 → 字段映射和主体核验 → 单元格级事实/来源预览 → 人工确认 → 新 XLSX 与变更清单。
|
|
16
|
+
|
|
17
|
+
**能力边界:** 仅支持当前文件安全规则允许的 XLSX;不支持 Word、宏、图片和复杂布局。默认只填空白,不用模型猜值;查询使用用户已授权 MCP 账号额度。
|
|
18
|
+
|
|
19
|
+
**升级与回滚:** 升级前停止 Profile 并备份任务目录,记录当前精确版本;使用上面的固定版本命令升级,再完整重启。回滚时将版本号替换为升级前记录的版本,并使用升级前任务目录副本;不以旧版直接读取已迁移任务目录。
|
|
20
|
+
|
|
21
|
+
相关智能体:[数据清洗补全](https://github.com/duhu2000/dsh-data-cleaning-agent) · [AI填表](https://github.com/duhu2000/dsh-form-fill-agent) · [访前尽调](https://github.com/duhu2000/dsh-pre-duediligence) · [招投标](https://github.com/duhu2000/dsh-tender-workbench)
|
|
22
|
+
|
|
23
|
+
|
|
3
24
|
[](https://www.npmjs.com/package/dsh-form-fill-agent)
|
|
4
25
|
[](https://github.com/duhu2000/dsh-form-fill-agent/releases)
|
|
5
26
|
|
|
@@ -43,7 +64,7 @@ dsh plugin --profile web add dsh-form-fill-agent@latest
|
|
|
43
64
|
|
|
44
65
|
## 使用边界与数据保存
|
|
45
66
|
|
|
46
|
-
补充要求为模型可读文本;字段或工作表范围请通过向导/工作台修改,不能仅改文字后假定执行范围已同步。任务 schema 5 可读取 schema 1–5;回退旧版本请使用升级前目录副本或独立任务目录。主包为 0.2.
|
|
67
|
+
补充要求为模型可读文本;字段或工作表范围请通过向导/工作台修改,不能仅改文字后假定执行范围已同步。任务 schema 5 可读取 schema 1–5;回退旧版本请使用升级前目录副本或独立任务目录。主包为 0.2.16,共享内核为 0.2.10,Provider 为 0.2.1,共享字段契约为 0.1.0。
|
|
47
68
|
|
|
48
69
|
支持企业完整登记名称或 18 位信用代码;简称、主体不一致或多候选不会自动猜选。仅有信用代码的表格也可作为查询锚点。连接候选检索工具时可检索简称,仍需人工确认;缺少工具时降级人工输入。字段映射采用确定性规则及人工确认,不让模型编造事实。支持普通内部公式、固定区域下拉、基础条件格式和普通表格对象;复杂 Excel 结构和 Word 暂不支持,保真边界见下方安全说明。
|
|
49
70
|
|
|
@@ -51,7 +72,7 @@ dsh plugin --profile web add dsh-form-fill-agent@latest
|
|
|
51
72
|
|
|
52
73
|
显式设置 DSH_HOME 时,任务保存至其 form-fill-tasks 目录,默认 24 小时过期;未设置时使用内存。任务保存输入表格和归一化预览,不持久化原始 MCP 响应,可主动删除任务。模型及 MCP 凭据由宿主管理。
|
|
53
74
|
|
|
54
|
-
0.2.
|
|
75
|
+
0.2.16 保存任务 schema 5,兼容读取旧 schema 1–4;回退旧版本须使用升级前任务目录副本或新目录,防止丢失已确认的字段/主体设置。
|
|
55
76
|
|
|
56
77
|
## 文档与反馈
|
|
57
78
|
|
package/lib/brand.css
CHANGED
|
@@ -49,3 +49,10 @@
|
|
|
49
49
|
@media(max-width:640px){.ff-ui .ff-wizard-nav{grid-template-columns:repeat(2,minmax(0,1fr));padding:8px 14px;gap:6px}.ff-ui .ff-wizard-nav button{min-height:44px;padding:6px 8px}.ff-ui #wizard .dialog-head{padding:16px 14px 8px}.ff-ui #wizard .dialog-head h2{font-size:20px}.ff-ui #wizard .dialog-head p{font-size:13px}.ff-ui #wizard .dialog-body{padding:14px}.ff-ui #wizard .dialog-footer{padding:12px 14px}}
|
|
50
50
|
.ff-ui .ff-next-action{position:fixed;bottom:0;left:0;right:0;z-index:20;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 20px;background:var(--surface);border-top:1px solid var(--line);box-shadow:0 -3px 12px #0001}.ff-ui .ff-next-action[hidden]{display:none}.ff-ui main{padding-bottom:100px}.ff-ui #direct-ack-label{display:block;padding:14px;margin:16px 0;background:var(--soft);border:1px solid var(--action);border-radius:8px}.ff-ui #direct-ack-label[hidden]{display:none}.ff-ui #wizard-open{display:block;min-height:44px;margin-top:12px}.ff-ui #scope-omissions-list{max-height:180px;overflow:auto;overflow-wrap:anywhere}.ff-ui .ff-task-more{margin:20px;color:var(--muted)}@media(max-width:480px){.ff-ui .ff-next-action{flex-direction:column;align-items:stretch;padding:10px 14px}.ff-ui .ff-next-action span{font-size:12px}}
|
|
51
51
|
.ff-ui #anchor-alert:not(:empty){padding:16px;border:1px solid var(--error);border-left:5px solid var(--error);border-radius:8px;background:color-mix(in srgb,var(--error) 10%,var(--surface));color:var(--error);font-weight:600;overflow-wrap:anywhere}.ff-ui .ff-mapping-conflict{border:2px solid var(--error);border-radius:8px;padding:12px;background:color-mix(in srgb,var(--error) 6%,var(--surface))}.ff-ui .ff-mapping-conflict .ff-mapping-status{color:var(--error);font-weight:600}
|
|
52
|
+
.ff-ui .ff-mapping-status{padding:8px 12px;border-radius:6px;overflow-wrap:anywhere}
|
|
53
|
+
.ff-ui .ff-mapping-status[data-state=automatic],.ff-ui .ff-mapping-status[data-state=confirmed]{color:var(--success);background:var(--success-bg)}
|
|
54
|
+
.ff-ui .ff-mapping-status[data-state=pending]{color:var(--review);background:var(--review-bg)}
|
|
55
|
+
.ff-ui .ff-mapping-status[data-state=unmatched]{color:var(--error);background:var(--error-bg)}
|
|
56
|
+
.ff-ui .ff-mapping-status[data-state=skipped]{color:var(--muted);background:var(--surface)}
|
|
57
|
+
.ff-ui .ff-mapping-role{margin-top:8px;max-width:100%}
|
|
58
|
+
.ff-ui .ff-mapping-row.ff-mapping-conflict{border-color:var(--review);background:var(--review-bg)}
|
package/lib/client.js
CHANGED
|
@@ -135,6 +135,7 @@ window.__ModuleLoader__.load({
|
|
|
135
135
|
React.useEffect(()=>{
|
|
136
136
|
const receive=async event=>{
|
|
137
137
|
if(event.origin!==location.origin||event.source!==frame.current?.contentWindow||current()!==view?.id)return;
|
|
138
|
+
if(event.data?.type==='ff-capabilities'){frame.current.contentWindow.postMessage({type:'ff-capabilities-result',mappingDraft:2},location.origin);return}
|
|
138
139
|
if(event.data?.type==='ff-wizard-state'){setModal(event.data.open===true);if(!event.data.open&&event.data.focusComposer)setTimeout(()=>document.querySelector('[data-composer-card] textarea, [data-composer-card] [contenteditable=true]')?.focus(),0);return}
|
|
139
140
|
if(event.data?.type==='ff-task'&&/^[a-f0-9-]{36}$/.test(event.data.taskId)){sessionTasks.set(view.id,event.data.taskId);return}
|
|
140
141
|
if(event.data?.type!=='ff-draft')return;
|
package/lib/http.js
CHANGED
|
@@ -18,6 +18,7 @@ export function createFormFillHandler({ basePath = '', getPort, now = Date.now,
|
|
|
18
18
|
const visible = (task, owner) => task && (!task.owner || task.owner === owner);
|
|
19
19
|
const metadata = (id, task) => ({ id, filename: task.filename ?? '未命名表格.xlsx', revision: task.revision ?? 1, state: task.state ?? (task.result ? 'completed' : 'preview_ready'), created: task.created, updatedAt: task.updatedAt ?? task.created, expiresAt: task.created + ttlMs, sessionId: task.sessionId, confirmed: !!task.result });
|
|
20
20
|
const settings = task => ({
|
|
21
|
+
mappingProtocol: 2,
|
|
21
22
|
diagnostics: diagnostics(task.preview),
|
|
22
23
|
configuration: task.configuration ?? {},
|
|
23
24
|
selectedFields: task.selectedFields,
|
package/lib/ui.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<div class="controls"><input type="file" id="file" accept=".xlsx" aria-label="选择 XLSX 文件"><span class="muted">选择文件后自动解析,核对结果后设置填写字段。</span></div>
|
|
16
16
|
<details><summary>体验合成模板</summary><p>下面模板只使用合成数据,不查询真实企业。</p><div class="controls" id="samples"><button class="secondary" data-name="客户台账">客户台账</button><button class="secondary" data-name="供应商准入表">供应商准入表</button><button class="secondary" data-name="合同主体信息表">合同主体信息表</button></div></details></div>
|
|
17
17
|
<section id="preview" hidden>
|
|
18
|
-
<div class="card" data-pane="rules"><h2>字段与规则</h2><p id="structure"></p><p id="anchor-alert" role="status"></p><p>默认只填空白。可选择工作表、修正表头行及字段;应用设置会清除旧预览和主体确认,请重新核验。</p><label>搜索字段 <input id="mapping-search" type="search" placeholder="按原表字段名称筛选"></label><p class="muted"
|
|
18
|
+
<div class="card" data-pane="rules"><h2>字段与规则</h2><p id="structure"></p><p id="anchor-alert" role="status"></p><p>默认只填空白。可选择工作表、修正表头行及字段;应用设置会清除旧预览和主体确认,请重新核验。</p><label>搜索字段 <input id="mapping-search" type="search" placeholder="按原表字段名称筛选"></label><p class="muted">未识别字段可手动选择;没有数据来源的字段选择“不填写”。绿色仅表示字段映射已确认,不代表企业已核验或已查询。多个输出位置可分别确认同一字段;定位企业的输入列需明确。规则只保存表头与映射,不含记录。</p><button id="mapping-save">保存字段规则</button><button id="mapping-load">复用字段规则</button><div id="mapping"></div><button id="configure" class="primary">应用字段设置</button><div id="mapping-next" hidden role="status"><p>设置已保存。下一步核验企业主体,确认填写范围后生成指令并发送,查询结果会进入填写预览。</p><button id="mapping-continue" class="primary">下一步:主体核验</button></div></div>
|
|
19
19
|
<div class="card" data-pane="identity"><h2>主体核验与查询</h2><p id="connection"></p><p>候选不自动选择。查询返回歧义后,可选择返回候选或填写已核验的完整登记名称/信用代码;原表主体文字不覆盖,选择仅用于后续查询。</p><div id="entities"></div><div id="direct-scope" role="status"></div><details id="scope-omissions" hidden><summary>查看未纳入字段</summary><p id="scope-omissions-list"></p></details><label id="direct-ack-label" hidden><input id="direct-ack" type="checkbox">确认仅填写已纳入的原表字段</label><textarea id="direct-command" aria-label="填写指令" readonly hidden></textarea><button id="wizard-open" class="primary">生成填写指令</button><p class="muted">查询不会自动发送;回填后可在原生对话框修改并发送。</p></div>
|
|
20
20
|
<div class="card" data-pane="preview"><h2>填写预览</h2><p id="summary"></p><details><summary>逐列填写结果</summary><p class="muted">按单元格统计;整行缺少主体标识的情况另见下方原因分类。</p><div class="scroll"><table><thead><tr><th>原表字段</th><th>映射字段</th><th>预览可填写格数</th><th>未完成项</th></tr></thead><tbody id="field-results"></tbody></table></div></details><p class="muted">每格显示原值、准备填写值和来源。取消勾选后点击“应用选择”,再确认新的预览。</p>
|
|
21
21
|
<div class="toolbar"><label>筛选单元格 <input id="result-search" type="search" placeholder="位置、字段或值"></label><span id="result-count" class="muted"></span></div>
|
|
@@ -76,13 +76,15 @@ $('retry-run').onclick=()=>{openWizard(true);wizardStep=3;renderWizard()};
|
|
|
76
76
|
async function api(path,body){if(current&&body?.id===current.id)body={...body,expectedRevision:current.revision};const response=await fetch(basePath+path,{...(body===undefined?{}:{method:'POST',body:JSON.stringify(body)}),headers:{'Content-Type':'application/json','X-Form-Fill-Owner':owner}});const data=await response.json();if(!response.ok)throw Error(data.message);return data}
|
|
77
77
|
function navigate(target){if(target==='wizard'){openWizard();return}if(target!=='import'&&!current){$('status').textContent='请先导入表格。';return}step=target;$('history').hidden=true;$('current').hidden=false;$('current-tab').setAttribute('aria-pressed','true');$('history-tab').setAttribute('aria-pressed','false');document.querySelectorAll('[data-pane]').forEach(p=>p.hidden=p.dataset.pane!==target);document.querySelectorAll('[data-step]').forEach(b=>b.setAttribute('aria-current',String(b.dataset.step===target)))}
|
|
78
78
|
function downloads(){ $('downloads').replaceChildren();for(const [kind,label,suffix] of [['xlsx','下载已填副本','-已填副本.xlsx'],['changes','下载变更清单','-变更.json'],['incomplete','下载未完成项','-未完成.json']]){const a=document.createElement('a');Object.assign(a,{className:'download',href:basePath+'/download/'+current.id+'/'+kind,download:filename+suffix,textContent:label});$('downloads').append(a)}}
|
|
79
|
-
function businessSummary(){return current?(retryIntent?'仅重试失败、取消或未执行的部分,保留成功结果。':'')+'填写文件“'+current.filename+'”中的空白字段。工作表:'+current.analysis.tables.map(t=>t.sheet).join('、')+'。填写字段:'+current.catalog.filter(f=>wizardFields.has(f.key)).map(f=>f.label).join('、')+'
|
|
79
|
+
function businessSummary(){return current?(retryIntent?'仅重试失败、取消或未执行的部分,保留成功结果。':'')+'填写文件“'+current.filename+'”中的空白字段。工作表:'+current.analysis.tables.map(t=>t.sheet).join('、')+'。填写字段:'+current.catalog.filter(f=>wizardFields.has(f.key)).map(f=>f.label).join('、')+'。待补空位置:'+current.analysis.opportunities.filter(p=>wizardFields.has(p.field)).map(p=>p.sheet+'!'+p.cell+'('+p.label+')').join('、')+'。保留已有值、公式与未确认位置,核验预览后生成新副本。':'请先上传 XLSX 文件。'}
|
|
80
80
|
function command(){return current?businessSummary()+'\n'+$('requirements').value+'\n执行关联:调用 form_fill_enrich:taskId='+current.id+',expectedRevision='+current.revision+(retryIntent?',mode=retry':'')+'。字段范围以工作台确认设置为准。':''}
|
|
81
|
-
function mappingVersion(){return JSON.stringify({tables:current?.analysis.tables.map(t=>({sheet:t.sheet,headerRow:t.headerRow,anchors:t.anchors,mappings:t.mappings.map(m=>[m.column,m.field])})),scope:current?.selectedFields??null})}
|
|
81
|
+
function mappingVersion(){return JSON.stringify({tables:current?.analysis.tables.map(t=>({sheet:t.sheet,headerRow:t.headerRow,anchors:t.anchors,mappings:t.mappings.map(m=>[m.column,m.field,m.role,m.evidence])})),scope:current?.selectedFields??null})}
|
|
82
82
|
function saveWizard(){if(current)sessionStorage.setItem('ff-wizard:'+current.id,JSON.stringify({fields:[...wizardFields],signature:mappingVersion(),requirements:$('requirements').value,step:wizardStep}))}
|
|
83
|
-
function
|
|
83
|
+
async function requireMappingHost(){if(current?.mappingProtocol!==2)throw Error('页面与服务版本不一致,请保存任务后更新并重新打开 DSH。');await new Promise((resolve,reject)=>{const timer=setTimeout(()=>{window.removeEventListener('message',receive);reject(Error('当前 Host 不支持此映射草稿协议,请保存任务后更新并重新打开 DSH。'))},1500);function receive(e){if(e.origin!==location.origin||e.source!==parent||e.data?.type!=='ff-capabilities-result')return;clearTimeout(timer);window.removeEventListener('message',receive);e.data.mappingDraft===2?resolve():reject(Error('Host 映射协议不兼容,请更新 DSH 插件。'))}window.addEventListener('message',receive);parent.postMessage({type:'ff-capabilities'},location.origin)})}
|
|
84
|
+
function isInput(t,m){return t.anchorColumns?t.anchorColumns.includes(m.column):t.anchors.includes(m.field)}
|
|
85
|
+
function initWizard(){const structure=mappingVersion();if(wizardTask===current?.id&&wizardStructure===structure)return;wizardTask=current?.id;wizardStructure=structure;const available=current?.analysis.tables.flatMap(t=>t.mappings.filter(m=>m.field&&!isInput(t,m)).map(m=>m.field))||[];let saved;try{saved=JSON.parse(sessionStorage.getItem('ff-wizard:'+current?.id))}catch{}const valid=saved?.signature===structure;wizardFields=new Set((valid?saved.fields:current?.selectedFields||available).filter(k=>available.includes(k)));$('requirements').value=saved?.requirements||'';wizardStep=valid?saved.step||0:0;if(saved&&!valid)$('wizard-message').textContent='字段映射或执行范围已变化,已重新加载当前可填写字段,请检查后再发送。'}
|
|
84
86
|
function scopeReview(){
|
|
85
|
-
const omitted=current?.analysis.tables.flatMap(t=>t.mappings.filter(m=>!t
|
|
87
|
+
const omitted=current?.analysis.tables.flatMap(t=>t.mappings.filter(m=>!isInput(t,m)&&(!m.field||!wizardFields.has(m.field))).map(m=>t.sheet+':'+m.label))||[];
|
|
86
88
|
const missing=current?.analysis.incomplete.filter(i=>i.reason==='missing-anchor')||[];
|
|
87
89
|
let problem='';
|
|
88
90
|
const tables=current?.analysis.tables||[],issues=current?.analysis.incomplete||[];
|
|
@@ -90,7 +92,7 @@ function scopeReview(){
|
|
|
90
92
|
const conflicts=issues.filter(i=>i.reason==='ambiguous-mapping');
|
|
91
93
|
if(conflicts.length){
|
|
92
94
|
const details=conflicts.map(i=>{const mappings=current.configuration?.mappings?.filter(m=>m.sheet===i.sheet&&m.field)||[];const repeated=[...new Set(mappings.filter(m=>mappings.some(n=>n.column!==m.column&&n.field===m.field)).map(m=>current.catalog.find(f=>f.key===m.field)?.label||m.field))];return i.sheet+(repeated.length?':'+repeated.join('、'):'')});
|
|
93
|
-
problem='字段映射存在重复或冲突('+details.join(';')+'
|
|
95
|
+
problem='字段映射存在重复或冲突('+details.join(';')+')。请返回字段设置,请逐一确认重复输出位置;重复企业标识仅保留一个定位列,其余设为仅接收填写。';
|
|
94
96
|
}else problem='未识别到可执行的表头。请在字段设置确认表头行,保留企业名称或有值的信用代码,并至少映射一个需要填写的字段。';
|
|
95
97
|
}else if(!wizardFields.size)problem='尚未选择填写字段。请返回字段设置,为原表中至少一个待填列选择对应字段;企业名称仅用于定位主体。';
|
|
96
98
|
else if(missing.length&&!current.analysis.opportunities.length)problem='待填行缺少可用企业标识。请返回字段设置映射有值的企业名称列,或检查原表主体内容。';
|
|
@@ -104,9 +106,9 @@ function renderWizard(){
|
|
|
104
106
|
$('wizard-rules').textContent=current?current.analysis.tables.map(t=>t.sheet+':第 '+t.headerRow+' 行表头').join(';'):'请先上传并识别表格。';
|
|
105
107
|
$('wizard-fields').replaceChildren();
|
|
106
108
|
const available=new Set(current?.analysis.tables.flatMap(t=>t.mappings.map(m=>m.field))||[]);
|
|
107
|
-
const fillable=new Set(current?.analysis.tables.flatMap(t=>t.mappings.filter(m=>m.field&&!t
|
|
109
|
+
const fillable=new Set(current?.analysis.tables.flatMap(t=>t.mappings.filter(m=>m.field&&!isInput(t,m)).map(m=>m.field))||[]);
|
|
108
110
|
for(const field of (current?.catalog||[]).filter(f=>available.has(f.key))){
|
|
109
|
-
const label=document.createElement('label');label.hidden=!field.label.includes($('field-search').value.trim());const input=document.createElement('input');input.type='checkbox';const fixed=
|
|
111
|
+
const label=document.createElement('label');label.hidden=!field.label.includes($('field-search').value.trim());const input=document.createElement('input');input.type='checkbox';const fixed=available.has(field.key)&&!fillable.has(field.key);input.checked=fixed||wizardFields.has(field.key);input.disabled=fixed||!available.has(field.key);input.dataset.field=field.key;
|
|
110
112
|
input.onchange=()=>{if(input.checked)wizardFields.add(field.key);else wizardFields.delete(field.key);saveWizard();renderWizard()};
|
|
111
113
|
label.append(input,document.createTextNode(field.label+(fixed?'(固定)':!available.has(field.key)?'(原表未映射)':'')));$('wizard-fields').append(label);
|
|
112
114
|
}
|
|
@@ -124,7 +126,7 @@ function closeWizard(focusComposer=false){saveWizard();$('draft-conflict').close
|
|
|
124
126
|
async function applyScope(){
|
|
125
127
|
if(!current||!wizardFields.size)throw Error('请至少选择一个已映射的填写字段');
|
|
126
128
|
const review=scopeReview();if(review.blocked)throw Error('没有可用主体标识,请先到字段设置映射企业名称列。');if(review.omitted.length&&!$('scope-ack').checked)throw Error('请确认未纳入的列,或返回字段设置完善映射。');
|
|
127
|
-
const original=current.selectedFields||[...new Set(current.analysis.tables.flatMap(t=>t.mappings.filter(m=>m.field&&!t
|
|
129
|
+
const original=current.selectedFields||[...new Set(current.analysis.tables.flatMap(t=>t.mappings.filter(m=>m.field&&!isInput(t,m)).map(m=>m.field)))];
|
|
128
130
|
if(retryIntent&&JSON.stringify([...original].sort())!==JSON.stringify([...wizardFields].sort()))throw Error('重试时请保留原字段范围;如需更改,请重新生成完整核验指令。');
|
|
129
131
|
if(JSON.stringify([...original].sort())!==JSON.stringify([...wizardFields].sort()))render(await api('/scope',{id:current.id,selectedFields:[...wizardFields]}));
|
|
130
132
|
}
|
|
@@ -132,11 +134,11 @@ async function sendDraft(mode){
|
|
|
132
134
|
if(busy)return;
|
|
133
135
|
if(parent===window){$('wizard-message').textContent='独立页面请复制指令到 DSH 对话。';return}
|
|
134
136
|
controls(true);$('wizard-message').textContent='';
|
|
135
|
-
try{await applyScope();saveWizard();parent.postMessage({type:'ff-draft',taskId:current.id,revision:current.revision,requirements:$('requirements').value,summary:businessSummary(),retryOnly:retryIntent,mode,expectedDraft},location.origin)}catch(e){$('wizard-message').textContent=e.message}finally{controls(false)}
|
|
137
|
+
try{await requireMappingHost();await applyScope();saveWizard();parent.postMessage({type:'ff-draft',taskId:current.id,revision:current.revision,requirements:$('requirements').value,summary:businessSummary(),retryOnly:retryIntent,mode,expectedDraft},location.origin)}catch(e){$('wizard-message').textContent=e.message;$('status').textContent=e.message}finally{controls(false)}
|
|
136
138
|
}
|
|
137
139
|
function directScope(){
|
|
138
140
|
initWizard();retryIntent=false;
|
|
139
|
-
wizardFields=new Set(current?.selectedFields||current?.analysis.tables.flatMap(t=>t.mappings.filter(m=>m.field&&!t
|
|
141
|
+
wizardFields=new Set(current?.selectedFields||current?.analysis.tables.flatMap(t=>t.mappings.filter(m=>m.field&&!isInput(t,m)).map(m=>m.field))||[]);
|
|
140
142
|
const review=scopeReview(),signature=mappingVersion();
|
|
141
143
|
if($('direct-ack').dataset.signature!==signature){$('direct-ack').checked=false;$('direct-ack').dataset.signature=signature}
|
|
142
144
|
$('direct-scope').textContent=review.problem||'将填写 '+wizardFields.size+' 个已确认字段。'+(review.omitted.length?'另有 '+review.omitted.length+' 列未纳入,可展开查看。':'');
|
|
@@ -156,7 +158,8 @@ async function directDraft(){
|
|
|
156
158
|
function highlightMappingConflicts(){
|
|
157
159
|
for(const section of $('mapping').querySelectorAll('fieldset')){
|
|
158
160
|
const fields=[...section.querySelectorAll('select[data-column]')];
|
|
159
|
-
for(const field of fields)
|
|
161
|
+
for(const field of fields)field.closest('.ff-mapping-row').updateMappingStatus?.();
|
|
162
|
+
for(const field of fields){const peers=fields.filter(other=>other.value===field.value);const conflict=!!field.value&&peers.length>1&&(peers.some(other=>other.dataset.manual!=='true')||(field.dataset.primary==='true'&&peers.filter(other=>other.closest('.ff-mapping-row').querySelector('.ff-mapping-role').value!=='output').length>1));field.setAttribute('aria-invalid',String(conflict));const row=field.closest('.ff-mapping-row');row.classList.toggle('ff-mapping-conflict',conflict);if(conflict){const message=row.querySelector('.ff-mapping-status');message.dataset.state='pending';message.textContent='待人工确认:重复位置需逐一确认;企业标识需保留唯一定位列。';}}
|
|
160
163
|
}
|
|
161
164
|
}
|
|
162
165
|
function filterResults(){const query=$('result-search').value.trim().toLowerCase(),rows=[...$('changes').rows];let shown=0;for(const tr of rows){tr.hidden=!!query&&!tr.textContent.toLowerCase().includes(query);if(!tr.hidden)shown++}const selected=rows.filter(tr=>tr.querySelector('input')?.checked).length;$('result-count').textContent=rows.length?'显示 '+shown+' / '+rows.length+' 格 · 已选 '+selected+' 格'+(!shown?' · 无匹配结果':''):'暂无可填写单元格'}
|
|
@@ -178,8 +181,14 @@ function renderSettings(data){
|
|
|
178
181
|
const title=document.createElement('strong');title.textContent=cell.label;label.append(title);
|
|
179
182
|
const select=document.createElement('select');select.dataset.column=cell.column;select.setAttribute('aria-label',sheet.name+' 字段 '+cell.label);
|
|
180
183
|
const override=data.configuration?.mappings?.find(m=>m.sheet===sheet.name&&m.column===cell.column);
|
|
181
|
-
const
|
|
184
|
+
const exactNormalize=s=>String(s??'').trim().replace(/\s+/g,'').toLowerCase();
|
|
185
|
+
const exact=data.catalog.filter(f=>[f.label,f.key,...(f.aliases||[])].some(a=>exactNormalize(a)===exactNormalize(cell.label)));
|
|
186
|
+
const automatic=exact.length===1&&exactNormalize(cell.label)!=='地址'?exact[0].key:'';
|
|
182
187
|
let chosen=override?override.field||'':automatic,manual=!!override;
|
|
188
|
+
select.dataset.manual=String(manual);
|
|
189
|
+
const role=document.createElement('select');role.className='ff-mapping-role';role.setAttribute('aria-label',sheet.name+' 字段用途 '+cell.label);
|
|
190
|
+
for(const [value,text] of [['input','用于定位企业'],['output','仅接收填写']]){const o=document.createElement('option');o.value=value;o.textContent=text;role.append(o)}
|
|
191
|
+
role.value=override?.role||'input';
|
|
183
192
|
const normalize=s=>String(s).normalize('NFKC').toLowerCase().replace(/[\s_()()·-]/g,'');
|
|
184
193
|
const matches=(field,query)=>{const text=normalize([field.label,field.key,...(field.aliases||[])].join(' '));return query.trim().split(/\s+/).every(word=>{const q=normalize(word);if(text.includes(q))return true;if(q.length<2)return false;let i=0;for(const c of text)if(c===q[i])i++;return i===q.length})};
|
|
185
194
|
const search=document.createElement('input');search.type='search';search.placeholder='关键词,例如:电话、信用代码';search.setAttribute('aria-label',sheet.name+' 搜索目标字段 '+cell.label);
|
|
@@ -193,7 +202,9 @@ function renderSettings(data){
|
|
|
193
202
|
const status=document.createElement('p');status.className='ff-mapping-status';status.setAttribute('aria-live','polite');
|
|
194
203
|
const count=document.createElement('small');count.className='muted';
|
|
195
204
|
const suggestions=document.createElement('div');suggestions.className='ff-mapping-suggestions';
|
|
196
|
-
const updateStatus=()=>{status.textContent=chosen?(manual?'
|
|
205
|
+
const updateStatus=()=>{const f=data.catalog.find(f=>f.key===chosen);role.hidden=!(f?.anchor||f?.anchorFallback);select.dataset.manual=String(manual);select.dataset.primary=String(!role.hidden);status.dataset.state=chosen?(manual?'confirmed':'automatic'):manual?'skipped':suggestions.childElementCount?'pending':'unmatched';status.textContent=chosen?(manual?'已确认':'自动通过')+':'+(f?.label||chosen):manual?'已跳过:保留原值,不填写':suggestions.childElementCount?'待人工确认:请选择推荐字段':'未匹配:保留原值,不填写;可搜索字段';};
|
|
206
|
+
label.updateMappingStatus=updateStatus;
|
|
207
|
+
role.onchange=()=>{manual=true;updateStatus();highlightMappingConflicts()};
|
|
197
208
|
const options=()=>{
|
|
198
209
|
const group=data.catalogGroups?.find(g=>g.id===dimension.value);
|
|
199
210
|
const found=(data.catalog||[]).filter(f=>matches(f,search.value)&&(!dimension.value||(dimension.value==='other'?!data.catalogGroups?.some(g=>g.fields.includes(f.key)):group?.fields.includes(f.key))));
|
|
@@ -206,7 +217,7 @@ function renderSettings(data){
|
|
|
206
217
|
select.value=chosen;count.textContent=found.length?'匹配 '+found.length+' 个字段':'没有匹配字段,请更换关键词或维度';updateStatus();
|
|
207
218
|
};
|
|
208
219
|
const choose=()=>{chosen=select.value;manual=true;suggestions.replaceChildren();trigger.textContent=chosen?(data.catalog.find(f=>f.key===chosen)?.label||chosen):'不填写';updateStatus();$('mapping-next').hidden=true};
|
|
209
|
-
const finish=()=>{choose();picker.open=false;trigger.focus()};
|
|
220
|
+
const finish=()=>{choose();highlightMappingConflicts();picker.open=false;trigger.focus()};
|
|
210
221
|
select.onchange=choose;
|
|
211
222
|
select.ondblclick=finish;
|
|
212
223
|
select.onkeydown=event=>{if(event.key==='Enter'){event.preventDefault();finish()}};
|
|
@@ -215,17 +226,18 @@ function renderSettings(data){
|
|
|
215
226
|
const hints={'原文件导入名称':['company_name'],'单位':['company_name'],'名称':['company_name'],'法定代表人':['legal_person'],'登记状态':['business_status'],'地址':['registered_address','mailing_address','invoice_address'],'网址':['contact_official_website']}[cell.label.trim()]||[];
|
|
216
227
|
const recommended=[...hints.map(key=>data.catalog.find(f=>f.key===key)).filter(Boolean),...(data.catalog||[]).filter(f=>!hints.includes(f.key)&&matches(f,cell.label))].slice(0,4);
|
|
217
228
|
if(recommended.length){const hint=document.createElement('span');hint.textContent='推荐(确认后采用):';suggestions.append(hint)}
|
|
218
|
-
for(const field of recommended){const button=document.createElement('button');button.type='button';button.textContent='推荐:'+field.label;button.onclick=()=>{chosen=field.key;manual=true;suggestions.replaceChildren();options();$('mapping-next').hidden=true;picker.open=false;trigger.focus()};suggestions.append(button)}
|
|
229
|
+
for(const field of recommended){const button=document.createElement('button');button.type='button';button.textContent='推荐:'+field.label;button.title=data.catalogGroups?.find(g=>g.fields.includes(field.key))?.label||'字段目录';button.onclick=()=>{chosen=field.key;manual=true;suggestions.replaceChildren();options();$('mapping-next').hidden=true;picker.open=false;trigger.focus()};suggestions.append(button)}
|
|
219
230
|
}
|
|
220
231
|
const filters=document.createElement('div');filters.className='ff-mapping-filters';filters.append(search,dimension);
|
|
221
232
|
select.size=8;
|
|
222
233
|
const done=document.createElement('button');done.type='button';done.textContent='确认选择';done.onclick=finish;
|
|
223
234
|
panel.append(filters,select,count,done);
|
|
224
|
-
label.append(status,suggestions,picker);options();fields.append(label);
|
|
235
|
+
label.append(status,suggestions,picker,role);options();fields.append(label);
|
|
225
236
|
}
|
|
226
237
|
};
|
|
227
|
-
header.onchange=showFields;section.append(header,fields);showFields();$('mapping').append(section);
|
|
238
|
+
header.onchange=()=>{showFields();highlightMappingConflicts()};section.append(header,fields);showFields();$('mapping').append(section);
|
|
228
239
|
}
|
|
240
|
+
highlightMappingConflicts();
|
|
229
241
|
const seen=new Set();
|
|
230
242
|
for(const item of data.changeSet.incomplete.filter(i=>i.reason==='candidate-review-required')){
|
|
231
243
|
const key=JSON.stringify([item.sheet,item.row]);if(seen.has(key))continue;seen.add(key);
|
|
@@ -260,11 +272,11 @@ initWizard();filterResults();syncActions();if($('wizard').open)renderWizard();
|
|
|
260
272
|
const missing=data.analysis.incomplete.filter(i=>i.reason==='missing-anchor');
|
|
261
273
|
$('anchor-alert').textContent=missing.length?'有 '+missing.length+' 行缺少可用主体标识。请把实际包含名称的列映射为“企业名称”,或提供非空信用代码;当前不会查询这些行。':'';
|
|
262
274
|
}
|
|
263
|
-
async function restore(silent=false){if(busy||!current&& !location.hash)return;const id=new URLSearchParams(location.hash.slice(1)).get('task');if(!id)return;try{const data=await api('/task/'+encodeURIComponent(id));if(!silent||data.revision!==current?.revision){render(data);if(!silent){const target=pendingNavigation||(data.confirmed?'download':'preview');pendingNavigation=undefined;if(target==='history')$('history-tab').click();else navigate(target)}}}catch(e){if(!silent)$('status').textContent=e.message}}
|
|
275
|
+
async function restore(silent=false){if(busy||!current&& !location.hash)return;const id=new URLSearchParams(location.hash.slice(1)).get('task');if(!id)return;try{const data=await api('/task/'+encodeURIComponent(id));if(!silent||data.id!==current?.id||data.revision!==current?.revision){render(data);if(!silent){const target=pendingNavigation||(data.confirmed?'download':'preview');pendingNavigation=undefined;if(target==='history')$('history-tab').click();else navigate(target)}}}catch(e){if(!silent)$('status').textContent=e.message}}
|
|
264
276
|
async function preview(buffer,name,demo=false){controls(true);try{const bytes=new Uint8Array(buffer);if(bytes.length>8388608)throw Error('文件超过 8 MiB');let binary='';for(let i=0;i<bytes.length;i+=32768)binary+=String.fromCharCode(...bytes.subarray(i,i+32768));$('grid').open=true;render(await api('/preview',{base64:btoa(binary),filename:name,sessionId,analyzeOnly:!demo}));navigate(demo?'preview':'rules');$('status').textContent='预览已准备好,请检查后确认。'}catch(e){$('status').textContent=e.message;$('wizard-message').textContent=e.message}finally{controls(false)}}
|
|
265
277
|
document.querySelectorAll('[data-step]').forEach(b=>b.onclick=()=>navigate(b.dataset.step));
|
|
266
278
|
$('file').onchange=async()=>{const input=$('file'),f=input.files[0];if(!f||busy)return;input.disabled=true;controls(true);$('status').textContent='正在自动解析表格…';try{await preview(await f.arrayBuffer(),f.name)}catch(e){$('status').textContent=e.message}finally{input.disabled=false;input.value='';controls(false)}};
|
|
267
|
-
function readMapping(){const configuration={sheets:[],headers:[],mappings:[]};for(const section of $('mapping').querySelectorAll('fieldset')){if(!section.querySelector('.sheet-enabled').checked)continue;const sheet=section.dataset.sheet;configuration.sheets.push(sheet);configuration.headers.push({sheet,row:Number(section.querySelector('.header-row').value)});for(const field of section.querySelectorAll('[data-column]'))configuration.mappings.push({sheet,column:Number(field.dataset.column),field:field.value||null})}return configuration}
|
|
279
|
+
function readMapping(){const configuration={sheets:[],headers:[],mappings:[]};for(const section of $('mapping').querySelectorAll('fieldset')){if(!section.querySelector('.sheet-enabled').checked)continue;const sheet=section.dataset.sheet;configuration.sheets.push(sheet);configuration.headers.push({sheet,row:Number(section.querySelector('.header-row').value)});for(const field of section.querySelectorAll('[data-column]'))if(field.dataset.manual==='true')configuration.mappings.push({sheet,column:Number(field.dataset.column),field:field.value||null,...(field.dataset.primary==='true'?{role:field.closest('.ff-mapping-row').querySelector('.ff-mapping-role').value}:{})})}return configuration}
|
|
268
280
|
function mappingSignature(configuration){return JSON.stringify(configuration.headers.map(h=>({sheet:h.sheet,row:h.row,cells:current.structure.find(s=>s.name===h.sheet)?.rows.find(r=>r.number===h.row)?.cells}))) }
|
|
269
281
|
$('mapping-save').onclick=()=>{try{if(!current||busy)return;const configuration=readMapping();localStorage.setItem('ff-mapping:'+owner,JSON.stringify({schema:1,signature:mappingSignature(configuration),configuration}));$('status').textContent='字段规则已保存在本浏览器。'}catch{$('status').textContent='浏览器无法保存字段规则。'}};
|
|
270
282
|
$('mapping-load').onclick=async()=>{if(!current||busy)return;controls(true);try{const saved=JSON.parse(localStorage.getItem('ff-mapping:'+owner)||'null');if(saved?.schema!==1||saved.signature!==mappingSignature(saved.configuration))throw Error('没有与当前工作表及表头一致的规则,请手动设置。');render(await api('/configure',{id:current.id,configuration:saved.configuration}));$('status').textContent='字段规则已复用,请检查后重新核验。'}catch(e){$('status').textContent=e.message}finally{controls(false)}};
|
|
@@ -281,7 +293,7 @@ document.querySelectorAll('[data-wizard-step]').forEach(b=>b.onclick=()=>{wizard
|
|
|
281
293
|
$('wizard-prev').onclick=()=>{wizardStep=Math.max(0,wizardStep-1);saveWizard();renderWizard()};$('wizard-next').onclick=()=>{wizardStep=Math.min(3,wizardStep+1);saveWizard();renderWizard()};
|
|
282
294
|
$('wizard-file').onchange=async()=>{const file=$('wizard-file').files[0];if(file){await preview(await file.arrayBuffer(),file.name);renderWizard()}};
|
|
283
295
|
$('wizard-edit-rules').onclick=()=>{closeWizard();navigate('rules')};
|
|
284
|
-
$('field-search').oninput=renderWizard;$('fields-all').onclick=()=>{wizardFields=new Set(current?.analysis.tables.flatMap(t=>t.mappings.filter(m=>m.field&&!t
|
|
296
|
+
$('field-search').oninput=renderWizard;$('fields-all').onclick=()=>{wizardFields=new Set(current?.analysis.tables.flatMap(t=>t.mappings.filter(m=>m.field&&!isInput(t,m)).map(m=>m.field))||[]);saveWizard();renderWizard()};$('fields-clear').onclick=()=>{wizardFields.clear();saveWizard();renderWizard()};
|
|
285
297
|
$('scope-ack').onchange=renderWizard;
|
|
286
298
|
$('requirements').oninput=()=>{saveWizard();$('qcc-command').textContent=command()};
|
|
287
299
|
$('copy-command').onclick=async()=>{try{await applyScope();saveWizard();await navigator.clipboard.writeText(command());$('status').textContent='已复制,请在 DSH 对话中发送。'}catch(e){$('status').textContent=e.message}};
|
|
@@ -294,6 +306,7 @@ window.addEventListener('message',e=>{if(e.origin!==location.origin||e.source!==
|
|
|
294
306
|
api('/health').then(s=>{$('connection').textContent=s.qccAvailable?'已发现企查查工商工具,可在对话中执行。':'尚未发现企查查工商工具;请先连接 MCP。合成模板仍可体验。'}).catch(()=>{});
|
|
295
307
|
$('preview-continue').onclick=async()=>{if(busy||!current)return;controls(true);try{const ids=[...$('changes').querySelectorAll('input:checked')].map(x=>x.value);if(!current.confirmed&&JSON.stringify([...ids].sort())!==JSON.stringify([...(current.selectedIds||[])].sort()))render(await api('/select',{id:current.id,selectedIds:ids}));navigate('download');$('confirm').scrollIntoView({block:'center'});$('confirm').focus();$('status').textContent=current.confirmed?'副本已生成,请点击下载。':'请确认填写结果,点击蓝色按钮生成新副本。'}catch(e){$('status').textContent=e.message}finally{controls(false)}};
|
|
296
308
|
$('mapping-continue').onclick=()=>{if(busy||!current)return;navigate('identity');$('wizard-open').focus()};
|
|
297
|
-
$('mapping').addEventListener('change',()=>{$('mapping-next').hidden=true});
|
|
309
|
+
$('mapping').addEventListener('change',()=>{$('mapping-next').hidden=true;highlightMappingConflicts()});
|
|
310
|
+
window.addEventListener('hashchange',()=>{if(new URLSearchParams(location.hash.slice(1)).get('task')!==current?.id)restore()});
|
|
298
311
|
navigate('import');restore();setInterval(()=>{if(!document.hidden&¤t&&!busy&&!$('wizard').open)restore(true)},2000);
|
|
299
312
|
</script></html>
|
package/lib/workflow.js
CHANGED
|
@@ -12,7 +12,7 @@ export async function previewBytes(bytes, { provider = createMockProvider(), max
|
|
|
12
12
|
if (provider.mode !== 'mock' && (provider.mode !== 'qcc' || confirmPaidCalls !== true)) throw new FillError('REAL_PROVIDER_DISABLED', '真实来源需要调用方明确授权');
|
|
13
13
|
const { analysis } = analyzeDocument(bytes, FIELD_CATALOG, configuration);
|
|
14
14
|
if (selectedFields !== undefined) {
|
|
15
|
-
if (!Array.isArray(selectedFields) || new Set(selectedFields).size !== selectedFields.length || selectedFields.some(key=>!FIELD_CATALOG.some(f=>f.key===key
|
|
15
|
+
if (!Array.isArray(selectedFields) || new Set(selectedFields).size !== selectedFields.length || selectedFields.some(key=>!FIELD_CATALOG.some(f=>f.key===key&&(!f.anchor||analysis.tables.some(t=>t.mappings.some(m=>m.field===key&&m.role==='output')))))) throw new FillError('FIELD_SCOPE','填写字段范围无效');
|
|
16
16
|
analysis.incomplete.push(...analysis.opportunities.filter(o=>!selectedFields.includes(o.field)).map(o=>({...o,reason:'user-excluded'})));
|
|
17
17
|
analysis.opportunities=analysis.opportunities.filter(o=>selectedFields.includes(o.field));
|
|
18
18
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-form-fill-agent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "AI
|
|
6
|
-
"repository": {
|
|
7
|
-
|
|
5
|
+
"description": "AI form filling and spreadsheet autofill for XLSX in DeepSeek Harness, using authorized Qichacha MCP facts with cell previews, user confirmation and export to a new copy.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/duhu2000/dsh-form-fill-agent.git",
|
|
9
|
+
"directory": "packages/dsh-form-fill-agent"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public",
|
|
13
|
+
"tag": "latest",
|
|
14
|
+
"registry": "https://registry.npmjs.org/"
|
|
15
|
+
},
|
|
8
16
|
"type": "module",
|
|
9
17
|
"main": "lib/index.js",
|
|
10
18
|
"exports": {
|
|
@@ -24,13 +32,36 @@
|
|
|
24
32
|
"node": ">=22"
|
|
25
33
|
},
|
|
26
34
|
"dependencies": {
|
|
27
|
-
"form-fill-core": "0.2.
|
|
35
|
+
"form-fill-core": "0.2.10",
|
|
28
36
|
"qcc-form-fill-provider": "0.2.1"
|
|
29
37
|
},
|
|
30
38
|
"dsh": {
|
|
31
|
-
"client": {
|
|
39
|
+
"client": {
|
|
40
|
+
"inject": [
|
|
41
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
42
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
43
|
+
],
|
|
44
|
+
"platform": "web"
|
|
45
|
+
},
|
|
32
46
|
"bundle": {
|
|
33
47
|
"patch": "./cordis.patch.yml"
|
|
34
48
|
}
|
|
35
|
-
}
|
|
49
|
+
},
|
|
50
|
+
"keywords": [
|
|
51
|
+
"AI填表",
|
|
52
|
+
"AI 填表",
|
|
53
|
+
"自动填表",
|
|
54
|
+
"表格填充",
|
|
55
|
+
"表格补全",
|
|
56
|
+
"Excel填表",
|
|
57
|
+
"Excel回填",
|
|
58
|
+
"form filling",
|
|
59
|
+
"spreadsheet autofill",
|
|
60
|
+
"企查查MCP",
|
|
61
|
+
"企查查 MCP",
|
|
62
|
+
"form-filling",
|
|
63
|
+
"spreadsheet-autofill",
|
|
64
|
+
"xlsx",
|
|
65
|
+
"enterprise-data"
|
|
66
|
+
]
|
|
36
67
|
}
|