808 lines
34 KiB
HTML
808 lines
34 KiB
HTML
<div
|
|
class="ai-person-widget"
|
|
id="ai-person-widget-{{ person.id }}"
|
|
data-run-url-template="{% url 'ai_workspace_run' type='widget' person_id=person.id operation='summarise' %}"
|
|
data-send-url="{% url 'ai_workspace_send' type='widget' person_id=person.id %}"
|
|
data-queue-url="{% url 'ai_workspace_queue' type='widget' person_id=person.id %}"
|
|
data-limit="{{ limit }}"
|
|
data-can-send="{{ send_state.can_send|yesno:'1,0' }}">
|
|
<div style="margin-bottom: 0.75rem; padding: 0.5rem 0.25rem; border-bottom: 1px solid rgba(0, 0, 0, 0.12);">
|
|
<p class="is-size-7 has-text-weight-semibold">Selected Person</p>
|
|
<h3 class="title is-5" style="margin-bottom: 0.25rem;">{{ person.name }}</h3>
|
|
<div class="tags" style="margin-top: 0.35rem;">
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='platform' %}">Platform {{ workspace_conversation.platform_type|title }}</a>
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='thread' %}">Thread {{ workspace_conversation.platform_thread_id|default:"-" }}</a>
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='workspace_created' %}">Workspace Created {{ workspace_conversation.created_at|default:"-" }}</a>
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='stability_state' %}">Stability {{ workspace_conversation.stability_state|title }}</a>
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='stability_score' %}">Stability Score {{ workspace_conversation.stability_score|default:"-" }}</a>
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='stability_confidence' %}">Confidence {{ workspace_conversation.stability_confidence }}</a>
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='sample_messages' %}">Sample Msg {{ workspace_conversation.stability_sample_messages }}</a>
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='sample_days' %}">Sample Days {{ workspace_conversation.stability_sample_days }}</a>
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='stability_computed' %}">Stability Computed {{ workspace_conversation.stability_last_computed_at|default:"-" }}</a>
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='commitment_inbound' %}">Commit In {{ workspace_conversation.commitment_inbound_score|default:"-" }}</a>
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='commitment_outbound' %}">Commit Out {{ workspace_conversation.commitment_outbound_score|default:"-" }}</a>
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='commitment_confidence' %}">Commit Confidence {{ workspace_conversation.commitment_confidence }}</a>
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='commitment_computed' %}">Commitment Computed {{ workspace_conversation.commitment_last_computed_at|default:"-" }}</a>
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='last_event' %}">Last Event {{ workspace_conversation.last_event_ts|default:"-" }}</a>
|
|
<a class="tag is-light" href="{% url 'ai_workspace_insight_detail' type='page' person_id=person.id metric='last_ai_run' %}">Last AI Run {{ workspace_conversation.last_ai_run_at|default:"-" }}</a>
|
|
</div>
|
|
<div class="buttons are-small" style="margin-top: 0.35rem; margin-bottom: 0;">
|
|
<a class="button is-light" href="{% url 'ai_workspace_insight_graphs' type='page' person_id=person.id %}">
|
|
<span class="icon is-small"><i class="fa-solid fa-chart-line"></i></span>
|
|
<span>Insight Graphs</span>
|
|
</a>
|
|
<a class="button is-light" href="{% url 'ai_workspace_information' type='page' person_id=person.id %}">
|
|
<span class="icon is-small"><i class="fa-solid fa-circle-info"></i></span>
|
|
<span>Information</span>
|
|
</a>
|
|
<a class="button is-light" href="{% url 'ai_workspace_insight_help' type='page' person_id=person.id %}">
|
|
<span class="icon is-small"><i class="fa-solid fa-circle-question"></i></span>
|
|
<span>Scoring Help</span>
|
|
</a>
|
|
</div>
|
|
{% with participants=workspace_conversation.participants.all %}
|
|
{% if participants %}
|
|
<div style="margin-top: 0.35rem;">
|
|
<p class="is-size-7 has-text-weight-semibold" style="margin-bottom: 0.2rem;">Participants</p>
|
|
<div class="tags">
|
|
{% for participant in participants %}
|
|
<span class="tag is-light">{{ participant.name }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% endwith %}
|
|
{% if participant_feedback_display %}
|
|
<div style="margin-top: 0.25rem;">
|
|
<p class="is-size-7 has-text-weight-semibold" style="margin-bottom: 0.2rem;">Participant Feedback</p>
|
|
<div class="tags">
|
|
<span class="tag is-light {{ participant_feedback_display.state_class }}">
|
|
<span class="icon is-small"><i class="{{ participant_feedback_display.state_icon }}"></i></span>
|
|
<span>{{ participant_feedback_display.state_label }}</span>
|
|
</span>
|
|
<span class="tag is-light">Inbound {{ participant_feedback_display.inbound_messages|default:"-" }}</span>
|
|
<span class="tag is-light">Outbound {{ participant_feedback_display.outbound_messages|default:"-" }}</span>
|
|
<span class="tag is-light">Sample Msg {{ participant_feedback_display.sample_messages|default:"-" }}</span>
|
|
<span class="tag is-light">Sample Days {{ participant_feedback_display.sample_days|default:"-" }}</span>
|
|
{% if participant_feedback_display.updated_at_label %}
|
|
<span class="tag is-light">Updated {{ participant_feedback_display.updated_at_label }}</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if compose_page_url %}
|
|
<div class="buttons are-small" style="margin-top: 0.45rem; margin-bottom: 0;">
|
|
<a class="button is-light" href="{{ compose_page_url }}">
|
|
<span class="icon is-small"><i class="{{ manual_icon_class }}"></i></span>
|
|
<span>Manual Text Mode</span>
|
|
</a>
|
|
{% if compose_widget_url %}
|
|
<button
|
|
type="button"
|
|
class="button is-light is-small js-widget-spawn-trigger is-hidden"
|
|
data-widget-url="{{ compose_widget_url }}"
|
|
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
|
hx-get="{{ compose_widget_url }}"
|
|
hx-target="#widgets-here"
|
|
hx-swap="afterend"
|
|
title="Open Manual Text widget here"
|
|
aria-label="Open Manual Text widget here">
|
|
<span class="icon is-small"><i class="fa-solid fa-table-cells-large"></i></span>
|
|
<span>Widget</span>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="notification is-{{ send_state.level }} is-light" style="padding: 0.5rem 0.75rem;">
|
|
<div class="is-flex is-justify-content-space-between is-align-items-center" style="gap: 0.4rem; flex-wrap: wrap;">
|
|
<div><strong>Send:</strong> {{ send_state.text }}</div>
|
|
<div class="buttons are-small" style="margin: 0;">
|
|
{% if not send_state.can_send %}
|
|
<button
|
|
type="button"
|
|
id="draft-override-top-btn-{{ person.id }}"
|
|
class="button is-warning is-light"
|
|
onclick="giaWorkspaceEnableSendOverride('{{ person.id }}', 'draft_reply'); return false;">
|
|
<span class="icon is-small"><i class="fa-solid fa-triangle-exclamation"></i></span>
|
|
<span>Allow Send In Pane</span>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div id="draft-top-status-{{ person.id }}" style="margin-top: 0.5rem;"></div>
|
|
</div>
|
|
|
|
<form id="ai-op-form-{{ person.id }}" style="margin-bottom: 0.75rem;">
|
|
<input type="hidden" name="limit" value="{{ limit }}">
|
|
<div class="field">
|
|
<label class="label is-small">Notes</label>
|
|
<div class="control">
|
|
<textarea class="textarea is-small" name="user_notes" rows="2" placeholder="Optional intent/context"></textarea>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<div id="ai-response-shell-{{ person.id }}" style="display: block; margin-bottom: 0.9rem;">
|
|
<div class="ai-response-capsule" style="margin-bottom: 0.5rem; border: 1px solid rgba(0, 0, 0, 0.16); border-radius: 8px; padding: 0.5rem 0.6rem;">
|
|
<div class="is-flex is-justify-content-space-between is-align-items-center" style="margin-bottom: 0.4rem;">
|
|
<div class="tags" style="margin-bottom: 0.25rem;">
|
|
<span class="tag is-info is-light is-small">Control</span>
|
|
<span class="tag is-warning is-light is-small">AI Output</span>
|
|
</div>
|
|
<div class="tabs is-small is-toggle is-toggle-rounded ai-top-tabs" style="margin-bottom: 0;">
|
|
<ul>
|
|
<li id="ai-tab-{{ person.id }}-plan_board" class="is-active ai-top-tab-mitigation">
|
|
<a
|
|
title="Control plan board"
|
|
aria-label="Control plan board"
|
|
onclick="giaWorkspaceOpenTab('{{ person.id }}', 'plan_board', false); return false;">
|
|
<span class="icon is-small"><i class="fa-solid fa-diagram-project"></i></span>
|
|
</a>
|
|
</li>
|
|
<li id="ai-tab-{{ person.id }}-corrections" class="ai-top-tab-mitigation">
|
|
<a
|
|
title="Corrections"
|
|
aria-label="Corrections"
|
|
onclick="giaWorkspaceOpenTab('{{ person.id }}', 'corrections', false); return false;">
|
|
<span class="icon is-small"><i class="fa-solid fa-screwdriver-wrench"></i></span>
|
|
</a>
|
|
</li>
|
|
<li id="ai-tab-{{ person.id }}-engage" class="ai-top-tab-mitigation">
|
|
<a
|
|
title="Engage"
|
|
aria-label="Engage"
|
|
onclick="giaWorkspaceOpenTab('{{ person.id }}', 'engage', false); return false;">
|
|
<span class="icon is-small"><i class="fa-solid fa-paper-plane"></i></span>
|
|
</a>
|
|
</li>
|
|
<li id="ai-tab-{{ person.id }}-fundamentals" class="ai-top-tab-mitigation">
|
|
<a
|
|
title="Fundamentals"
|
|
aria-label="Fundamentals"
|
|
onclick="giaWorkspaceOpenTab('{{ person.id }}', 'fundamentals', false); return false;">
|
|
<span class="icon is-small"><i class="fa-solid fa-book-open"></i></span>
|
|
</a>
|
|
</li>
|
|
<li id="ai-tab-{{ person.id }}-ask_ai" class="ai-top-tab-mitigation">
|
|
<a
|
|
title="Ask AI"
|
|
aria-label="Ask AI"
|
|
onclick="giaWorkspaceOpenTab('{{ person.id }}', 'ask_ai', false); return false;">
|
|
<span class="icon is-small"><i class="fa-solid fa-robot"></i></span>
|
|
</a>
|
|
</li>
|
|
<li id="ai-tab-{{ person.id }}-summarise" class="ai-top-tab-output ai-top-tab-output-start">
|
|
<a
|
|
title="Summary"
|
|
aria-label="Summary"
|
|
onclick="giaWorkspaceOpenTab('{{ person.id }}', 'summarise', false); return false;">
|
|
<span class="icon is-small"><i class="fa-solid fa-file-lines"></i></span>
|
|
</a>
|
|
</li>
|
|
<li id="ai-tab-{{ person.id }}-draft_reply" class="ai-top-tab-output">
|
|
<a
|
|
title="Draft"
|
|
aria-label="Draft"
|
|
onclick="giaWorkspaceOpenTab('{{ person.id }}', 'draft_reply', false); return false;">
|
|
<span class="icon is-small"><i class="fa-solid fa-pen-to-square"></i></span>
|
|
</a>
|
|
</li>
|
|
<li id="ai-tab-{{ person.id }}-extract_patterns" class="ai-top-tab-output">
|
|
<a
|
|
title="Patterns"
|
|
aria-label="Patterns"
|
|
onclick="giaWorkspaceOpenTab('{{ person.id }}', 'extract_patterns', false); return false;">
|
|
<span class="icon is-small"><i class="fa-solid fa-chart-line"></i></span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="is-flex is-align-items-center" style="gap: 0.35rem;">
|
|
<span id="ai-cache-indicator-{{ person.id }}" class="tag is-warning is-light is-small" style="display: none;">
|
|
Cached
|
|
</span>
|
|
<button
|
|
type="button"
|
|
class="button is-small is-ghost"
|
|
title="Refresh current tab"
|
|
onclick="giaWorkspaceRefresh('{{ person.id }}'); return false;">
|
|
<span class="icon is-small"><i class="fa-solid fa-rotate-right"></i></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="ai-stage-{{ person.id }}" style="min-height: 7rem;">
|
|
<div id="ai-pane-{{ person.id }}-artifacts" class="ai-pane" style="display: none;"></div>
|
|
<div id="ai-pane-{{ person.id }}-summarise" class="ai-pane" style="display: none;"></div>
|
|
<div id="ai-pane-{{ person.id }}-draft_reply" class="ai-pane"></div>
|
|
<div id="ai-pane-{{ person.id }}-extract_patterns" class="ai-pane" style="display: none;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<style>
|
|
@keyframes aiFadeInUp {
|
|
from { opacity: 0; transform: translateY(6px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
.ai-animate-in {
|
|
animation: aiFadeInUp 180ms ease-out;
|
|
}
|
|
.ai-response-capsule {
|
|
transition: all 180ms ease-out;
|
|
}
|
|
.ai-person-widget .ai-top-tabs li.ai-top-tab-output-start {
|
|
margin-left: 0.35rem;
|
|
}
|
|
.ai-person-widget .ai-top-tabs li.ai-top-tab-mitigation a {
|
|
background: rgba(51, 114, 209, 0.08);
|
|
border-color: rgba(51, 114, 209, 0.26);
|
|
}
|
|
.ai-person-widget .ai-top-tabs li.ai-top-tab-output a {
|
|
background: rgba(245, 171, 53, 0.1);
|
|
border-color: rgba(223, 145, 22, 0.3);
|
|
}
|
|
.ai-person-widget .ai-top-tabs li.ai-top-tab-mitigation.is-active a {
|
|
background: #3273dc;
|
|
border-color: #3273dc;
|
|
color: #fff;
|
|
}
|
|
.ai-person-widget .ai-top-tabs li.ai-top-tab-output.is-active a {
|
|
background: #e09116;
|
|
border-color: #e09116;
|
|
color: #fff;
|
|
}
|
|
.ai-person-widget .ai-top-tabs li a {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 2.2rem;
|
|
min-width: 2.2rem;
|
|
padding: 0 0.55rem;
|
|
line-height: 1.15;
|
|
text-align: center;
|
|
white-space: nowrap;
|
|
}
|
|
@media screen and (max-width: 768px) {
|
|
.ai-person-widget .ai-top-tabs li a {
|
|
min-height: 2.1rem;
|
|
min-width: 2.1rem;
|
|
padding: 0 0.45rem;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
(function() {
|
|
const personId = "{{ person.id }}";
|
|
const canSend = (document.getElementById("ai-person-widget-" + personId)?.dataset.canSend || "0") === "1";
|
|
const CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes
|
|
const widget = document.getElementById("ai-person-widget-" + personId);
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
window.giaWorkspaceState = window.giaWorkspaceState || {};
|
|
window.giaWorkspaceCache = window.giaWorkspaceCache || (function() {
|
|
try {
|
|
// One-time migration flush to avoid stale cached pane HTML from earlier UI schema.
|
|
localStorage.removeItem("gia_workspace_cache_v1");
|
|
localStorage.removeItem("gia_workspace_cache_v2");
|
|
localStorage.removeItem("gia_workspace_cache_v3");
|
|
return JSON.parse(localStorage.getItem("gia_workspace_cache_v4") || "{}");
|
|
} catch (e) {
|
|
return {};
|
|
}
|
|
})();
|
|
|
|
function persistCache() {
|
|
try {
|
|
localStorage.setItem("gia_workspace_cache_v4", JSON.stringify(window.giaWorkspaceCache));
|
|
} catch (e) {
|
|
// Ignore storage write issues.
|
|
}
|
|
}
|
|
|
|
function runUrl(operation) {
|
|
const template = widget.dataset.runUrlTemplate || "";
|
|
if (template.indexOf("/summarise/") >= 0) {
|
|
return template.replace("/summarise/", "/" + operation + "/");
|
|
}
|
|
return template.replace("summarise", operation);
|
|
}
|
|
|
|
function formData() {
|
|
const form = document.getElementById("ai-op-form-" + personId);
|
|
const params = new URLSearchParams(new FormData(form));
|
|
return params;
|
|
}
|
|
|
|
function cacheKey(operation) {
|
|
return personId + "|" + operation + "|" + formData().toString();
|
|
}
|
|
|
|
function applyForceSendState(operation) {
|
|
const force = !!(window.giaWorkspaceState[personId] && window.giaWorkspaceState[personId].forceSend);
|
|
const forceInput = document.getElementById("draft-send-force-" + personId + "-" + operation);
|
|
const sendBtn = document.getElementById("draft-send-btn-" + personId + "-" + operation);
|
|
if (forceInput) {
|
|
forceInput.value = force ? "1" : "0";
|
|
}
|
|
if (sendBtn && !canSend) {
|
|
sendBtn.disabled = !force;
|
|
}
|
|
}
|
|
|
|
function getCacheEntry(operation) {
|
|
const key = cacheKey(operation);
|
|
const raw = window.giaWorkspaceCache[key];
|
|
if (!raw) {
|
|
return null;
|
|
}
|
|
function evict() {
|
|
delete window.giaWorkspaceCache[key];
|
|
persistCache();
|
|
}
|
|
if (typeof raw === "string") {
|
|
// Backward compatibility: old format has no timestamp; treat as expired.
|
|
evict();
|
|
return null;
|
|
}
|
|
if (raw && typeof raw === "object" && typeof raw.html === "string") {
|
|
const ts = typeof raw.ts === "number" ? raw.ts : null;
|
|
if (!ts) {
|
|
evict();
|
|
return null;
|
|
}
|
|
if ((Date.now() - ts) > CACHE_TTL_MS) {
|
|
evict();
|
|
return null;
|
|
}
|
|
return { html: raw.html, ts: ts };
|
|
}
|
|
evict();
|
|
return null;
|
|
}
|
|
|
|
function formatCacheAge(ts) {
|
|
if (!ts) {
|
|
return "Cached";
|
|
}
|
|
const deltaSec = Math.max(0, Math.floor((Date.now() - ts) / 1000));
|
|
if (deltaSec < 5) return "Cached just now";
|
|
if (deltaSec < 60) return "Cached " + deltaSec + "s ago";
|
|
if (deltaSec < 3600) return "Cached " + Math.floor(deltaSec / 60) + "m ago";
|
|
if (deltaSec < 86400) return "Cached " + Math.floor(deltaSec / 3600) + "h ago";
|
|
return "Cached " + Math.floor(deltaSec / 86400) + "d ago";
|
|
}
|
|
|
|
function executeInlineScripts(container) {
|
|
if (!container) {
|
|
return;
|
|
}
|
|
const scripts = container.querySelectorAll("script");
|
|
scripts.forEach(function(oldScript) {
|
|
const newScript = document.createElement("script");
|
|
if (oldScript.src) {
|
|
newScript.src = oldScript.src;
|
|
} else {
|
|
newScript.textContent = oldScript.textContent || "";
|
|
}
|
|
Array.from(oldScript.attributes || []).forEach(function(attr) {
|
|
if (attr.name !== "src") {
|
|
newScript.setAttribute(attr.name, attr.value);
|
|
}
|
|
});
|
|
oldScript.parentNode.replaceChild(newScript, oldScript);
|
|
});
|
|
}
|
|
|
|
function setCachedIndicator(show, ts) {
|
|
const indicator = document.getElementById("ai-cache-indicator-" + personId);
|
|
if (!indicator) {
|
|
return;
|
|
}
|
|
if (show) {
|
|
indicator.textContent = formatCacheAge(ts);
|
|
}
|
|
indicator.style.display = show ? "inline-flex" : "none";
|
|
}
|
|
|
|
const OPERATION_TABS = ["summarise", "draft_reply", "extract_patterns"];
|
|
const MITIGATION_TABS = ["plan_board", "corrections", "engage", "fundamentals", "ask_ai"];
|
|
const ALL_TOP_TABS = MITIGATION_TABS.concat(OPERATION_TABS);
|
|
|
|
function isMitigationTab(tabKey) {
|
|
return MITIGATION_TABS.indexOf(tabKey) !== -1;
|
|
}
|
|
|
|
function operationForTab(tabKey) {
|
|
return isMitigationTab(tabKey) ? "artifacts" : tabKey;
|
|
}
|
|
|
|
function setTopCapsuleActive(tabKey) {
|
|
ALL_TOP_TABS.forEach(function(name) {
|
|
const tab = document.getElementById("ai-tab-" + personId + "-" + name);
|
|
if (tab) {
|
|
tab.classList.toggle("is-active", name === tabKey);
|
|
}
|
|
});
|
|
}
|
|
|
|
function showOperationPane(operation) {
|
|
["artifacts", "summarise", "draft_reply", "extract_patterns"].forEach(function(op) {
|
|
const pane = document.getElementById("ai-pane-" + personId + "-" + op);
|
|
if (!pane) {
|
|
return;
|
|
}
|
|
pane.style.display = op === operation ? "block" : "none";
|
|
});
|
|
}
|
|
|
|
function applyMitigationTabSelection() {
|
|
const state = window.giaWorkspaceState[personId] || {};
|
|
const targetTab = state.currentMitigationTab || "plan_board";
|
|
setTopCapsuleActive(targetTab);
|
|
if (typeof window.giaMitigationShowTab === "function") {
|
|
window.giaMitigationShowTab(personId, targetTab);
|
|
}
|
|
}
|
|
|
|
function hydrateCachedIfAvailable(operation) {
|
|
if (operation === "artifacts") {
|
|
return false;
|
|
}
|
|
const entry = getCacheEntry(operation);
|
|
const pane = document.getElementById("ai-pane-" + personId + "-" + operation);
|
|
if (!pane) {
|
|
return false;
|
|
}
|
|
if (entry && !pane.dataset.loaded) {
|
|
pane.innerHTML = entry.html;
|
|
pane.dataset.loaded = "1";
|
|
executeInlineScripts(pane);
|
|
if (window.htmx) {
|
|
window.htmx.process(pane);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
window.giaWorkspaceShowTab = function(pid, operation, tabKey) {
|
|
if (pid !== personId) {
|
|
return;
|
|
}
|
|
showOperationPane(operation);
|
|
const activeTab = tabKey || (
|
|
operation === "artifacts"
|
|
? ((window.giaWorkspaceState[personId] || {}).currentMitigationTab || "plan_board")
|
|
: operation
|
|
);
|
|
setTopCapsuleActive(activeTab);
|
|
const hydrated = hydrateCachedIfAvailable(operation);
|
|
const entry = operation === "artifacts" ? null : getCacheEntry(operation);
|
|
setCachedIndicator(hydrated || !!entry, entry ? entry.ts : null);
|
|
window.giaWorkspaceState[personId] = window.giaWorkspaceState[personId] || {};
|
|
window.giaWorkspaceState[personId].current = operation;
|
|
window.giaWorkspaceState[personId].currentTab = activeTab;
|
|
};
|
|
|
|
window.giaWorkspaceOpenTab = function(pid, tabKey, forceRefresh) {
|
|
if (pid !== personId) {
|
|
return;
|
|
}
|
|
window.giaWorkspaceState[personId] = window.giaWorkspaceState[personId] || {};
|
|
if (isMitigationTab(tabKey)) {
|
|
window.giaWorkspaceState[personId].currentMitigationTab = tabKey;
|
|
}
|
|
window.giaWorkspaceState[personId].pendingTabKey = tabKey;
|
|
window.giaWorkspaceRun(personId, operationForTab(tabKey), !!forceRefresh);
|
|
};
|
|
|
|
window.giaWorkspaceRun = function(pid, operation, forceRefresh) {
|
|
if (pid !== personId) {
|
|
return;
|
|
}
|
|
const cacheAllowed = operation !== "artifacts";
|
|
const shell = document.getElementById("ai-response-shell-" + personId);
|
|
const pane = document.getElementById("ai-pane-" + personId + "-" + operation);
|
|
if (!shell || !pane) {
|
|
return;
|
|
}
|
|
const currentState = window.giaWorkspaceState[personId] || {};
|
|
const targetTabKey = currentState.pendingTabKey || (
|
|
operation === "artifacts"
|
|
? (currentState.currentMitigationTab || "plan_board")
|
|
: operation
|
|
);
|
|
if (!forceRefresh && currentState.current === operation && pane.dataset.loaded === "1") {
|
|
window.giaWorkspaceShowTab(personId, operation, targetTabKey);
|
|
if (operation === "artifacts") {
|
|
applyMitigationTabSelection();
|
|
}
|
|
if (window.giaWorkspaceState[personId]) {
|
|
window.giaWorkspaceState[personId].pendingTabKey = "";
|
|
}
|
|
return;
|
|
}
|
|
window.giaWorkspaceShowTab(personId, operation, targetTabKey);
|
|
if (operation === "artifacts") {
|
|
applyMitigationTabSelection();
|
|
}
|
|
|
|
const key = cacheKey(operation);
|
|
const entry = getCacheEntry(operation);
|
|
if (cacheAllowed && !forceRefresh && entry) {
|
|
pane.innerHTML = entry.html;
|
|
pane.dataset.loaded = "1";
|
|
executeInlineScripts(pane);
|
|
pane.classList.remove("ai-animate-in");
|
|
void pane.offsetWidth;
|
|
pane.classList.add("ai-animate-in");
|
|
setCachedIndicator(true, entry.ts);
|
|
if (window.htmx) {
|
|
window.htmx.process(pane);
|
|
}
|
|
if (operation === "draft_reply" && typeof window.giaWorkspaceUseDraft === "function") {
|
|
window.giaWorkspaceUseDraft(personId, operation, 0);
|
|
}
|
|
if (operation === "artifacts") {
|
|
applyMitigationTabSelection();
|
|
}
|
|
if (window.giaWorkspaceState[personId]) {
|
|
window.giaWorkspaceState[personId].pendingTabKey = "";
|
|
}
|
|
return;
|
|
}
|
|
|
|
setCachedIndicator(false, null);
|
|
pane.innerHTML = '<div class="notification is-light ai-animate-in">Loading...</div>';
|
|
const url = runUrl(operation) + "?" + formData().toString();
|
|
fetch(url, { method: "GET" })
|
|
.then(function(resp) { return resp.text(); })
|
|
.then(function(html) {
|
|
pane.innerHTML = html;
|
|
pane.dataset.loaded = "1";
|
|
executeInlineScripts(pane);
|
|
pane.classList.remove("ai-animate-in");
|
|
void pane.offsetWidth;
|
|
pane.classList.add("ai-animate-in");
|
|
if (cacheAllowed) {
|
|
window.giaWorkspaceCache[key] = {
|
|
html: html,
|
|
ts: Date.now(),
|
|
};
|
|
persistCache();
|
|
setCachedIndicator(true, window.giaWorkspaceCache[key].ts);
|
|
} else {
|
|
setCachedIndicator(false, null);
|
|
}
|
|
if (window.htmx) {
|
|
window.htmx.process(pane);
|
|
}
|
|
if (operation === "draft_reply" && typeof window.giaWorkspaceUseDraft === "function") {
|
|
window.giaWorkspaceUseDraft(personId, operation, 0);
|
|
}
|
|
if (operation === "artifacts") {
|
|
applyMitigationTabSelection();
|
|
}
|
|
if (window.giaWorkspaceState[personId]) {
|
|
window.giaWorkspaceState[personId].pendingTabKey = "";
|
|
}
|
|
})
|
|
.catch(function() {
|
|
pane.innerHTML = '<div class="notification is-danger is-light ai-animate-in">Failed to load AI response.</div>';
|
|
});
|
|
};
|
|
|
|
window.giaWorkspaceRefresh = function(pid) {
|
|
if (pid !== personId) {
|
|
return;
|
|
}
|
|
const state = window.giaWorkspaceState[personId] || {};
|
|
const currentTab = state.currentTab || (
|
|
state.current === "artifacts"
|
|
? (state.currentMitigationTab || "plan_board")
|
|
: (state.current || "plan_board")
|
|
);
|
|
window.giaWorkspaceOpenTab(personId, currentTab, true);
|
|
};
|
|
|
|
window.giaWorkspaceUseDraft = function(pid, operation, index) {
|
|
if (pid !== personId) {
|
|
return;
|
|
}
|
|
const host = document.getElementById("draft-host-" + personId + "-" + operation);
|
|
const optionCard = host ? host.querySelector('.draft-option-card[data-index="' + index + '"]') : null;
|
|
const option = optionCard ? optionCard.querySelector(".draft-text") : null;
|
|
if (!option) {
|
|
return;
|
|
}
|
|
const cards = host ? host.querySelectorAll(".draft-option-card") : [];
|
|
cards.forEach(function(el) { el.classList.remove("is-selected"); });
|
|
if (optionCard) {
|
|
optionCard.classList.add("is-selected");
|
|
}
|
|
host.dataset.selected = String(index);
|
|
const sendShell = document.getElementById("draft-send-shell-" + personId + "-" + operation);
|
|
const hiddenInput = document.getElementById("draft-send-input-" + personId + "-" + operation);
|
|
const preview = document.getElementById("draft-send-preview-" + personId + "-" + operation);
|
|
if (!sendShell || !hiddenInput || !preview) {
|
|
return;
|
|
}
|
|
hiddenInput.value = option.textContent.trim();
|
|
preview.value = option.textContent.trim();
|
|
applyForceSendState(operation);
|
|
sendShell.classList.remove("ai-animate-in");
|
|
void sendShell.offsetWidth;
|
|
sendShell.classList.add("ai-animate-in");
|
|
};
|
|
|
|
window.giaWorkspaceEnableSendOverride = function(pid, operation) {
|
|
if (pid !== personId) {
|
|
return;
|
|
}
|
|
window.giaWorkspaceState[personId] = window.giaWorkspaceState[personId] || {};
|
|
window.giaWorkspaceState[personId].forceSend = true;
|
|
applyForceSendState(operation);
|
|
if (typeof window.giaEngageSyncSendOverride === "function") {
|
|
window.giaEngageSyncSendOverride(personId);
|
|
}
|
|
const overrideBtn = document.getElementById("draft-override-top-btn-" + personId);
|
|
if (overrideBtn) {
|
|
overrideBtn.classList.remove("is-warning");
|
|
overrideBtn.classList.add("is-success");
|
|
const labelNode = overrideBtn.querySelector("span:last-child");
|
|
if (labelNode) {
|
|
labelNode.textContent = "Override Enabled";
|
|
}
|
|
}
|
|
const statusHost = document.getElementById("draft-top-status-" + personId);
|
|
if (statusHost) {
|
|
statusHost.innerHTML = '<div class="notification is-success is-light" style="padding: 0.45rem 0.6rem;">Send override enabled for this pane.</div>';
|
|
}
|
|
};
|
|
|
|
window.giaWorkspaceQueueSelectedDraft = function(pid) {
|
|
if (pid !== personId) {
|
|
return;
|
|
}
|
|
const queueUrl = widget.dataset.queueUrl;
|
|
const preview = document.getElementById("draft-send-preview-" + personId + "-draft_reply");
|
|
const statusHost = document.getElementById("draft-top-status-" + personId);
|
|
const text = preview ? preview.value.trim() : "";
|
|
if (!text) {
|
|
if (statusHost) {
|
|
statusHost.innerHTML = '<div class="notification is-warning is-light" style="padding: 0.45rem 0.6rem;">Select a draft first, then queue it.</div>';
|
|
}
|
|
return;
|
|
}
|
|
const payload = new URLSearchParams();
|
|
payload.append("draft_text", text);
|
|
fetch(queueUrl, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
"X-CSRFToken": "{{ csrf_token }}",
|
|
},
|
|
body: payload.toString(),
|
|
})
|
|
.then(function(resp) { return resp.text(); })
|
|
.then(function(html) {
|
|
if (statusHost) {
|
|
statusHost.innerHTML = html;
|
|
}
|
|
})
|
|
.catch(function() {
|
|
if (statusHost) {
|
|
statusHost.innerHTML = '<div class="notification is-danger is-light" style="padding: 0.45rem 0.6rem;">Failed to queue draft.</div>';
|
|
}
|
|
});
|
|
};
|
|
|
|
if (typeof window.giaMitigationShowTab !== "function") {
|
|
window.giaMitigationShowTab = function(pid, tabName) {
|
|
const names = ["plan_board", "corrections", "engage", "fundamentals", "ask_ai"];
|
|
names.forEach(function(name) {
|
|
const pane = document.getElementById("mitigation-tab-" + pid + "-" + name);
|
|
const tab = document.getElementById("mitigation-tab-btn-" + pid + "-" + name);
|
|
if (!pane) {
|
|
return;
|
|
}
|
|
const active = (name === tabName);
|
|
pane.style.display = active ? "block" : "none";
|
|
if (tab) {
|
|
tab.classList.toggle("is-active", active);
|
|
}
|
|
});
|
|
const shell = document.getElementById("mitigation-shell-" + pid);
|
|
if (!shell) {
|
|
return;
|
|
}
|
|
shell.querySelectorAll('input[name="active_tab"]').forEach(function(input) {
|
|
input.value = tabName;
|
|
});
|
|
};
|
|
}
|
|
|
|
if (typeof window.giaMitigationToggleEdit !== "function") {
|
|
window.giaMitigationToggleEdit = function(button) {
|
|
const form = button ? button.closest("form") : null;
|
|
if (!form) {
|
|
return;
|
|
}
|
|
const card = form.closest(".mitigation-artifact-card");
|
|
const editing = button.dataset.editState === "edit";
|
|
const fields = form.querySelectorAll('[data-editable="1"]');
|
|
const toggles = form.querySelectorAll('[data-editable-toggle="1"]');
|
|
if (!editing) {
|
|
fields.forEach(function(field) {
|
|
field.removeAttribute("readonly");
|
|
});
|
|
toggles.forEach(function(field) {
|
|
field.removeAttribute("disabled");
|
|
});
|
|
if (card) {
|
|
card.classList.add("is-editing");
|
|
}
|
|
button.dataset.editState = "edit";
|
|
button.classList.remove("is-light");
|
|
button.title = "Save";
|
|
button.innerHTML = '<span class="icon is-small"><i class="fa-solid fa-check"></i></span>';
|
|
} else {
|
|
form.requestSubmit();
|
|
}
|
|
};
|
|
}
|
|
|
|
if (typeof window.giaEngageSetAction !== "function") {
|
|
window.giaEngageSetAction = function(pid, action) {
|
|
const actionInput = document.getElementById("engage-action-input-" + pid);
|
|
if (actionInput) {
|
|
actionInput.value = action;
|
|
}
|
|
};
|
|
}
|
|
|
|
if (typeof window.giaEngageAutoPreview !== "function") {
|
|
window.giaEngageAutoPreview = function(pid) {
|
|
const form = document.getElementById("engage-form-" + pid);
|
|
if (!form) {
|
|
return;
|
|
}
|
|
window.giaEngageSetAction(pid, "preview");
|
|
form.requestSubmit();
|
|
};
|
|
}
|
|
|
|
if (typeof window.giaEngageSelect !== "function") {
|
|
window.giaEngageSelect = function(pid, kind, value, node) {
|
|
let inputId = "";
|
|
if (kind === "share") {
|
|
inputId = "engage-share-input-" + pid;
|
|
} else if (kind === "framing") {
|
|
inputId = "engage-framing-input-" + pid;
|
|
}
|
|
const input = inputId ? document.getElementById(inputId) : null;
|
|
if (input) {
|
|
input.value = value;
|
|
}
|
|
const li = node && node.closest ? node.closest("li") : null;
|
|
if (li && li.parentElement) {
|
|
Array.from(li.parentElement.children).forEach(function(child) {
|
|
child.classList.remove("is-active");
|
|
});
|
|
li.classList.add("is-active");
|
|
}
|
|
window.giaEngageAutoPreview(pid);
|
|
};
|
|
}
|
|
|
|
window.giaWorkspaceOpenTab(personId, "plan_board", false);
|
|
})();
|
|
</script>
|