From 1ae7550d77ec6ff3265a1674cb51cbd76d09d169 Mon Sep 17 00:00:00 2001 From: nguyentrungthat <80239428+nguentrungthat@users.noreply.github.com> Date: Wed, 6 May 2026 10:32:45 +0700 Subject: [PATCH] Add Prompt AI model, API, seeds and UI Introduce PromptAi support: add model, migration, seeder and controller with full CRUD endpoints, and register routes under /api/prompt-ai. Integrate DB-driven prompts into LineConnection (replace hardcoded prompt strings for DPELP and ENV with fetched PromptAi records). Update frontend ModalConfig to add a Prompt AI management tab (fetch, create, edit prompts), plus related UI tweaks (tabs, prompt editor modal, axios/notifications). This makes AI prompts editable at runtime without code changes. --- .../app/controllers/prompt_ais_controller.ts | 135 +++++++ BACKEND/app/models/prompt_ai.ts | 28 ++ BACKEND/app/services/line_connection.ts | 67 +--- .../1776031200000_create_prompt_ais_table.ts | 19 + BACKEND/database/seeders/index.ts | 8 + BACKEND/database/seeders/prompt_ai_seeder.ts | 323 +++++++++++++++ BACKEND/start/routes.ts | 10 + FRONTEND/src/components/Modal/ModalConfig.tsx | 370 ++++++++++++++---- 8 files changed, 843 insertions(+), 117 deletions(-) create mode 100644 BACKEND/app/controllers/prompt_ais_controller.ts create mode 100644 BACKEND/app/models/prompt_ai.ts create mode 100644 BACKEND/database/migrations/1776031200000_create_prompt_ais_table.ts create mode 100644 BACKEND/database/seeders/index.ts create mode 100644 BACKEND/database/seeders/prompt_ai_seeder.ts diff --git a/BACKEND/app/controllers/prompt_ais_controller.ts b/BACKEND/app/controllers/prompt_ais_controller.ts new file mode 100644 index 0000000..ca289f0 --- /dev/null +++ b/BACKEND/app/controllers/prompt_ais_controller.ts @@ -0,0 +1,135 @@ +import PromptAi from '#models/prompt_ai' +import type { HttpContext } from '@adonisjs/core/http' + +export default class PromptAisController { + /** + * Display a list of all prompt AIs + */ + async get({}: HttpContext) { + const promptAis = await PromptAi.all() + return { status: true, data: promptAis } + } + + /** + * Get prompt AI by type + */ + async getByType({ request, response }: HttpContext) { + try { + const { type } = request.only(['type']) + if (!type) { + return response.badRequest({ status: false, message: 'Type is required' }) + } + + const promptAis = await PromptAi.query().where('type', type).where('is_active', true) + return { status: true, data: promptAis } + } catch (error) { + return response.badRequest({ + error: error, + message: 'Failed to get prompt AI by type', + status: false, + }) + } + } + + /** + * Create a new prompt AI + */ + async create({ request, response }: HttpContext) { + let payload = request.only(['title', 'content', 'description', 'type', 'is_active']) + try { + // Check if title already exists + const existedPromptAi = await PromptAi.findBy('title', payload.title) + if (existedPromptAi) { + return response.badRequest({ + status: false, + message: 'Prompt AI with this title already exists', + }) + } + + const promptAi = await PromptAi.create({ + title: payload.title, + content: payload.content, + description: payload.description || null, + type: payload.type || 'general', + is_active: payload.is_active !== undefined ? payload.is_active : true, + }) + return response.created({ + status: true, + message: 'Prompt AI created successfully', + data: promptAi, + }) + } catch (error) { + return response.badRequest({ + error: error, + message: 'Prompt AI create failed', + status: false, + }) + } + } + + /** + * Update prompt AI + */ + async update({ request, response }: HttpContext) { + let payload = request.only(['id', 'title', 'content', 'description', 'type', 'is_active']) + try { + const promptAi = await PromptAi.find(payload.id) + if (!promptAi) { + return response.status(404).json({ status: false, message: 'Prompt AI not found' }) + } + + // Check if title already exists (and it's not the same record) + if (payload.title && payload.title !== promptAi.title) { + const existedPromptAi = await PromptAi.findBy('title', payload.title) + if (existedPromptAi) { + return response.badRequest({ + status: false, + message: 'Prompt AI with this title already exists', + }) + } + } + + Object.assign(promptAi, { + title: payload.title || promptAi.title, + content: payload.content || promptAi.content, + description: payload.description !== undefined ? payload.description : promptAi.description, + type: payload.type || promptAi.type, + is_active: payload.is_active !== undefined ? payload.is_active : promptAi.is_active, + }) + await promptAi.save() + return response.ok({ + status: true, + message: 'Prompt AI updated successfully', + data: promptAi, + }) + } catch (error) { + return response.badRequest({ + error: error, + message: 'Prompt AI update failed', + status: false, + }) + } + } + + /** + * Delete prompt AI + */ + async delete({ request, response }: HttpContext) { + try { + const { id } = request.only(['id']) + const promptAi = await PromptAi.find(id) + if (!promptAi) { + return response.status(404).json({ status: false, message: 'Prompt AI not found' }) + } + + await promptAi.delete() + return response.ok({ status: true, message: 'Prompt AI deleted successfully' }) + } catch (error) { + return response.badRequest({ + error: error, + message: 'Prompt AI delete failed', + status: false, + }) + } + } +} diff --git a/BACKEND/app/models/prompt_ai.ts b/BACKEND/app/models/prompt_ai.ts new file mode 100644 index 0000000..72891f3 --- /dev/null +++ b/BACKEND/app/models/prompt_ai.ts @@ -0,0 +1,28 @@ +import { DateTime } from 'luxon' +import { BaseModel, column } from '@adonisjs/lucid/orm' + +export default class PromptAi extends BaseModel { + @column({ isPrimary: true }) + declare id: number + + @column() + declare title: string + + @column() + declare content: string + + @column() + declare description: string | null + + @column() + declare type: string + + @column() + declare is_active: boolean + + @column.dateTime({ autoCreate: true }) + declare createdAt: DateTime + + @column.dateTime({ autoCreate: true, autoUpdate: true }) + declare updatedAt: DateTime +} diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts index d81bc6d..0ca9fe4 100644 --- a/BACKEND/app/services/line_connection.ts +++ b/BACKEND/app/services/line_connection.ts @@ -28,6 +28,7 @@ import path from 'node:path' import axios from 'axios' import redis from '@adonisjs/redis/services/main' import Line from '#models/line' +import PromptAi from '#models/prompt_ai' import { CustomSocket, ErrorRow, TestResult } from '../ultils/types.js' import momentTZ from 'moment-timezone' import { PhysicalPortTest } from './physical_test_service.js' @@ -882,34 +883,20 @@ export default class LineConnection { */ async detectLogWithAI(log: string) { try { + // Get prompt from database + const promptRecord = await PromptAi.findBy('type', 'dpelp') + if (!promptRecord) { + console.log('[ERROR] Prompt DPELP not found in database') + return '' + } + const payload = { model: 'gpt-4o-mini', max_tokens: 1000, messages: [ { role: 'user', - content: `You are a network hardware tester. - Your task is to analyze router/switch logs to determine whether the device meets hardware standards for reselling. - Focus ONLY on hardware-related problems or abnormal warnings. - Software or configuration issues (e.g., port up/down, admin down, invalid commands, CLI errors, licensing messages) should be ignored unless they indicate hardware failure. - OUTPUT FORMAT (must follow exactly): - { - "issue": [ "problem 1", "problem 2", ... ], - "summary": "short summary under 30 words" - } - RULES: - - Summaries must be in English. - - Each issue must be one short line. - - If the log contains no hardware issues, output: { "issue": ["No issues detected."], "summary": "No hardware issues found." } - - Keep responses concise, readable, and strictly in JSON format. - - Do NOT add explanations outside the JSON. - - Your job is to detect hardware faults, missing components, overheating, failing modules, PSU issues, sensor anomalies, SIM/card missing, modem errors, transceiver issues, POST/diagnostics failures, etc. - The log to analyze will be provided after this prompt. - - Here is the log: - - ${log} - `, + content: promptRecord + "\n Here's the log:\n" + log, }, ], } @@ -1708,40 +1695,20 @@ Ports Missing/Down: ${missing.length}\n\n` */ async detectShowEnvWithAI(log: string) { try { + // Get prompt from database + const promptRecord = await PromptAi.findBy('type', 'env') + if (!promptRecord) { + console.log('[ERROR] Prompt ENV not found in database') + return '' + } + const payload = { model: 'gpt-4o-mini', max_tokens: 1000, messages: [ { role: 'user', - content: `You are a network log parser. - -Input is the raw output of Cisco "show environment" or "show environment all". - -Your task: -- Focus ONLY on FAN and POWER related information. -- Ignore TEMPERATURE, VOLTAGE, and other sensors unless they relate to FAN or POWER. -- Extract each FAN or POWER component and its state. -- Normalize each item into the format: - - ": " - -Examples: -- "FAN is OK" -> "FAN: OK" -- "FAN 2 is FAILED" -> "FAN 2: FAILED" -- "POWER SUPPLY A is NOT PRESENT" -> "POWER SUPPLY A: NOT PRESENT" -- "PSU 1 Absent" -> "PSU 1: ABSENT" - -Output requirements: -- Return ONLY a valid JSON array of strings. -- Do NOT include any explanation or extra text. -- Do NOT include code block. -- JSON must be directly parsable. - -Here is the input log: - -${log} -`, + content: promptRecord + "\n Here's the log:\n" + log, }, ], } diff --git a/BACKEND/database/migrations/1776031200000_create_prompt_ais_table.ts b/BACKEND/database/migrations/1776031200000_create_prompt_ais_table.ts new file mode 100644 index 0000000..5ec7d29 --- /dev/null +++ b/BACKEND/database/migrations/1776031200000_create_prompt_ais_table.ts @@ -0,0 +1,19 @@ +import { BaseSchema } from '@adonisjs/lucid/schema' + +export default class extends BaseSchema { + protected tableName = 'prompt_ais' + + async up() { + this.schema.createTable(this.tableName, (table) => { + table.increments('id') + table.string('title').notNullable().unique() + table.text('content').notNullable() + table.string('type').notNullable() + table.timestamps() + }) + } + + async down() { + this.schema.dropTable(this.tableName) + } +} diff --git a/BACKEND/database/seeders/index.ts b/BACKEND/database/seeders/index.ts new file mode 100644 index 0000000..5a1311f --- /dev/null +++ b/BACKEND/database/seeders/index.ts @@ -0,0 +1,8 @@ +import { BaseSeeder } from '@adonisjs/lucid/seeders' +import PromptAiSeeder from './prompt_ai_seeder.js' + +export default class IndexSeeder extends BaseSeeder { + async run() { + await new PromptAiSeeder().run() + } +} diff --git a/BACKEND/database/seeders/prompt_ai_seeder.ts b/BACKEND/database/seeders/prompt_ai_seeder.ts new file mode 100644 index 0000000..fc409c4 --- /dev/null +++ b/BACKEND/database/seeders/prompt_ai_seeder.ts @@ -0,0 +1,323 @@ +import { BaseSeeder } from '@adonisjs/lucid/seeders' +import PromptAi from '#models/prompt_ai' + +const PROMPT_DPELP = `Bạn là Cisco Network Hardware TAC Engineer. + +Mục tiêu: +Phân tích log từ thiết bị Cisco và đưa ra kết luận nhanh, chính xác về tình trạng HARDWARE (trả lời bằng tiếng VIỆT, ngắn gọn, dễ hiểu). + +================================================== +PHÂN LOẠI THIẾT BỊ +================== + +* Router / ASR / ISR +* Switch (Catalyst) +* Wireless Controller (WLC 9800) +* Access Point (AP) +* Catalyst 6500 / 4500 (Special handling) + +================================================== +REQUIREMENTS (BẮT BUỘC) +======================= + +Router / ASR / WLC: + +* show platform +* show environment (hoặc show environment all / show env) +* show license (bắt buộc, không dùng show version thay thế) +* show inventory +* show version + +Switch: + +* show platform +* show environment +* show inventory +* show version +* show post → MUST = Passed + +Access Point: + +* show version +* show inventory + +Catalyst 6500 / 4500 (EXCEPTION): + +* KHÔNG yêu cầu show platform +* BẮT BUỘC: + + * show module + * show environment + * show inventory + * show version + * show post (nếu có) + +--- + +EXCEPTION (SWITCH ONLY): + +* Nếu technician đã chạy lệnh **show platform** nhưng thiết bị trả về: + → "% Incomplete command" hoặc command không supported + → XEM NHƯ ĐÃ CÓ show platform + → KHÔNG được trả về INSUFFICIENT DATA vì thiếu show platform + +--- + +Nếu thiếu lệnh bắt buộc khác: +→ RESULT: INSUFFICIENT DATA (missing ) +→ Ghi rõ thiếu lệnh +→ Vẫn được phép nêu dấu hiệu nghi ngờ trong EVIDENCE + +================================================== +HARDWARE CHECK RULE +=================== + +FAIL nếu có: + +* Power Supply Failure / PEM failure +* Module / RP / ESP state != ok/active +* Fan failure +* Temperature warning / critical +* Crash / watchdog / hardware error +* Environment sensor != Normal +* Module status = PwrDown / Failed / Unknown +* POST != Passed (đối với switch) +* ❗ Chassis authentication failed (platform-level) + +PASS nếu: + +* Tất cả module = ok/active +* Environment = Normal +* POST = Passed (đối với switch) +* Không có lỗi hardware + +PASS WITH WARNING nếu: + +* Không lỗi hardware nhưng có risk: + + * Smart License + * Minor issue (AC low, warning logs, etc.) + +⚠️ LƯU Ý: + +* PSU redundancy missing (chỉ có 1 PSU hoặc PSU thứ 2 not present) + → KHÔNG được đưa vào WARNING + → Vẫn có thể là PASS nếu không có lỗi khác + +================================================== +SPECIAL LOGIC: CHASSIS AUTHENTICATION +===================================== + +* Nếu phát hiện log: + → "chassis authentication failed" + → "PLATFORM_SCC-1-AUTHENTICATION_FAIL" + +→ PHẢI kết luận FAIL ngay (hardware/security platform issue) + +EXCEPTION: + +* Nếu log liên quan đến: + → login failed + → authentication failed do username/password (AAA, TACACS, RADIUS) + +→ KHÔNG tính là hardware issue + +================================================== +PSU LOGIC (QUAN TRỌNG) +====================== + +* PSU fail nhưng: + Vin = 0V AND Iout = 0A + → KHÔNG lỗi (chưa cắm điện) + +* PSU có điện nhưng fail → FAIL + +* Có log "Power Supply Failure" → FAIL + +* Chỉ có 1 PSU → KHÔNG cảnh báo (no warning) + +* AC low → PASS WITH WARNING + +================================================== +LICENSE RULE +============ + +Switch: + +* Có thể lấy từ show version + +Router / ASR: + +* Bắt buộc show license + +Phân loại: + +* Traditional → OK +* Smart License → PASS WITH WARNING + +================================================== +PORT ANALYSIS +============= + +Phải trích xuất: + +* Total ports +* RJ45 ports +* PoE ports +* SFP/Uplink ports +* Uplink module + +================================================== +MODULE ANALYSIS (6500/4500) +=========================== + +* Phải liệt kê từng module: + + * Slot number + * Model + * Serial + * Status: PASS / FAIL / NOT APPLICABLE + +* Nếu: + + * Status = Ok + Online Diag = Pass → PASS + * Status = PwrDown / Failed → FAIL + * Online Diag = Not Applicable → NOT APPLICABLE + +⚠️ CRITICAL REQUIREMENT: + +* "show module" là BẮT BUỘC cho Catalyst 6500 / 4500 + +* Nếu thiếu "show module": + + → RESULT: INSUFFICIENT DATA (missing show module) + → KHÔNG được phép trả về PASS / FAIL / PASS WITH WARNING + → KHÔNG được suy luận trạng thái module từ "show inventory" + +* Rule này có độ ưu tiên CAO NHẤT + → Override toàn bộ logic hardware khác + +================================================== +MISSING LOGIC (GLOBAL) +====================== + +* Đối với Catalyst 6500 / 4500: + + * Nếu thiếu "show module" + → LUÔN trả về: + RESULT: INSUFFICIENT DATA (missing show module) + + * KHÔNG phụ thuộc vào: + + * show environment + * show version + * show inventory + * bất kỳ log nào khác + +================================================== +OUTPUT FORMAT (STRICT) +====================== + +RESULT: PASS / PASS WITH WARNING / FAIL / INSUFFICIENT DATA +(Phải kèm lý do ngắn gọn trong ngoặc) + +SUMMARY: + +* Model: +* Serial: +* Hardware status: +* Key issue: +* Module (, SN: ): + +PORT DETAILS: + +* Total ports: +* RJ45 ports: +* PoE ports: +* SFP/Uplink ports: +* Uplink module: + +LICENSE: + +* Type: +* Status: + +WARNING: + +* (nếu có, KHÔNG bao gồm PSU redundancy missing) + +MISSING: + +* (nếu thiếu) + +EVIDENCE: + +* tối đa 3 dòng + +RECOMMENDATION: + +* hành động + +MODULE STATUS (nếu là 6500/4500): + +* Module (, SN: ): PASS / FAIL / NOT APPLICABLE + +================================================== +OUTPUT DELIVERY +=============== + +* LUÔN dùng writing block +* Không thêm giải thích ngoài report +* Format sạch, copy được ngay +* Ngôn ngữ: TIẾNG VIỆT +` + +const PROMPT_ENV = `You are a network log parser. +Input is the raw output of Cisco "show environment" or "show environment all". + +Your task: +- Focus ONLY on FAN and POWER related information. +- Ignore TEMPERATURE, VOLTAGE, and other sensors unless they relate to FAN or POWER. +- Extract each FAN or POWER component and its state. +- Normalize each item into the format: + + ": " + +Examples: +- "FAN is OK" -> "FAN: OK" +- "FAN 2 is FAILED" -> "FAN 2: FAILED" +- "POWER SUPPLY A is NOT PRESENT" -> "POWER SUPPLY A: NOT PRESENT" +- "PSU 1 Absent" -> "PSU 1: ABSENT" + +Output requirements: +- Return ONLY a valid JSON array of strings. +- Do NOT include any explanation or extra text. +- Do NOT include code block. +- JSON must be directly parsable. +` + +export default class extends BaseSeeder { + async run() { + // Check if data already exists to avoid duplicates + const existingDpelp = await PromptAi.findBy('title', 'Prompt ran after done DPELP') + const existingEnv = await PromptAi.findBy('title', 'Run check log off show') + + if (!existingDpelp) { + await PromptAi.create({ + title: 'Prompt ran after done DPELP', + type: 'dpelp', + content: PROMPT_DPELP, + }) + console.log('✅ Created prompt: Prompt ran after done DPELP') + } + + if (!existingEnv) { + await PromptAi.create({ + title: 'Run check log off show environment', + type: 'env', + content: PROMPT_ENV, + }) + console.log('✅ Created prompt: Run check log off show') + } + } +} diff --git a/BACKEND/start/routes.ts b/BACKEND/start/routes.ts index bc1efc8..9f62ac2 100644 --- a/BACKEND/start/routes.ts +++ b/BACKEND/start/routes.ts @@ -130,3 +130,13 @@ router router.post('/delete', '#controllers/keywords_controller.delete') }) .prefix('/api/keywords') + +router + .group(() => { + router.get('/', '#controllers/prompt_ais_controller.get') + router.post('/getByType', '#controllers/prompt_ais_controller.getByType') + router.post('/create', '#controllers/prompt_ais_controller.create') + router.post('/update', '#controllers/prompt_ais_controller.update') + router.post('/delete', '#controllers/prompt_ais_controller.delete') + }) + .prefix('/api/prompt-ai') diff --git a/FRONTEND/src/components/Modal/ModalConfig.tsx b/FRONTEND/src/components/Modal/ModalConfig.tsx index 1086a3f..01785c7 100644 --- a/FRONTEND/src/components/Modal/ModalConfig.tsx +++ b/FRONTEND/src/components/Modal/ModalConfig.tsx @@ -1,7 +1,24 @@ import { useEffect, useRef, useState } from "react"; -import { Modal, Button, ColorPicker, Group, Grid } from "@mantine/core"; +import { + Modal, + Button, + ColorPicker, + Group, + Grid, + Tabs, + TextInput, + Textarea, + Table, + Badge, + ActionIcon, + Stack, +} from "@mantine/core"; import { Terminal } from "xterm"; import { FitAddon } from "@xterm/addon-fit"; +import { IconEdit } from "@tabler/icons-react"; +import axios from "axios"; +import { notifications } from "@mantine/notifications"; +const apiUrl = import.meta.env.VITE_BACKEND_URL; interface Props { opened: boolean; @@ -9,9 +26,33 @@ interface Props { onSave: () => void; } +interface PromptAI { + id: number; + title: string; + type: string; + content: string; +} + +interface FormData { + title: string; + type: string; + content: string; +} + export default function ModalConfig({ opened, onClose, onSave }: Props) { const [color, setColor] = useState("#41ee4a"); const [loaded, setLoaded] = useState(false); + const [prompts, setPrompts] = useState([]); + const [loadingPrompts, setLoadingPrompts] = useState(false); + const [modalEditing, setModalEditing] = useState(false); + const [isDisabled, setIsDisabled] = useState(false); + const [editingId, setEditingId] = useState(null); + const [formData, setFormData] = useState({ + title: "", + type: "general", + content: "", + }); + const xtermRef = useRef(null); const terminal = useRef(null); const fitRef = useRef(null); @@ -22,6 +63,86 @@ export default function ModalConfig({ opened, onClose, onSave }: Props) { if (saved) setColor(saved); }, []); + // Fetch all prompts + const fetchPrompts = async () => { + setLoadingPrompts(true); + try { + const response = await axios.get(apiUrl + "api/prompt-ai"); + if (response.data.status) { + setPrompts(response.data.data); + } + } catch (error) { + console.error("Error fetching prompts:", error); + notifications.show({ + title: "Error", + message: "Failed to load prompts", + color: "red", + }); + } finally { + setLoadingPrompts(false); + } + }; + + // Submit form (create or update) + const handleSubmitPrompt = async (e: React.FormEvent) => { + e.preventDefault(); + if (!formData.title.trim() || !formData.content.trim()) { + notifications.show({ + title: "Error", + message: "Title and Content are required", + color: "red", + }); + return; + } + setIsDisabled(true); + try { + const url = editingId ? "/api/prompt-ai/update" : "/api/prompt-ai/create"; + const payload = editingId ? { id: editingId, ...formData } : formData; + + const response = await axios.post(apiUrl + url, payload); + if (response?.data?.status) { + notifications.show({ + title: "Success", + message: "Prompt updated successfully", + color: "green", + }); + setFormData({ + title: "", + type: "general", + content: "", + }); + setEditingId(null); + fetchPrompts(); + setIsDisabled(false); + } else { + notifications.show({ + title: "Error", + message: response?.data?.message || "Operation failed", + color: "red", + }); + } + } catch (error) { + setIsDisabled(false); + console.error("Error:", error); + notifications.show({ + title: "Error", + message: "An error occurred", + color: "red", + }); + } + }; + + // Edit prompt + const handleEditPrompt = (prompt: PromptAI) => { + setFormData({ + title: prompt.title, + type: prompt.type, + content: prompt.content, + }); + setEditingId(prompt.id); + setModalEditing(true); + }; + const handleSave = () => { localStorage.setItem("terminal-text-color", color); onClose(); @@ -53,7 +174,7 @@ export default function ModalConfig({ opened, onClose, onSave }: Props) { if (xtermRef.current) terminal.current.open(xtermRef.current); terminal.current?.write( - "Change color \nChange color\nChange color\nChange color" + "Change color \nChange color\nChange color\nChange color", ); fitAddon.fit(); }, [loaded, xtermRef]); @@ -72,6 +193,7 @@ export default function ModalConfig({ opened, onClose, onSave }: Props) { if (opened) { setTimeout(() => { setLoaded(true); + fetchPrompts(); }, 100); } else { setLoaded(false); @@ -84,78 +206,192 @@ export default function ModalConfig({ opened, onClose, onSave }: Props) { return ( - - - - + + Terminal Color + Prompt AI + + + + + + + + + + + + +
+
{ + event.preventDefault(); + event.stopPropagation(); + }} + ref={xtermRef} + style={{ + width: "100%", + paddingLeft: "10px", + paddingBottom: "10px", + fontSize: "12px", + maxHeight: "220px", + height: "220px", + padding: "4px", + paddingRight: 0, + }} + onDoubleClick={(event) => { + event.preventDefault(); + event.stopPropagation(); + }} + /> +
+ + + + + + + {/* Table */} +
+

Existing Prompts

+ {loadingPrompts ? ( +
Loading...
+ ) : prompts.length === 0 ? ( +
No prompts found
+ ) : ( +
+ + + + Title + Type + Actions + + + + {prompts.map((prompt) => ( + + + {prompt.title} + + + {prompt.type} + + + + handleEditPrompt(prompt)} + > + + + {/* handleDeletePrompt(prompt.id)} + > + + */} + + + + ))} + +
+
+ )} +
+
+
+ + + setModalEditing(false)} + title="Edit Prompt AI" + > + {/* Form */} +
+ + + setFormData({ ...formData, title: e.currentTarget.value }) + } + required /> - - - - -
-
{ - event.preventDefault(); - event.stopPropagation(); - }} - ref={xtermRef} - style={{ - width: "100%", - paddingLeft: "10px", - paddingBottom: "10px", - fontSize: "12px", - maxHeight: "220px", - height: "220px", - padding: "4px", - paddingRight: 0, - }} - onDoubleClick={(event) => { - event.preventDefault(); - event.stopPropagation(); - }} +