feat(ask): progress panel + 240s ajax timeout

The ask mode goes through the full agent loop (hybrid search + rerank +
Claude). Typical runs land at 30-90s; long queries reach 90-120s. With
the previous UX the user saw only "Loading…" on the submit button and
"מחפש…" in the results area — indistinguishable from a stuck request.

- Render a dedicated progress panel on ask with a spinner, a description
  of what shira is doing, and a live elapsed-seconds counter.
- After 2 minutes, append a "taking longer than usual" warning so users
  know the request is still running.
- Override Espo.Ajax timeout to 240s; jQuery's default (undefined) or
  EspoCRM's request-level default would kill the browser request well
  before shira finishes on a cold cache.
- Version 0.1.7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 12:06:29 +00:00
parent 8829a2e93a
commit 34ad5cd023
2 changed files with 58 additions and 3 deletions
@@ -103,17 +103,68 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) {
runAsk: function (message) {
this.setLoading(true);
this.startAskProgress();
// Ask goes through the full agent loop (search + rerank + LLM),
// which routinely takes 30-90s. The default jQuery Ajax timeout
// is too short; override it here so the browser doesn't give up
// before shira-hermes responds.
Espo.Ajax.postRequest('KnowledgeBase/action/ask', {
message: message,
}).then(res => {
}, {timeout: 240000}).then(res => {
this.stopAskProgress();
this.setLoading(false);
this.renderAskAnswer(res.text || '');
}).catch(err => {
this.stopAskProgress();
this.setLoading(false);
this.showError(err);
});
},
startAskProgress: function () {
// Show a live elapsed-time indicator so users know the request
// is still running rather than stuck. Typical answers arrive in
// 30-90s; we warn after 2 minutes.
if (this.mode !== 'ask') return;
const $results = this.$el.find('.kb-results');
$results.html(
'<div class="kb-ask-progress panel panel-info">' +
' <div class="panel-body" style="direction:rtl;text-align:right;">' +
' <i class="fas fa-circle-notch fa-spin" style="margin-left:8px;"></i>' +
' <strong>שירה חושבת…</strong>' +
' <div class="small text-muted" style="margin-top:6px;">' +
' שירה מחפשת בבסיס הידע, מסכמת ומציגה ציטוטים מדויקים.' +
' זה יכול לקחת בין 30 ל-90 שניות.' +
' </div>' +
' <div class="small" style="margin-top:10px;">' +
' <span>זמן שעבר: </span>' +
' <span class="kb-elapsed">0</span>' +
' <span> שניות</span>' +
' </div>' +
' </div>' +
'</div>'
);
const startedAt = Date.now();
this._askTimer = setInterval(() => {
const sec = Math.round((Date.now() - startedAt) / 1000);
const $elapsed = this.$el.find('.kb-elapsed');
if ($elapsed.length) $elapsed.text(sec);
if (sec === 120) {
this.$el.find('.kb-ask-progress .panel-body').append(
'<div class="alert alert-warning" style="margin-top:10px;">' +
'לוקח יותר זמן מהרגיל. עדיין ממתינים…</div>'
);
}
}, 1000);
},
stopAskProgress: function () {
if (this._askTimer) {
clearInterval(this._askTimer);
this._askTimer = null;
}
},
renderSearchResults: function (hits) {
const $results = this.$el.find('.kb-results');
if (!hits.length) {
@@ -260,7 +311,11 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) {
const $btn = this.$el.find('[data-action="submit"]');
if (loading) {
$btn.prop('disabled', true).text(this.translate('Loading', 'labels'));
this.$el.find('.kb-results').html('<div class="text-muted">מחפש…</div>');
// Only overwrite results with "מחפש…" for search. For ask,
// startAskProgress() renders a richer progress panel.
if (this.mode !== 'ask') {
this.$el.find('.kb-results').html('<div class="text-muted">מחפש…</div>');
}
} else {
$btn.prop('disabled', false).text(this.getSubmitLabel());
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "KnowledgeBase",
"module": "KnowledgeBase",
"version": "0.1.6",
"version": "0.1.7",
"acceptableVersions": [
">=8.0.0"
],