AI_view.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. const { div, h2, p, section, button, form, textarea, br, span, input } = require("../server/node_modules/hyperaxe");
  2. const { template, i18n } = require('./main_views');
  3. const { renderUrl } = require('../backend/renderUrl');
  4. exports.aiView = (history = [], userPrompt = '') => {
  5. return template(
  6. i18n.aiTitle,
  7. section(
  8. div({ class: "tags-header" },
  9. h2(i18n.aiTitle),
  10. p(i18n.aiDescription),
  11. userPrompt ? div({ class: 'user-prompt', style: 'margin-bottom: 2em; font-size: 0.95em; color: #888;' },
  12. `${i18n.aiPromptUsed || 'System Prompt'}: `,
  13. span({ style: 'font-style: italic;' }, `"${userPrompt}"`)
  14. ) : null,
  15. form({ method: 'POST', action: '/ai', style: "margin-bottom: 0;" },
  16. textarea({ name: 'input', rows: 4, placeholder: i18n.aiInputPlaceholder, required: true }),
  17. br(),
  18. div({ style: "display: flex; gap: 1.5em; justify-content: flex-end; align-items: center; margin-top: 0.7em;" },
  19. button({ type: 'submit' }, i18n.aiSubmitButton)
  20. )
  21. ),
  22. div({ style: "display: flex; justify-content: flex-end; margin-bottom: 2em;" },
  23. form({ method: 'POST', action: '/ai/clear', style: "display: inline;" },
  24. button({
  25. type: 'submit',
  26. style: `
  27. background: #b80c09;
  28. color: #fff;
  29. border: none;
  30. padding: 0.4em 1.2em;
  31. border-radius: 6px;
  32. cursor: pointer;
  33. font-size: 1em;
  34. margin-left: 1em;
  35. `
  36. }, i18n.aiClearHistory || 'Clear chat history')
  37. )
  38. ),
  39. br(),
  40. ...history.map(entry =>
  41. div({
  42. class: 'chat-entry',
  43. style: `
  44. margin-bottom: 2em;
  45. position: relative;
  46. background: #191919;
  47. border-radius: 10px;
  48. box-shadow: 0 0 8px #0004;
  49. padding-top: 1.8em;
  50. `
  51. },
  52. entry.timestamp ? span({
  53. style: `
  54. position: absolute;
  55. top: 0.5em;
  56. right: 1.3em;
  57. font-size: 0.92em;
  58. color: #888;
  59. `
  60. }, new Date(entry.timestamp).toLocaleString()) : null,
  61. br(), br(),
  62. div({ class: 'user-question', style: 'margin-bottom: 0.75em;' },
  63. h2(`${i18n.aiUserQuestion}:`),
  64. p(...renderUrl(entry.question))
  65. ),
  66. div({
  67. class: 'ai-response',
  68. style: `
  69. max-width: 800px;
  70. margin: auto;
  71. background: #111;
  72. padding: 1.25em;
  73. border-radius: 6px;
  74. font-family: sans-serif;
  75. line-height: 1.6;
  76. color: #ffcc00;
  77. `
  78. },
  79. h2(`${i18n.aiResponseTitle}:`),
  80. ...String(entry.answer || '')
  81. .split('\n\n')
  82. .flatMap(paragraph =>
  83. paragraph
  84. .split('\n')
  85. .map(line =>
  86. p({ style: "margin-bottom: 1.2em;" }, ...renderUrl(line.trim()))
  87. )
  88. )
  89. ),
  90. div({
  91. class: 'ai-train-bar',
  92. style: `
  93. display:flex;
  94. align-items:center;
  95. gap:12px;
  96. margin: 12px auto 8px auto;
  97. max-width: 800px;
  98. padding: 8px 0;
  99. border-top: 1px solid #2a2a2a;
  100. `
  101. },
  102. Array.isArray(entry.snippets) && entry.snippets.length
  103. ? span({ style: 'color:#9aa; font-size:0.95em;' }, `${i18n.aiSnippetsUsed}: ${entry.snippets.length}`)
  104. : null,
  105. h2(`${i18n.statsAITraining}:`),
  106. entry.trainStatus === 'approved'
  107. ? span({ style: 'color:#5ad25a; font-weight:600;' }, i18n.aiTrainApproved)
  108. : entry.trainStatus === 'rejected'
  109. ? span({ style: 'color:#ff6b6b; font-weight:600;' }, i18n.aiTrainRejected)
  110. : null,
  111. entry.trainStatus === 'approved' || entry.trainStatus === 'rejected'
  112. ? null
  113. : form({ method: 'POST', action: '/ai/approve', style: 'display:inline-block;' },
  114. input({ type: 'hidden', name: 'ts', value: String(entry.timestamp) }),
  115. button({ type: 'submit', class: 'approve-btn', style: 'background:#1e7e34;color:#fff;border:none;padding:0.45em 0.9em;border-radius:6px;cursor:pointer;' }, i18n.aiApproveTrain)
  116. ),
  117. entry.trainStatus === 'approved' || entry.trainStatus === 'rejected'
  118. ? null
  119. : form({ method: 'POST', action: '/ai/reject', style: 'display:inline-block;' },
  120. input({ type: 'hidden', name: 'ts', value: String(entry.timestamp) }),
  121. button({ type: 'submit', class: 'reject-btn', style: 'background:#a71d2a;color:#fff;border:none;padding:0.45em 0.9em;border-radius:6px;cursor:pointer;' }, i18n.aiRejectTrain)
  122. )
  123. )
  124. )
  125. )
  126. )
  127. )
  128. );
  129. };