mirror of https://github.com/itflow-org/itflow
commit
3106685972
17
CHANGELOG.md
17
CHANGELOG.md
|
|
@ -2,6 +2,23 @@
|
|||
|
||||
This file documents all notable changes made to ITFlow.
|
||||
|
||||
## [25.09.1]
|
||||
|
||||
### Fixes
|
||||
- **Web Installer**: Resolved issue with broken installer caused by incorrect database schema file name.
|
||||
- Hide the "Add Credit" button as the feature is not fully implemented yet.
|
||||
- Corrected long invoice/quote notes that were overlapping with the footer in PDF exports.
|
||||
- Fixed AI settings not appearing in the Admin Menu when the Billing module was disabled.
|
||||
- Enabled wrapping of client tags when they are too long.
|
||||
- Fixed an issue where AI was not functioning correctly.
|
||||
- Removed extra spacing between the contact name and icon in the Ticket Details contact card.
|
||||
|
||||
### Features
|
||||
- Redesigned **AI Ticket Summary**, now divided into 3 sections: Main Issue, Actions Taken, and Resolution/Next Steps.
|
||||
- Updated the **AI Ticket Summary** prompt to include ticket status, reply author, source, category, and priority.
|
||||
|
||||
---
|
||||
|
||||
## [25.09]
|
||||
|
||||
***BACK UP*** before updating.
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
<p>Saved Payments</p>
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<li class="nav-item">
|
||||
<a href="ai_provider.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'ai_provider.php' ? 'active' : ''); ?>">
|
||||
<i class="nav-icon fas fa-robot"></i>
|
||||
|
|
@ -84,7 +85,7 @@
|
|||
<p>AI Models</p>
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ($config_module_enable_ticketing) { ?>
|
||||
<li class="nav-item">
|
||||
<a href="ticket_status.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'ticket_status.php' ? 'active' : ''); ?>">
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@
|
|||
<input type="text" class="form-control" name="name" placeholder="Template name" maxlength="200">
|
||||
</div>
|
||||
|
||||
<?php if ($config_ai_enable == 1) { ?>
|
||||
<!-- Prompt for AI -->
|
||||
<div class="form-group">
|
||||
<label>Enter a prompt for the type of IT documentation you want to generate:</label>
|
||||
<div class="input-group mb-3">
|
||||
|
|
@ -27,7 +25,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<!-- TinyMCE Content -->
|
||||
<div class="form-group">
|
||||
|
|
|
|||
|
|
@ -300,7 +300,7 @@ if (isset($_GET['export_quote_pdf'])) {
|
|||
|
||||
// Start TCPDF
|
||||
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
|
||||
$pdf->SetMargins(15, 15, 15);
|
||||
$pdf->SetMargins(10, 10, 10);
|
||||
$pdf->setPrintHeader(false);
|
||||
$pdf->setPrintFooter(false);
|
||||
$pdf->AddPage();
|
||||
|
|
@ -397,7 +397,7 @@ if (isset($_GET['export_quote_pdf'])) {
|
|||
// Totals
|
||||
$html .= '<table width="100%" cellspacing="0" cellpadding="4">
|
||||
<tr>
|
||||
<td width="60%" rowspan="6" valign="top"><i>' . nl2br($quote_note) . '</i></td>
|
||||
<td width="60%"><i style="font-size:9pt;">' . nl2br($quote_note) . '</i></td>
|
||||
<td width="40%">
|
||||
<table width="100%" cellpadding="3" cellspacing="0">
|
||||
<tr><td>Subtotal:</td><td align="right">' . numfmt_format_currency($currency_format, $sub_total, $quote_currency_code) . '</td></tr>';
|
||||
|
|
@ -526,7 +526,7 @@ if (isset($_GET['export_invoice_pdf'])) {
|
|||
|
||||
// Start TCPDF
|
||||
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
|
||||
$pdf->SetMargins(15, 15, 15);
|
||||
$pdf->SetMargins(10, 10, 10);
|
||||
$pdf->setPrintHeader(false);
|
||||
$pdf->setPrintFooter(false);
|
||||
$pdf->AddPage();
|
||||
|
|
@ -620,7 +620,7 @@ if (isset($_GET['export_invoice_pdf'])) {
|
|||
// Totals
|
||||
$html .= '<table width="100%" cellspacing="0" cellpadding="4">
|
||||
<tr>
|
||||
<td width="60%" rowspan="6" valign="top"><i>' . nl2br($invoice_note) . '</i></td>
|
||||
<td width="60%"><i style="font-size:9pt;">' . nl2br($invoice_note) . '</i></td>
|
||||
<td width="40%">
|
||||
<table width="100%" cellpadding="3" cellspacing="0">
|
||||
<tr><td>Subtotal:</td><td align="right">' . numfmt_format_currency($currency_format, $sub_total, $invoice_currency_code) . '</td></tr>';
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@
|
|||
* Update this file each time we merge develop into master. Format is YY.MM (add a .v if there is more than one release a month.
|
||||
*/
|
||||
|
||||
DEFINE("APP_VERSION", "25.09");
|
||||
DEFINE("APP_VERSION", "25.09.1");
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
document.getElementById('rewordButton').addEventListener('click', function() {
|
||||
var textInput = this.closest('form').querySelector('textarea');
|
||||
var ticketDescription = document.getElementById('ticketDescription');
|
||||
var rewordButton = document.getElementById('rewordButton');
|
||||
var undoButton = document.getElementById('undoButton');
|
||||
var previousText = textInput.value; // Store the current text
|
||||
|
||||
// Disable the Reword button and show loading state
|
||||
rewordButton.disabled = true;
|
||||
rewordButton.innerText = 'Processing...';
|
||||
|
||||
fetch('post.php?ai_reword', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// Body with the text to reword and the ticket description
|
||||
body: JSON.stringify({
|
||||
text: textInput.value,
|
||||
ticketDescription: ticketDescription.innerText.valueOf(),
|
||||
}),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
textInput.value = data.rewordedText || 'Error: Could not reword the text.';
|
||||
rewordButton.disabled = false;
|
||||
rewordButton.innerText = 'Reword'; // Reset button text
|
||||
undoButton.style.display = 'inline'; // Show the Undo button
|
||||
|
||||
// Set up the Undo button to revert to the previous text
|
||||
undoButton.onclick = function() {
|
||||
textInput.value = previousText;
|
||||
this.style.display = 'none'; // Hide the Undo button again
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
rewordButton.disabled = false;
|
||||
rewordButton.innerText = 'Reword'; // Reset button text
|
||||
});
|
||||
});
|
||||
64
post/ai.php
64
post/ai.php
|
|
@ -31,7 +31,7 @@ if (isset($_GET['ai_reword'])) {
|
|||
["role" => "system", "content" => $promptText],
|
||||
["role" => "user", "content" => $userText],
|
||||
],
|
||||
"temperature" => 0.7
|
||||
"temperature" => 0.5
|
||||
];
|
||||
|
||||
// Initialize cURL session to the OpenAI Chat API.
|
||||
|
|
@ -78,6 +78,8 @@ if (isset($_GET['ai_reword'])) {
|
|||
|
||||
if (isset($_GET['ai_ticket_summary'])) {
|
||||
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
|
||||
$sql = mysqli_query($mysqli, "SELECT * FROM ai_models LEFT JOIN ai_providers ON ai_model_ai_provider_id = ai_provider_id WHERE ai_model_use_case = 'General' LIMIT 1");
|
||||
|
||||
$row = mysqli_fetch_array($sql);
|
||||
|
|
@ -89,21 +91,27 @@ if (isset($_GET['ai_ticket_summary'])) {
|
|||
$ticket_id = intval($_POST['ticket_id']);
|
||||
|
||||
// Query the database for ticket details
|
||||
// (You can reuse code from ticket.php or write a simplified query here)
|
||||
$sql = mysqli_query($mysqli, "
|
||||
SELECT ticket_subject, ticket_details
|
||||
SELECT ticket_subject, ticket_details, ticket_source, ticket_priority, ticket_status_name, category_name
|
||||
FROM tickets
|
||||
LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id
|
||||
LEFT JOIN categories ON ticket_category = category_id
|
||||
WHERE ticket_id = $ticket_id
|
||||
LIMIT 1
|
||||
");
|
||||
$row = mysqli_fetch_assoc($sql);
|
||||
$ticket_subject = $row['ticket_subject'];
|
||||
$ticket_details = strip_tags($row['ticket_details']); // strip HTML for cleaner prompt
|
||||
$ticket_status = $row['ticket_status_name'];
|
||||
$ticket_category = $row['category_name'];
|
||||
$ticket_source = $row['ticket_source'];
|
||||
$ticket_priority = $row['ticket_priority'];
|
||||
|
||||
// Get ticket replies
|
||||
$sql_replies = mysqli_query($mysqli, "
|
||||
SELECT ticket_reply, ticket_reply_type
|
||||
SELECT ticket_reply, ticket_reply_type, user_name
|
||||
FROM ticket_replies
|
||||
LEFT JOIN users ON ticket_reply_by = user_id
|
||||
WHERE ticket_reply_ticket_id = $ticket_id
|
||||
AND ticket_reply_archived_at IS NULL
|
||||
ORDER BY ticket_reply_id ASC
|
||||
|
|
@ -113,20 +121,52 @@ if (isset($_GET['ai_ticket_summary'])) {
|
|||
while ($reply = mysqli_fetch_assoc($sql_replies)) {
|
||||
$reply_type = $reply['ticket_reply_type'];
|
||||
$reply_text = strip_tags($reply['ticket_reply']);
|
||||
$all_replies_text .= "\n[$reply_type]: $reply_text";
|
||||
$reply_by = $reply['user_name'];
|
||||
$all_replies_text .= "\nReply Type: $reply_type Reply By: $reply_by: Reply Text: $reply_text";
|
||||
}
|
||||
|
||||
// Craft a prompt for ChatGPT
|
||||
$prompt = "Based on the language detection (case not detected use default English), dont show \"Language Detected\", and Summarize using this language, the following ticket and its responses in a concise and clear way. The summary should be short, highlight the main issue, the actions taken, and any resolution steps:\n\nTicket Subject: $ticket_subject\nTicket Details: $ticket_details\nReplies:$all_replies_text\n\nShort Summary:";
|
||||
$prompt = "
|
||||
Summarize the following IT support ticket and its responses in a concise, clear, and professional manner.
|
||||
The summary should include:
|
||||
|
||||
1. Main Issue: What was the problem reported by the user?
|
||||
2. Actions Taken: What steps were taken to address the issue?
|
||||
3. Resolution or Next Steps: Was the issue resolved or is it ongoing?
|
||||
|
||||
Please ensure:
|
||||
- If there are multiple issues, summarize each separately.
|
||||
- Urgency: If the ticket or replies express urgency or escalation, highlight it.
|
||||
- Attachments: If mentioned in the ticket, note any relevant attachments or files.
|
||||
- Avoid extra explanations or unnecessary information.
|
||||
|
||||
Ticket Data:
|
||||
- Ticket Source: $ticket_source
|
||||
- Current Ticket Status: $ticket_status
|
||||
- Ticket Priority: $ticket_priority
|
||||
- Ticket Category: $ticket_category
|
||||
- Ticket Subject: $ticket_subject
|
||||
- Ticket Details: $ticket_details
|
||||
- Replies:
|
||||
$all_replies_text
|
||||
|
||||
Formatting instructions:
|
||||
- Use valid HTML tags only.
|
||||
- Use <h3> for section headers (Main Issue, Actions Taken, Resolution).
|
||||
- Use <ul><li> for bullet points under each section.
|
||||
- Do NOT wrap the output in ```html or any other code fences.
|
||||
- Do NOT include <html>, <head>, or <body>.
|
||||
- Output only the summary content in pure HTML.
|
||||
If any part of the ticket or replies is unclear or ambiguous, mention it in the summary and suggest if further clarification is needed.
|
||||
";
|
||||
|
||||
// Prepare the POST data
|
||||
$post_data = [
|
||||
"model" => "$model_name",
|
||||
"messages" => [
|
||||
["role" => "system", "content" => "You are a helpful assistant."],
|
||||
["role" => "system", "content" => "Your task is to summarize IT support tickets with clear, concise details."],
|
||||
["role" => "user", "content" => $prompt]
|
||||
],
|
||||
"temperature" => 0.7
|
||||
"temperature" => 0.3
|
||||
];
|
||||
|
||||
$ch = curl_init();
|
||||
|
|
@ -149,8 +189,8 @@ if (isset($_GET['ai_ticket_summary'])) {
|
|||
$response_data = json_decode($response, true);
|
||||
$summary = $response_data['choices'][0]['message']['content'] ?? "No summary available.";
|
||||
|
||||
// Print the summary
|
||||
echo nl2br(htmlentities($summary));
|
||||
|
||||
echo $summary; // nl2br to convert newlines to <br>, htmlspecialchars to prevent XSS
|
||||
}
|
||||
|
||||
if (isset($_GET['ai_create_document_template'])) {
|
||||
|
|
@ -183,7 +223,7 @@ if (isset($_GET['ai_create_document_template'])) {
|
|||
["role" => "system", "content" => $system_message],
|
||||
["role" => "user", "content" => $user_message]
|
||||
],
|
||||
"temperature" => 0.7
|
||||
"temperature" => 0.5
|
||||
];
|
||||
|
||||
$ch = curl_init();
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ if (isset($_POST['add_database'])) {
|
|||
|
||||
|
||||
// Name of the file
|
||||
$filename = 'install.sql';
|
||||
$filename = '../db.sql';
|
||||
// Temporary variable, used to store current query
|
||||
$templine = '';
|
||||
// Read in entire file
|
||||
|
|
|
|||
|
|
@ -504,7 +504,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
|
|||
<?php } ?>
|
||||
<?php
|
||||
if (!empty($client_tags_display)) { ?>
|
||||
<div class="mt-1">
|
||||
<div class="mt-1 text-wrap">
|
||||
<?php echo $client_tags_display; ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
|
@ -566,10 +566,12 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
|
|||
<span class="text-secondary">Paid</span>
|
||||
<span><?php echo numfmt_format_currency($currency_format, $amount_paid, $session_company_currency); ?></span>
|
||||
</div>
|
||||
<?php if ($credit_balance > 0) { ?>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="text-secondary">Credit</span>
|
||||
<span class="text-success"><?php echo numfmt_format_currency($currency_format, $credit_balance, $session_company_currency); ?></span>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="text-secondary">Monthly</span>
|
||||
<span><?php echo numfmt_format_currency($currency_format, $recurring_monthly, $session_company_currency); ?></span>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
<?php $show_add_credit = 0; // Remove once credits is added hides the button ?>
|
||||
|
||||
<div class="card d-print-none">
|
||||
<div class="card-header pb-1 pt-2 px-3">
|
||||
<div class="card-title">
|
||||
|
|
@ -16,10 +18,12 @@
|
|||
<i class="fas fa-fw fa-edit mr-2"></i>Edit Client
|
||||
</a>
|
||||
<?php if (lookupUserPermission("module_billing") >= 2) { ?>
|
||||
<?php if ($show_add_credit) { ?>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#addCreditModal">
|
||||
<i class="fas fa-fw fa-wallet mr-2"></i>Add Credit
|
||||
</a>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
<?php if (lookupUserPermission("module_client") >= 3) { ?>
|
||||
<div class="dropdown-divider"></div>
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
document.getElementById('rewordButton').addEventListener('click', function() {
|
||||
var textInput = this.closest('form').querySelector('textarea');
|
||||
var ticketDescription = document.getElementById('ticketDescription');
|
||||
var rewordButton = document.getElementById('rewordButton');
|
||||
var undoButton = document.getElementById('undoButton');
|
||||
var previousText = textInput.value; // Store the current text
|
||||
|
||||
// Disable the Reword button and show loading state
|
||||
rewordButton.disabled = true;
|
||||
rewordButton.innerText = 'Processing...';
|
||||
|
||||
fetch('post.php?ai_reword', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// Body with the text to reword and the ticket description
|
||||
body: JSON.stringify({
|
||||
text: textInput.value,
|
||||
ticketDescription: ticketDescription.innerText.valueOf(),
|
||||
}),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
textInput.value = data.rewordedText || 'Error: Could not reword the text.';
|
||||
rewordButton.disabled = false;
|
||||
rewordButton.innerText = 'Reword'; // Reset button text
|
||||
undoButton.style.display = 'inline'; // Show the Undo button
|
||||
|
||||
// Set up the Undo button to revert to the previous text
|
||||
undoButton.onclick = function() {
|
||||
textInput.value = previousText;
|
||||
this.style.display = 'none'; // Hide the Undo button again
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
rewordButton.disabled = false;
|
||||
rewordButton.innerText = 'Reword'; // Reset button text
|
||||
});
|
||||
});
|
||||
|
|
@ -2232,7 +2232,7 @@ if (isset($_GET['export_invoice_pdf'])) {
|
|||
|
||||
// Start TCPDF
|
||||
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
|
||||
$pdf->SetMargins(15, 15, 15);
|
||||
$pdf->SetMargins(10, 10, 10);
|
||||
$pdf->setPrintHeader(false);
|
||||
$pdf->setPrintFooter(false);
|
||||
$pdf->AddPage();
|
||||
|
|
@ -2326,7 +2326,7 @@ if (isset($_GET['export_invoice_pdf'])) {
|
|||
// Totals
|
||||
$html .= '<table width="100%" cellspacing="0" cellpadding="4">
|
||||
<tr>
|
||||
<td width="60%" rowspan="6" valign="top"><i>' . nl2br($invoice_note) . '</i></td>
|
||||
<td width="60%"><i style="font-size:9pt;">' . nl2br($invoice_note) . '</i></td>
|
||||
<td width="40%">
|
||||
<table width="100%" cellpadding="3" cellspacing="0">
|
||||
<tr><td>Subtotal:</td><td align="right">' . numfmt_format_currency($currency_format, $sub_total, $invoice_currency_code) . '</td></tr>';
|
||||
|
|
|
|||
|
|
@ -669,7 +669,7 @@ if (isset($_GET['export_quote_pdf'])) {
|
|||
|
||||
// Start TCPDF
|
||||
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
|
||||
$pdf->SetMargins(15, 15, 15);
|
||||
$pdf->SetMargins(10, 10, 10);
|
||||
$pdf->setPrintHeader(false);
|
||||
$pdf->setPrintFooter(false);
|
||||
$pdf->AddPage();
|
||||
|
|
@ -766,7 +766,7 @@ if (isset($_GET['export_quote_pdf'])) {
|
|||
// Totals
|
||||
$html .= '<table width="100%" cellspacing="0" cellpadding="4">
|
||||
<tr>
|
||||
<td width="60%" rowspan="6" valign="top"><i>' . nl2br($quote_note) . '</i></td>
|
||||
<td width="60%"><i style="font-size:9pt;">' . nl2br($quote_note) . '</i></td>
|
||||
<td width="40%">
|
||||
<table width="100%" cellpadding="3" cellspacing="0">
|
||||
<tr><td>Subtotal:</td><td align="right">' . numfmt_format_currency($currency_format, $sub_total, $quote_currency_code) . '</td></tr>';
|
||||
|
|
|
|||
|
|
@ -933,8 +933,7 @@ if (isset($_GET['ticket_id'])) {
|
|||
<div>
|
||||
<i class="fa fa-fw fa-user text-secondary mr-2"></i><a href="#" class="ajax-modal"
|
||||
data-modal-size="lg"
|
||||
data-modal-url="modals/contact/contact_details.php?id=<?= $contact_id ?>">
|
||||
<strong><?php echo $contact_name; ?></strong>
|
||||
data-modal-url="modals/contact/contact_details.php?id=<?= $contact_id ?>"><strong><?= $contact_name ?></strong>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
|
@ -1276,17 +1275,17 @@ require_once "../includes/footer.php";
|
|||
|
||||
<!-- Summary Modal -->
|
||||
<div class="modal fade" id="summaryModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content bg-dark">
|
||||
<div class="modal-header">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-dark">
|
||||
<h5 class="modal-title" id="summaryModalTitle">Ticket Summary</h5>
|
||||
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
|
||||
<button type="button" class="close text-white" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body bg-white">
|
||||
<div id="summaryContent" class="text-center">
|
||||
<i class="fas fa-spinner fa-spin"></i> Generating summary...
|
||||
<div class="modal-body">
|
||||
<div id="summaryContent">
|
||||
<div class="text-center"><i class="fas fa-spinner fa-spin"></i> Generating summary...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue