Explorar el Código

设计量细目,ai识别填写demo

MaiXinRong hace 2 semanas
padre
commit
ffe20c7fd8

+ 83 - 0
app/controller/ledger_controller.js

@@ -1186,6 +1186,89 @@ module.exports = app => {
                 ctx.body = responseData;
                 ctx.body = responseData;
             }
             }
         }
         }
+
+        async _aiCompliePosCalcDetail(ctx, data) {
+            const pos = await this.ctx.service.pos.getDataById(data.pos);
+            if (!pos) throw '选择的计量单元不存在,请刷新页面后再试';
+
+            const validColSet = data.template.col_set.filter(x => { return !x.expr; });
+            const AIInspect = require('../lib/ai_inspect');
+            const aiInspect = new AIInspect({ apiKey: 'app-BIOhBbFlNSakoRGKU91e2ksJ', baseUrl: 'http://39.108.81.93:8080/v1' });
+            const transResult = await aiInspect.posCalcDetailRead(validColSet, data.billsName, data.keyword, data.files);
+            const rows = transResult.res.split('\n').filter(x => { return !!x; });
+            rows.shift();
+            const pcdData = rows.map(x => {
+                const rd = x.split('|');
+                const nd = { pid: pos.id, lid: pos.lid };
+                for (const [i, c] of validColSet.entries()) {
+                    if (c.type === 'num') {
+                        nd[c.field] = ctx.helper._.toNumber(rd[i]);
+                    } else {
+                        nd[c.field] = rd[i];
+                    }
+                }
+                return nd;
+            });
+            const responseData = await ctx.service.posCalcDetail.updateDatas({ add: pcdData });
+            ctx.body = { err: 0, msg: '', data: responseData };
+        }
+        async aiComplie(ctx) {
+            let stream;
+            try {
+                const parts = ctx.multipart({ autoFields: true });
+
+                let index = 0;
+                const create_time = Date.parse(new Date()) / 1000;
+                stream = await parts();
+                const user = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                const rela_id = parts.field.rela_id;
+                const rela_type = parts.field.rela_type;
+                const billsName = parts.field.rela_bills_name;
+                const rela_template = parts.field.rela_template;
+                const template = await ctx.service.calcTmpl.getTemplate(rela_template);
+                const keyword = parts.field.rela_keyword;
+
+                const uploadfiles = [];
+                while (stream !== undefined) {
+                    if (!stream.filename) throw '未发现上传文件!';
+
+                    const fileInfo = path.parse(stream.filename);
+                    const filepath = `app/public/upload/${ctx.tender.id}/temp/${ctx.moment().format('YYYYMMDD')}/${create_time + '_' + index + fileInfo.ext}`;
+
+                    // 保存文件
+                    await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream);
+                    await sendToWormhole(stream);
+
+                    // 插入到stage_pay对应的附件列表中
+                    uploadfiles.push({
+                        rela_id, rela_type,
+                        filename: fileInfo.name,
+                        fileext: fileInfo.ext,
+                        filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
+                        filepath,
+                    });
+                    ++index;
+                    if (Array.isArray(parts.field.size) && index < parts.field.size.length) {
+                        stream = await parts();
+                    } else {
+                        stream = undefined;
+                    }
+                }
+                const result = await ctx.service.tempFile.addFiles(ctx.tender.id, uploadfiles, user);
+
+                switch(rela_type) {
+                    case 'pos':
+                        await this._aiCompliePosCalcDetail(ctx, { billsName, template, pos: rela_id, files: result, keyword });
+                        break;
+                    default:
+                        throw '暂不提供该ai识别内容';
+                }
+            } catch (err) {
+                ctx.log(err);
+                if (stream) await sendToWormhole(stream);
+                ctx.ajaxErrorBody(err, 'ai识别填写失败');
+            }
+        }
     }
     }
 
 
     return LedgerController;
     return LedgerController;

+ 23 - 27
app/lib/ai_inspect.js

@@ -49,39 +49,42 @@ class AiInspect {
         }
         }
     }
     }
 
 
-    async posCalcDetailMatch(template, file, userId = `user_123`) {
+    async workflows(body) {
         const url = `${this.baseUrl}/workflows/run`;
         const url = `${this.baseUrl}/workflows/run`;
         const headers = {
         const headers = {
             'Authorization': `Bearer ${this.apiKey}`,
             'Authorization': `Bearer ${this.apiKey}`,
             'Content-Type': 'application/json',
             'Content-Type': 'application/json',
         };
         };
-        const body = {
-            inputs: {
-                headerList: template.col_set.map(x => { return x.title }).join('|'),
-                imgList: [{
-                    type: "image",
-                    transfer_method: "remote_url",
-                    url: file.viewpath,
-                }],
-            },
-            user: userId,
-        };
-
         try {
         try {
             const res = await axios.post(url, body, { headers });
             const res = await axios.post(url, body, { headers });
-            return res.data;
+            return res.data.data.outputs;
         } catch (err) {
         } catch (err) {
             console.error('Dify 请求失败:', err.response || err.response.data || err.message);
             console.error('Dify 请求失败:', err.response || err.response.data || err.message);
             throw new Error('Dify 请求失败');
             throw new Error('Dify 请求失败');
         }
         }
     }
     }
 
 
-    async imageTransExcel(files, userId = `user_123`) {
-        const url = `${this.baseUrl}/workflows/run`;
-        const headers = {
-            'Authorization': `Bearer ${this.apiKey}`,
-            'Content-Type': 'application/json',
+    async posCalcDetailRead(col, billsName, keyword, files, userId = `user_123`) {
+        const body = {
+            inputs: {
+                header: col.map(x => { return x.title }).join(','),
+                billsName,
+                keyword,
+                imgList: [],
+            },
+            user: userId,
         };
         };
+        for (const f of files) {
+            body.inputs.imgList.push({
+                type: "image",
+                transfer_method: "remote_url",
+                url: f.viewpath,
+            })
+        }
+        return await this.workflows(body);
+    }
+
+    async imageTransExcel(files, userId = `user_123`) {
         const body = {
         const body = {
             inputs: { imgList: [] },
             inputs: { imgList: [] },
             user: userId,
             user: userId,
@@ -94,14 +97,7 @@ class AiInspect {
                 url: f.viewpath,
                 url: f.viewpath,
             })
             })
         }
         }
-
-        try {
-            const res = await axios.post(url, body, { headers });
-            return res.data.data.outputs;
-        } catch (err) {
-            console.error('Dify 请求失败:', err.response || err.response.data || err.message);
-            throw new Error('Dify 请求失败');
-        }
+        return await this.workflows(body);
     }
     }
 }
 }
 
 

+ 43 - 0
app/public/css/main.css

@@ -2350,3 +2350,46 @@ animation:shake 1s .2s ease both;}
     background: #ccc;
     background: #ccc;
     border-radius: 3px;
     border-radius: 3px;
 }
 }
+.ai-btn {
+    position: relative;
+    border: 1px solid rgba(255,255,255,0.15);
+    color: #fff;
+    cursor: pointer;
+    overflow: hidden;
+    background: linear-gradient(
+            270deg,
+            #a855f7,
+            #ec4899,
+            #f59e0b,
+            #a855f7
+    );
+    background-size: 400% 400%;
+    animation: aiGradient 6s ease infinite;
+    box-shadow:
+            0 0 6px rgba(0, 245, 255, 0.5),
+            0 0 12px rgba(139, 92, 246, 0.35);
+
+    transition: all 0.3s ease;
+}
+.ai-btn::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: -120%;
+    width: 80%;
+    height: 100%;
+    background: linear-gradient(
+            120deg,
+            transparent,
+            rgba(255,255,255,0.35),
+            transparent
+    );
+    transform: skewX(-25deg);
+    animation: shine 3s infinite;
+}
+.ai-btn:hover {
+    transform: translateY(-2px);
+    box-shadow:
+            0 0 8px rgba(0, 245, 255, 0.8),
+            0 0 14px rgba(139, 92, 246, 0.6);
+}

+ 10 - 4
app/public/js/ledger.js

@@ -62,6 +62,7 @@ const checkOption = {
 };
 };
 
 
 $(document).ready(function() {
 $(document).ready(function() {
+    const aiTrans = AITrans();
     const unit = ledgerSpreadSetting.cols.find(x => { return x.field === 'unit'; });
     const unit = ledgerSpreadSetting.cols.find(x => { return x.field === 'unit'; });
     if (unit) unit.comboEdit = true;
     if (unit) unit.comboEdit = true;
     let stdXmj, stdGcl, dealBills, searchLedger, featureDisplay, posDisplay;
     let stdXmj, stdGcl, dealBills, searchLedger, featureDisplay, posDisplay;
@@ -133,7 +134,6 @@ $(document).ready(function() {
     const ancGclSheet = ancGclSpread.getActiveSheet();
     const ancGclSheet = ancGclSpread.getActiveSheet();
 
 
     // 设计量明细相关
     // 设计量明细相关
-    const aiTrans = AITrans();
     const posCalcDetail = (function() {
     const posCalcDetail = (function() {
         const detail = createPosCalcDetail({ id: 'id', masterId: 'pid', topId: 'lid', sort: [['pcd_order', 'asc']]});
         const detail = createPosCalcDetail({ id: 'id', masterId: 'pid', topId: 'lid', sort: [['pcd_order', 'asc']]});
         const spread = SpreadJsObj.createNewSpread($('#pos-detail-spread')[0]);
         const spread = SpreadJsObj.createNewSpread($('#pos-detail-spread')[0]);
@@ -518,7 +518,7 @@ $(document).ready(function() {
             });
             });
         }
         }
 
 
-        return { detail, spread, sheet, refresh, loadCurDetailData, reloadCurDetailData }
+        return { detail, spread, sheet, refresh, loadCurDetailData, reloadCurDetailData, afterPostData: ctrlObj.afterPostData }
     })();
     })();
     const ancGclDetail = (function() {
     const ancGclDetail = (function() {
         const detail = createAncGclDetail({ id: 'id', masterId: 'ag_id', firstId: 'lid', secondId: 'pid', sort: [['agd_order', 'asc']]});
         const detail = createAncGclDetail({ id: 'id', masterId: 'ag_id', firstId: 'lid', secondId: 'pid', sort: [['agd_order', 'asc']]});
@@ -1732,6 +1732,8 @@ $(document).ready(function() {
                 $('#dqjiedian').find('.check-all-file').prop('checked', false);
                 $('#dqjiedian').find('.check-all-file').prop('checked', false);
             }
             }
             SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
             SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
+            const curNode = SpreadJsObj.getSelectObject(info.sheet);
+            aiTrans.setRelaInfo('bills', curNode, curNode);
         },
         },
         topRowChanged(e, info) {
         topRowChanged(e, info) {
             SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
             SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
@@ -3459,6 +3461,7 @@ $(document).ready(function() {
             posOperationObj.refreshOperationValid(posSpread.getActiveSheet());
             posOperationObj.refreshOperationValid(posSpread.getActiveSheet());
             posCalcDetail.loadCurDetailData();
             posCalcDetail.loadCurDetailData();
             ancGclObj.loadCurAncillaryGcl();
             ancGclObj.loadCurAncillaryGcl();
+            aiTrans.setRelaInfo('pos', SpreadJsObj.getSelectObject(info.sheet), SpreadJsObj.getSelectObject(ledgerSheet));
         },
         },
         addPegs: function (pegs) {
         addPegs: function (pegs) {
             if (!pegs || pegs.length <= 0) return;
             if (!pegs || pegs.length <= 0) return;
@@ -3991,6 +3994,7 @@ $(document).ready(function() {
         },
         },
         selectionChanged: function(e, info) {
         selectionChanged: function(e, info) {
             ancGclDetail.loadCurDetailData();
             ancGclDetail.loadCurDetailData();
+            aiTrans.setRelaInfo('ancGcl', SpreadJsObj.getSelectObject(info.sheet), SpreadJsObj.getSelectObject(ledgerSheet));
         },
         },
         removeCalcTemplate: function(node) {
         removeCalcTemplate: function(node) {
             const ancGclUpdateData = { calcTmpl: { id: node.id, calc_template: '' } };
             const ancGclUpdateData = { calcTmpl: { id: node.id, calc_template: '' } };
@@ -6473,7 +6477,9 @@ $(document).ready(function() {
             $('#binddskuser').modal('show');
             $('#binddskuser').modal('show');
         }
         }
     });
     });
-
+    $('.ai-btn').click(function() {
+        if (aiTrans.rela_type() === 'pos') aiTrans.transDetail(posCalcDetail.afterPostData);
+    });
 });
 });
 // 生成当前节点列表
 // 生成当前节点列表
 function getNodeList(node) {
 function getNodeList(node) {
@@ -6499,7 +6505,7 @@ function getNodeList(node) {
   }
   }
   $('#nodelist-table').html(html);
   $('#nodelist-table').html(html);
   $('#nodelist-table').on('click', 'tr', function() {
   $('#nodelist-table').on('click', 'tr', function() {
-      $('#nodelist-table tr').removeClass('bg-light')
+      $('#nodelist-table tr').removeClass('bg-light');
       $(this).addClass('bg-light')
       $(this).addClass('bg-light')
   })
   })
 }
 }

+ 68 - 13
app/public/js/shares/ai_trans.js

@@ -1,18 +1,18 @@
 const AITrans = function() {
 const AITrans = function() {
     const transResultObj = {
     const transResultObj = {
         spread: null,
         spread: null,
-        sheet: null,
+        transSheetData: [],
         initSpread: function() {
         initSpread: function() {
             if (!this.spread) {
             if (!this.spread) {
-                this.spread = SpreadJsObj.createNewSpread($('#ai-trans-spread')[0]);
-                this.sheet = this.spread.getActiveSheet();
+                this.spread = SpreadJsObj.createNewSpread($('#ai-trans-grid-spread')[0]);
+                this.spread.options.tabStripVisible = true;
             }
             }
         }
         }
     };
     };
-    $('#ai-trans').bind('shown.bs.modal', function() {
+    $('#ai-trans-grid').bind('shown.bs.modal', function() {
         transResultObj.spread.refresh();
         transResultObj.spread.refresh();
     });
     });
-    $('#ai-trans-file').change(function() {
+    $('#ai-trans-grid-file').change(function() {
         const files = this.files;
         const files = this.files;
         if (!files || files.length === 0) return;
         if (!files || files.length === 0) return;
 
 
@@ -38,19 +38,74 @@ const AITrans = function() {
             formData.append('file[]', file);
             formData.append('file[]', file);
         }
         }
         postDataWithFile('ai-trans', formData, function (data) {
         postDataWithFile('ai-trans', formData, function (data) {
-            const sheetParts = data.split('==++==');
+            transResultObj.transSheetData.length = 0;
+            const sheetParts = data.split('\n==++==\n');
             for (const sp of sheetParts) {
             for (const sp of sheetParts) {
-                const rows = sp.split('\n').map(r => { return r.split('|')});
-                console.log(rows);
-                SpreadJsObj.loadSimpleSheetData(transResultObj.sheet, rows);
+                const rows = sp.split('\n').filter(x => { return !!x; }).map(r => { return r.split('|')});
+                transResultObj.transSheetData.push(rows);
+            }
+            transResultObj.spread.setSheetCount(transResultObj.transSheetData.length);
+            for (const [i, sheetData] of transResultObj.transSheetData.entries()) {
+                SpreadJsObj.loadSimpleSheetData(transResultObj.spread.getSheet(i), sheetData);
             }
             }
         });
         });
     });
     });
-    const show = function(rela_type, rela_id) {
+    const show = function() {
+        if (!transResultObj.rela_id) toastr.warning('请先选择清单、计量单元、附属工程量后再使用');
+
         transResultObj.initSpread();
         transResultObj.initSpread();
-        transResultObj.rela_type = rela_type;
-        transResultObj.rela_id = rela_id;
+        $('#ai-trans-grid').modal('show');
+    };
+
+    $('#ai-trans-ok').click(function() {
+        const files = $('#ai-trans-file')[0].files;
+        if (!files || files.length === 0) return;
+
+        const formData = new FormData();
+        formData.append('rela_type', transResultObj.rela_type);
+        formData.append('rela_id', transResultObj.rela_id);
+        if (transResultObj.rela_type === 'ancGcl') {
+            formData.append('rela_template', transResultObj.rela_node.calc_template);
+        } else {
+            formData.append('rela_template', transResultObj.rela_bills.calc_template);
+        }
+        formData.append('rela_bills_name', transResultObj.rela_bills.name);
+        formData.append('rela_keyword', $('#ai-trans-keyword').val());
+        for (const file of files) {
+            if (file === undefined) {
+                toastr.error('未选择图片!');
+                return false;
+            }
+            const filesize = file.size;
+            if (filesize > 50 * 1024 * 1024) {
+                toastr.error('选择图片大小过大!');
+                return false;
+            }
+            const fileext = '.' + file.name.toLowerCase().split('.').splice(-1)[0];
+            if (whiteList.indexOf(fileext) === -1) {
+                toastr.error('只能上传指定格式的图片!');
+                return false;
+            }
+            formData.append('size', filesize);
+            formData.append('file[]', file);
+        }
+        postDataWithFile(window.location.pathname + '/aiComplie', formData, function (data) {
+            if (transResultObj.callback) transResultObj.callback(data);
+            $('#ai-trans').modal('hide');
+        });
+    });
+    const transDetail = function(callback) {
+        if (!transResultObj.rela_id) toastr.warning('请先选择清单、计量单元、附属工程量后再使用');
+        // todo 判断条件,调整
+        if (!transResultObj.rela_bills || transResultObj.rela_bills.name.indexOf('钢筋') === 0) toastr.warning('当前清单暂不支持');
+        transResultObj.callback = callback;
         $('#ai-trans').modal('show');
         $('#ai-trans').modal('show');
     };
     };
-    return { show }
+    const setRelaInfo = function(rela_type, rela_node, rela_bills) {
+        transResultObj.rela_type = rela_type;
+        transResultObj.rela_node = rela_node;
+        transResultObj.rela_id = rela_node ? rela_node.id : undefined;
+        transResultObj.rela_bills = rela_bills;
+    };
+    return { show, setRelaInfo, transDetail, rela_type: function() {return transResultObj.rela_type} }
 };
 };

+ 4 - 3
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -211,7 +211,7 @@ const SpreadJsObj = {
             return sheet.getRange(target.row, target.col, 1, 1);
             return sheet.getRange(target.row, target.col, 1, 1);
         } else if (target.hitTestType === spreadNS.SheetArea.corner) {
         } else if (target.hitTestType === spreadNS.SheetArea.corner) {
             return sheet.getRange(-1, -1, sheet.getRowCount(), sheet.getColumnCount());
             return sheet.getRange(-1, -1, sheet.getRowCount(), sheet.getColumnCount());
-        };
+        }
     },
     },
     getCellInSelections: function (selections, row, col) {
     getCellInSelections: function (selections, row, col) {
         const count = selections.length;
         const count = selections.length;
@@ -1002,7 +1002,7 @@ const SpreadJsObj = {
                 // 单元格重新写入数据
                 // 单元格重新写入数据
                 this._loadRowData(sheet, data, row);
                 this._loadRowData(sheet, data, row);
                 sheet.setRowVisible(row, data.visible);
                 sheet.setRowVisible(row, data.visible);
-            };
+            }
             this.endMassOperation(sheet);
             this.endMassOperation(sheet);
         } catch (err) {
         } catch (err) {
             this.endMassOperation(sheet);
             this.endMassOperation(sheet);
@@ -1156,7 +1156,8 @@ const SpreadJsObj = {
             // 设置总行数
             // 设置总行数
             const totalRow = sortData.length;
             const totalRow = sortData.length;
             sheet.setRowCount(totalRow, spreadNS.SheetArea.viewport);
             sheet.setRowCount(totalRow, spreadNS.SheetArea.viewport);
-            if (sortData && sortData.length > 0) sheet.setColumnCount(data[0].length, spreadNS.SheetArea.viewport);
+            const totalCol = sortData.map(x => { return x.length; }).reduce((x, y) => { return math.max(x, y)});
+            if (sortData && sortData.length > 0) sheet.setColumnCount(totalCol, spreadNS.SheetArea.viewport);
 
 
             if (sortData) {
             if (sortData) {
                 // 单元格写入数据
                 // 单元格写入数据

+ 1 - 0
app/router.js

@@ -701,6 +701,7 @@ module.exports = app => {
     app.post('/tender/:id/ledger/deal2sgfh', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.deal2sgfh');
     app.post('/tender/:id/ledger/deal2sgfh', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.deal2sgfh');
     app.post('/tender/:id/ledger/sgfh2deal', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.sgfh2deal');
     app.post('/tender/:id/ledger/sgfh2deal', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.sgfh2deal');
     app.post('/tender/:id/ledger/check', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.check');
     app.post('/tender/:id/ledger/check', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.check');
+    app.post('/tender/:id/ledger/aiComplie', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.aiComplie');
     app.post('/tender/:id/measure/ledger/auditors', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.ledgerAuditors');
     app.post('/tender/:id/measure/ledger/auditors', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'ledgerController.ledgerAuditors');
 
 
     // 台账附件
     // 台账附件

+ 1 - 1
app/service/pos_calc_detail.js

@@ -154,7 +154,7 @@ module.exports = app => {
             const insertData = [], updateData = [];
             const insertData = [], updateData = [];
             let isCalc = false;
             let isCalc = false;
             for (const [i, d] of datas.entries()) {
             for (const [i, d] of datas.entries()) {
-                if (!d.lid || !d.pid || !d.pcd_order) throw '新增设计量明细数据,提交的数据错误';
+                if (!d.lid || !d.pid) throw '新增设计量明细数据,提交的数据错误';
                 const nd = {
                 const nd = {
                     id: this.uuid.v4(), tid: this.ctx.tender.id,
                     id: this.uuid.v4(), tid: this.ctx.tender.id,
                     create_user_id: user_id, update_user_id: user_id,
                     create_user_id: user_id, update_user_id: user_id,

+ 5 - 0
app/view/ledger/explode.ejs

@@ -129,6 +129,11 @@
                                         </div>
                                         </div>
                                     </div>
                                     </div>
                                 </li>
                                 </li>
+                                <li class="nav-item ml-3">
+                                    <div class="d-inline-block">
+                                        <button href="javascript: void(0)" class="btn btn-primary btn-sm pull-right mr-2 ai-btn">✨AI识别填写</button>
+                                    </div>
+                                </li>
                             </ul>
                             </ul>
                         </div>
                         </div>
                         <div class="ml-auto">
                         <div class="ml-auto">

+ 34 - 3
app/view/shares/ai_trans.ejs

@@ -1,4 +1,4 @@
-<div class="modal fade" id="ai-trans" data-backdrop="static">
+<div class="modal fade" id="ai-trans-grid" data-backdrop="static">
     <div class="modal-dialog modal-lg" role="document">
     <div class="modal-dialog modal-lg" role="document">
         <div class="modal-content">
         <div class="modal-content">
             <div class="modal-header">
             <div class="modal-header">
@@ -9,12 +9,43 @@
             </div>
             </div>
             <div class="modal-body">
             <div class="modal-body">
                 <div class="form-group">
                 <div class="form-group">
+                    <label for="ai-trans-grid-file">选择文件</label><i class="fa fa-spinner fa-pulse fa-lg fa-fw text-primary" id="select-excel-loading" style="display: none;"></i>
+                    <input type="file" class="form-control-file" id="ai-trans-grid-file" accept="*.json" name="file">
+                </div>
+            </div>
+            <div class="modal-body">
+                <div id="ai-trans-grid-spread" class="modal-height-500"></div>
+            </div>
+        </div>
+    </div>
+</div>
+<div class="modal fade" id="ai-trans" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">AI识别</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
                     <label for="ai-trans-file">选择文件</label><i class="fa fa-spinner fa-pulse fa-lg fa-fw text-primary" id="select-excel-loading" style="display: none;"></i>
                     <label for="ai-trans-file">选择文件</label><i class="fa fa-spinner fa-pulse fa-lg fa-fw text-primary" id="select-excel-loading" style="display: none;"></i>
                     <input type="file" class="form-control-file" id="ai-trans-file" accept="*.json" name="file">
                     <input type="file" class="form-control-file" id="ai-trans-file" accept="*.json" name="file">
                 </div>
                 </div>
+                <div class="form-group">
+                    <label for="ai-trans-file">高级设置</label>
+                    <div class="input-group input-group-sm ml-2 mt-1">
+                        <div class="input-group-prepend">
+                            <span class="input-group-text">仅识别:</span>
+                        </div>
+                        <input type="text" class="form-control mr-3" style="width: 240px" value="" id="ai-trans-keyword">
+                    </div>
+                </div>
             </div>
             </div>
-            <div class="modal-body">
-                <div id="ai-trans-spread" class="modal-height-500"></div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <a href="javascript: void(0);" class="btn btn-sm btn-primary" id="ai-trans-ok">确定</a>
             </div>
             </div>
         </div>
         </div>
     </div>
     </div>