knowledgelibview.html.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. <?php
  2. namespace zin;
  3. $this->app->loadLang('ai');
  4. /* 当前类型的名称 */
  5. $typeLabel = '';
  6. if(isset($lang->ai->knowledgeLibs->knowledgeObjectTypes[$objectType]['label']))
  7. {
  8. $typeLabel = $lang->ai->knowledgeLibs->knowledgeObjectTypes[$objectType]['label'];
  9. }
  10. elseif(isset($lang->ai->knowledgeLibs->knowledgeObjectImportTypes[$objectType]['label']))
  11. {
  12. $typeLabel = $lang->ai->knowledgeLibs->knowledgeObjectImportTypes[$objectType]['label'];
  13. }
  14. jsVar('confirmDeleteFile', $lang->ai->knowledgeLibs->confirmDeleteFile);
  15. jsVar('confirmBatchDelete', sprintf($lang->ai->knowledgeLibs->confirmBatchDelete, $typeLabel));
  16. jsVar('noItemSelected', $lang->ai->knowledgeLibs->noItemSelected);
  17. jsVar('confirmPublish', $lang->ai->knowledgeLibs->confirmPublish);
  18. jsVar('confirmUnpublish', $lang->ai->knowledgeLibs->confirmUnpublish);
  19. jsVar('syncFailedText', $lang->ai->knowledgeLibs->syncFailed);
  20. jsVar('syncFailedHint', $lang->ai->knowledgeLibs->syncFailedHint);
  21. jsVar('knowledgeLibId', $knowledgeLib->id);
  22. jsVar('knowledgeItemId', $contentID);
  23. jsVar('knowledgeFrom', $from);
  24. jsVar('knowledgeLib', $knowledgeLib);
  25. /* 构建自定义添加菜单配置 */
  26. $buildCustomMenuConfig = function() use ($lang, $textFileItems)
  27. {
  28. $menuConfig = array();
  29. /* 自定义文本 */
  30. $textChildren = array();
  31. if(!empty($textFileItems['text']))
  32. {
  33. foreach($textFileItems['text'] as $item) $textChildren[] = array('id' => $item->id, 'text' => $item->title);
  34. }
  35. if(!empty($textChildren))
  36. {
  37. $menuConfig[] = array('text' => $lang->ai->knowledgeLibs->customText, 'icon' => 'file-text', 'type' => 'text', 'children' => $textChildren);
  38. }
  39. /* 本地文件 */
  40. $fileChildren = array();
  41. if(!empty($textFileItems['file']))
  42. {
  43. foreach($textFileItems['file'] as $item) $fileChildren[] = array('id' => $item->id, 'text' => $item->title);
  44. }
  45. if(!empty($fileChildren))
  46. {
  47. $menuConfig[] = array('text' => $lang->ai->knowledgeLibs->localFile, 'icon' => 'folder-o', 'type' => 'file', 'children' => $fileChildren);
  48. }
  49. return $menuConfig;
  50. };
  51. /* 构建禅道对象数据菜单配置 */
  52. $buildZentaoDataMenuConfig = function() use ($lang)
  53. {
  54. $menuConfig = array();
  55. /* 禅道对象 */
  56. foreach($lang->ai->knowledgeLibs->knowledgeObjectTypes as $itemType => $item)
  57. {
  58. $menuConfig[] = array('text' => $item['label'], 'icon' => $item['icon'], 'type' => $itemType, 'className' => 'kb-item-' . $itemType);
  59. }
  60. /* 资产库导入 */
  61. foreach($lang->ai->knowledgeLibs->knowledgeObjectImportTypes as $importItemType => $item)
  62. {
  63. $menuConfig[] = array('text' => $item['label'], 'icon' => $item['icon'], 'type' => $importItemType, 'className' => 'kb-item-' . $importItemType);
  64. }
  65. return $menuConfig;
  66. };
  67. /* 构建菜单项 */
  68. $buildMenuItems = function($config) use ($knowledgeLib, $type, $objectType, $contentID, $stats)
  69. {
  70. $items = array();
  71. foreach($config as $item)
  72. {
  73. $menuItem = array('id' => $item['type'], 'text' => $item['text'], 'icon' => $item['icon']);
  74. if(isset($item['children']))
  75. {
  76. $menuItem['className'] = 'kb-' . $item['type'];
  77. $menuItem['children'] = array();
  78. foreach($item['children'] as $child)
  79. {
  80. $isActive = ($type === $item['type'] && $contentID == $child['id']);
  81. $menuItem['children'][] = array(
  82. 'id' => $child['id'],
  83. 'text' => $child['text'],
  84. 'active' => $isActive,
  85. 'url' => inlink('knowledgelibview', "id={$knowledgeLib->id}&type={$item['type']}&contentID={$child['id']}")
  86. );
  87. }
  88. $items[] = $menuItem;
  89. }
  90. else
  91. {
  92. if(!in_array($item['type'], $stats->objectTypes)) continue;
  93. $isActive = ($objectType === $item['type']);
  94. $menuItem['className'] = 'kb-item-' . $item['type'];
  95. $menuItem['active'] = $isActive;
  96. $menuItem['url'] = inlink('knowledgelibview', "id={$knowledgeLib->id}&type=object_{$item['type']}");
  97. $items[] = $menuItem;
  98. }
  99. }
  100. return $items;
  101. };
  102. $selectedId = $type === 'object' ? $objectType : $contentID;
  103. $canAIChat = hasPriv('ai', 'aichatwithknowledgelib');
  104. $canSearch = hasPriv('ai', 'searchknowledgelib');
  105. $switchPublish = $knowledgeLib->published == '1' ? hasPriv('ai', 'unpublishknowledgelib') : hasPriv('ai', 'publishknowledgelib');
  106. $canCreateKnowledge = hasPriv('ai', 'createknowledge');
  107. $canDeleteKnowledge = hasPriv('ai', 'deleteknowledgeitem');
  108. $canEditKnowledge = hasPriv('ai', 'editknowledge');
  109. $showActions = $canAIChat || $canSearch || $switchPublish;
  110. $headerActions = array();
  111. if(!$isEmpty && $showActions)
  112. {
  113. $headerActions = array(
  114. $knowledgeLib->published == '1' && $canAIChat ? btn(
  115. set::icon('ai'),
  116. set::text($lang->ai->knowledgeLibs->aiChat),
  117. set::type('ghost'),
  118. set::size('small'),
  119. on::click()->call('openAIChatWithKnowledgeLib')
  120. ) : null,
  121. $canSearch ? btn(
  122. set::icon('search'),
  123. set::text($lang->ai->knowledgeLibs->searchTest),
  124. set::type('ghost'),
  125. set::size('small'),
  126. set::url(createLink('ai', 'searchknowledgelib', "knowledgeLibID={$knowledgeLib->id}&type=$type&contentID=$contentID"))
  127. ) : null,
  128. $switchPublish ? btn(
  129. set::icon($knowledgeLib->published == '1' ? 'cancel' : 'publish'),
  130. set::text($knowledgeLib->published == '1' ? $lang->ai->prompts->action->unpublish : $lang->ai->prompts->action->publish),
  131. set::type('ghost'),
  132. set::size('small'),
  133. on::click()->call($knowledgeLib->published == '1' ? 'unpublishKnowledgeLib' : 'publishKnowledgeLib')
  134. ) : null
  135. );
  136. }
  137. /* Permission Config. */
  138. $privs = array();
  139. $privs['taskBrowse'] = hasPriv('execution', 'task');
  140. $privs['productStory'] = hasPriv('product', 'browse');
  141. $privs['projectStory'] = hasPriv('projectstory', 'story');
  142. $privs['executionStory'] = hasPriv('execution', 'story');
  143. $privs['productplanView'] = hasPriv('productplan', 'view');
  144. $privs['requirementBrowse'] = hasPriv('product', 'requirement');
  145. $privs['epicBrowse'] = hasPriv('product', 'epic');
  146. $privs['productBug'] = hasPriv('bug', 'browse');
  147. $privs['releaseBrowse'] = hasPriv('release', 'browse');
  148. $privs['projectReleaseBrowse'] = hasPriv('projectRelease', 'browse');
  149. $privs['productplanBrowse'] = hasPriv('productplan', 'browse');
  150. $privs['feedbackBrowse'] = hasPriv('feedback', 'admin');
  151. $privs['ticketBrowse'] = hasPriv('ticket', 'browse');
  152. /* 构建创建菜单项 */
  153. $buildCreateItems = function($menuConfig) use ($knowledgeLib, $lang, $privs, $from, $config)
  154. {
  155. $createItems = array();
  156. foreach($lang->ai->knowledgeLibs->knowledgeTypes as $knowledgeType => $menuConfig)
  157. {
  158. $menuItem = array('icon' => $menuConfig['icon'], 'text' => $menuConfig['label'], 'className' => 'kb-item-' . $knowledgeType);
  159. if(!empty($menuConfig['action']))
  160. {
  161. $menuItem['data-toggle'] = 'modal';
  162. $menuItem['data-size'] = $menuConfig['action'] == 'adddoc' ? 'lg' : 'md';
  163. $menuItem['url'] = createLink('ai', $menuConfig['action'], "lib={$knowledgeLib->id}&from={$from}");
  164. }
  165. $createItems[] = $menuItem;
  166. }
  167. foreach($lang->ai->knowledgeLibs->knowledgeObjectTypes as $itemObjectType => $itemConfig)
  168. {
  169. $priv = isset($itemConfig['priv']) ? $itemConfig['priv'] : null;
  170. if($priv && isset($privs[$priv]) && !$privs[$priv]) continue;
  171. $menuItem = array('icon' => $itemConfig['icon'], 'text' => $itemConfig['label'], 'className' => 'kb-item-' . $itemObjectType);
  172. if(!empty($itemConfig['module']))
  173. {
  174. $menuItem['url'] = createLink($itemConfig['module'], $itemConfig['method'], $itemConfig['params']);
  175. $menuItem['data-toggle'] = 'modal';
  176. $menuItem['data-size'] = 'lg';
  177. }
  178. if(isset($lang->ai->knowledgeLibs->knowledgeObjectSubTypes[$itemObjectType]))
  179. {
  180. $subItems = array();
  181. foreach($lang->ai->knowledgeLibs->knowledgeObjectSubTypes[$itemObjectType] as $subType => $subLabel)
  182. {
  183. $priv = isset($subLabel['priv']) ? $subLabel['priv'] : null;
  184. if($priv && isset($privs[$priv]) && !$privs[$priv]) continue;
  185. if($subLabel['key'] == 'ER' && !$config->enableER) continue;
  186. if($subLabel['key'] == 'UR' && !$config->URAndSR) continue;
  187. $subItem = array('text' => is_array($subLabel) ? $subLabel['label'] : $subLabel);
  188. if(!empty($itemConfig['action']))
  189. {
  190. $subItem['innerClass'] = 'ajax-get';
  191. $subItem['url'] = createLink('ai', $itemConfig['action'], "lib={$knowledgeLib->id}&objectType={$itemObjectType}&subType={$subType}&from={$from}");
  192. }
  193. if(is_array($subLabel) && !empty($subLabel['module']))
  194. {
  195. $subItem['url'] = createLink($subLabel['module'], $subLabel['method'], $subLabel['params']);
  196. $subItem['data-toggle'] = 'modal';
  197. $subItem['data-size'] = 'lg';
  198. }
  199. $subItems[] = $subItem;
  200. }
  201. $menuItem['items'] = $subItems;
  202. }
  203. else if(!empty($itemConfig['action']))
  204. {
  205. $menuItem['data-toggle'] = 'modal';
  206. $menuItem['data-size'] = $itemConfig['action'] == 'adddoc' ? 'lg' : 'md';
  207. $menuItem['url'] = createLink('ai', $itemConfig['action'], "lib={$knowledgeLib->id}&from={$from}");
  208. }
  209. $createItems[] = $menuItem;
  210. }
  211. return $createItems;
  212. };
  213. /* 构建选择提示 */
  214. $buildVoidView = function($message)
  215. {
  216. return div(
  217. setClass('void-view'),
  218. $message
  219. );
  220. };
  221. /* 构建文本/文件视图 */
  222. $buildTextView = function($knowledge) use ($lang)
  223. {
  224. return div
  225. (
  226. setClass('section-content'),
  227. div
  228. (
  229. setClass('show-content'),
  230. zui::AIKnowledgeChunkList
  231. (
  232. set::id($knowledge->id),
  233. set::needSyncKnowledgeItemText($lang->ai->knowledgeLibs->needSyncKnowledgeItem),
  234. set::syncingKnowledgeItemText($lang->ai->knowledgeLibs->syncingKnowledgeItem),
  235. set::emptyKnowledgeDataText($lang->ai->knowledgeLibs->emptyKnowledgeData)
  236. )
  237. ),
  238. div
  239. (
  240. setClass('origin-content hidden'),
  241. setStyle(array('max-height' => 'calc(100vh - 280px)')),
  242. editor
  243. (
  244. set::maxHeight('calc(100vh - 280px)'),
  245. set::resizable(false),
  246. set::name('content'),
  247. set::markdown($knowledge->contentType == 'markdown'),
  248. set::uploadUrl('disabled'),
  249. html($knowledge->content)
  250. ),
  251. div
  252. (
  253. setClass('bg-canvas text-center mt-2 actions'),
  254. setData(array('knowledgeID' => $knowledge->id, 'knowledgeTitle' => $knowledge->title)),
  255. btn(
  256. set::type('primary'),
  257. set::text($lang->save),
  258. on::click()->call('saveTextKnowledge')
  259. )
  260. )
  261. )
  262. );
  263. };
  264. /* 构建对象视图 */
  265. $buildObjectView = function($objectType, $objectList, $objectCols) use ($lang, $knowledgeLib, $pager, $users)
  266. {
  267. $objectList = initTableData($objectList, $objectCols, null);
  268. $footToolbar = hasPriv('ai', 'deleteknowledgeitem') ? array(
  269. 'items' => array(
  270. array(
  271. 'text' => $lang->delete,
  272. 'data-on' => 'click',
  273. 'data-call' => "handleBatchDeleteBtnClick('.dtable')"
  274. )
  275. ),
  276. 'btnProps' => array('size' => 'sm', 'btnType' => 'secondary')
  277. ) : null;
  278. $dtablePager = $pager ? usePager(array(
  279. 'recPerPage' => $pager->recPerPage,
  280. 'recTotal' => $pager->recTotal,
  281. 'linkCreator' => createLink('ai', 'knowledgelibview', "id={$knowledgeLib->id}&type=object_{$objectType}&contentID=0&recTotal={recTotal}&recPerPage={recPerPage}&pageID={page}")
  282. )) : false;
  283. $dtableProps = [
  284. 'id' => 'knowledgeObjectList',
  285. 'cols' => $objectCols,
  286. 'data' => $objectList,
  287. 'userMap' => $users,
  288. 'footPager' => $dtablePager,
  289. 'footToolbar' => $footToolbar,
  290. 'emptyTip' => $lang->noData,
  291. 'checkable' => hasPriv('ai', 'deleteknowledgeitem'),
  292. 'extraHeight' => '+,#mainContent>.detail-header,.page-kbview>.main-content>.section-header,36',
  293. 'onRenderCell' => jsRaw('window.renderObjectTableCell')
  294. ];
  295. return dtable(set($dtableProps));
  296. };
  297. $customMenuConfig = $buildCustomMenuConfig();
  298. /* 构建主视图 */
  299. $buildMainView = function() use ($selectedId, $lang, $type, $objectType, $knowledgeData, $objectList, $objectCols, $knowledgeLib, $canEditKnowledge, $canDeleteKnowledge, $buildVoidView, $buildTextView, $buildObjectView, $buildZentaoDataMenuConfig, $customMenuConfig, $buildMenuItems)
  300. {
  301. $mainContent = null;
  302. $knowledge = $knowledgeData;
  303. $zentaoMenuConfig = $buildZentaoDataMenuConfig();
  304. $customMenuItems = $buildMenuItems($customMenuConfig);
  305. $zentaoMenuItems = $buildMenuItems($zentaoMenuConfig);
  306. if(empty($type) || empty($knowledge))
  307. {
  308. $mainContent = $buildVoidView($lang->ai->knowledgeLibs->selectContent);
  309. }
  310. else
  311. {
  312. switch($type) {
  313. case 'text':
  314. case 'file':
  315. $mainContent = $buildTextView($knowledge);
  316. break;
  317. case 'object':
  318. $mainContent = $buildObjectView($objectType, $objectList, $objectCols);
  319. break;
  320. default:
  321. $mainContent = $buildVoidView($lang->ai->knowledgeLibs->selectContent);
  322. }
  323. }
  324. return div(
  325. setClass('page-kbview'),
  326. sidebar(
  327. setClass('sidebar-left'),
  328. set::width(220),
  329. set::minWidth(220),
  330. panel(
  331. tabs(
  332. tabPane(
  333. set::key('custom'),
  334. set::title($lang->ai->knowledgeLibs->customAdd),
  335. set::active($type !== 'object'),
  336. treeEditor(
  337. set::id('knowledgeTree'),
  338. set::items($customMenuItems),
  339. set::selected($selectedId),
  340. set::defaultNestedShow(true),
  341. set::canSplit(false),
  342. set::collapsedIcon('folder text-warning'),
  343. set::expandedIcon('folder-open text-warning'),
  344. set::normalIcon(''),
  345. set::preserve(false)
  346. )
  347. ),
  348. tabPane(
  349. set::key('zentao'),
  350. set::title($lang->ai->knowledgeLibs->zentaoData),
  351. set::active($type === 'object'),
  352. treeEditor(
  353. set::id('knowledgeTree'),
  354. set::items($zentaoMenuItems),
  355. set::selected($selectedId),
  356. set::defaultNestedShow(true),
  357. set::canSplit(false),
  358. set::collapsedIcon('folder text-warning'),
  359. set::expandedIcon('folder-open text-warning'),
  360. set::normalIcon(''),
  361. set::preserve(false)
  362. )
  363. )
  364. )
  365. )
  366. ),
  367. div(
  368. setClass('main-content min-w-0'),
  369. $knowledge ? div(
  370. setClass('section-header'),
  371. h2(span(
  372. set::title($knowledge->title),
  373. $knowledge->title
  374. ), $type != 'object' ? input(
  375. set::type('text'),
  376. setClass('size-sm'),
  377. set::value($knowledge->title),
  378. set::maxlength(255),
  379. on::blur()->call('updateName')
  380. ) : null, $type != 'object' && $canEditKnowledge ? btn(
  381. setClass('edit-btn'),
  382. set::type('ghost'),
  383. set::icon('edit'),
  384. on::click()->call('toggleEditMode')
  385. ) : null),
  386. $type == 'object' ? div(
  387. setClass('actions'),
  388. $canEditKnowledge ? btn(
  389. setID('syncObjectListBtn'),
  390. set::type('ghost'),
  391. set::icon('refresh'),
  392. set::text($lang->ai->knowledgeLibs->updateListData),
  393. set::hint($lang->ai->knowledgeLibs->updateFromSourceData),
  394. on::click()->call('requestSyncObjectListToZAI', $knowledgeLib->id, $objectType, false, true)
  395. ): null
  396. ) : div(
  397. setClass('actions'),
  398. $canEditKnowledge && $type == 'text' ? div(
  399. setClass('show-source'),
  400. span($lang->ai->knowledgeLibs->viewSourceData),
  401. switcher(on::change()->call('toggleShowSource', jsRaw('event')))
  402. ) : null,
  403. $type == 'text' ? divider() : null,
  404. $canDeleteKnowledge ? btn(setClass('ajax-submit'), set::type('ghost'), set::icon('trash'), set::hint($lang->delete), set::url(createLink('ai', 'deleteknowledgeitem', "id=$selectedId&knowledgeLibID=$knowledgeLib->id")), setData('confirm', $lang->ai->knowledgeLibs->confirmDeleteFile)) : null
  405. )
  406. ) : null,
  407. $mainContent
  408. )
  409. );
  410. };
  411. $createMenuItems = $buildCreateItems($customMenuConfig);
  412. /* 构建空视图 */
  413. $buildEmptyView = function() use ($lang, $canCreateKnowledge, $createMenuItems)
  414. {
  415. $title = $lang->ai->knowledgeLibs->noPrivAddKnowledge;
  416. $actions = array();
  417. $menuItems = array();
  418. if($canCreateKnowledge)
  419. {
  420. $title = $lang->ai->knowledgeLibs->addKnowledgeTip;
  421. $menuItems = $createMenuItems;
  422. }
  423. foreach($menuItems as $menuItem)
  424. {
  425. if(isset($menuItem['items']) && !empty($menuItem['items']))
  426. {
  427. $actions[] = dropdown(
  428. btn(
  429. setClass($menuItem['className']),
  430. set::icon($menuItem['icon']),
  431. set::text($menuItem['text'])
  432. ),
  433. set::items($menuItem['items']),
  434. set::placement('bottom-center')
  435. );
  436. }
  437. else
  438. {
  439. $btnProps = array(setClass($menuItem['className']), set::icon($menuItem['icon']), set::text($menuItem['text']));
  440. if(!empty($menuItem['url']))
  441. {
  442. $btnProps[] = set('href', $menuItem['url']);
  443. $btnProps[] = setData(array('toggle' => $menuItem['data-toggle'], 'size' => $menuItem['data-size']));
  444. }
  445. $actions[] = btn(...$btnProps);
  446. }
  447. }
  448. return div
  449. (
  450. setClass('empty-view'),
  451. h2($title),
  452. div(setClass('add-actions'), $actions)
  453. );
  454. };
  455. detailHeader(
  456. to::prefix(
  457. backBtn(
  458. set::icon('back'),
  459. set::type('secondary'),
  460. set::url(inlink($from)),
  461. $lang->goback
  462. ),
  463. entityLabel(
  464. set(array(
  465. 'entityID' => $knowledgeLib->id,
  466. 'level' => 1,
  467. 'text' => $knowledgeLib->name,
  468. 'title' => $knowledgeLib->name
  469. ))
  470. )
  471. ),
  472. to::suffix(
  473. div(
  474. setClass('flex items-center gap-x-1'),
  475. $headerActions,
  476. $canCreateKnowledge ? dropdown(
  477. btn(set::icon('plus'), set::text($lang->ai->knowledgeLibs->addKnowledge), set::type('primary')),
  478. set::placement('bottom-end'),
  479. set::items($createMenuItems)
  480. ) : null
  481. )
  482. )
  483. );
  484. if($isEmpty)
  485. {
  486. $buildEmptyView();
  487. }
  488. else
  489. {
  490. $buildMainView();
  491. }