Further improve detail display and work on inline latency display
This commit is contained in:
20
core/templates/partials/ai-insight-nav.html
Normal file
20
core/templates/partials/ai-insight-nav.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<div class="tags has-addons" style="margin-top: 0.6rem; margin-bottom: 0;">
|
||||
<a
|
||||
class="tag {% if active_tab == 'graphs' %}is-dark{% else %}is-link is-light{% endif %}"
|
||||
href="{{ graphs_url }}">
|
||||
<span class="icon is-small"><i class="fa-solid fa-chart-line"></i></span>
|
||||
<span>Insight Graphs</span>
|
||||
</a>
|
||||
<a
|
||||
class="tag {% if active_tab == 'information' %}is-dark{% else %}is-link is-light{% endif %}"
|
||||
href="{{ information_url }}">
|
||||
<span class="icon is-small"><i class="fa-solid fa-circle-info"></i></span>
|
||||
<span>Information View</span>
|
||||
</a>
|
||||
<a
|
||||
class="tag {% if active_tab == 'help' %}is-dark{% else %}is-link is-light{% endif %}"
|
||||
href="{{ help_url }}">
|
||||
<span class="icon is-small"><i class="fa-solid fa-circle-question"></i></span>
|
||||
<span>Scoring Guide</span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -188,22 +188,20 @@
|
||||
{% endif %}
|
||||
|
||||
{% if memory_proposals %}
|
||||
<article class="box" style="padding: 0.55rem; margin-top: 0.55rem; border: 1px solid rgba(0, 0, 0, 0.14); box-shadow: none;">
|
||||
<section style="margin-top: 0.55rem;">
|
||||
<p class="is-size-7 has-text-weight-semibold" style="margin-bottom: 0.35rem;">Memory Proposals</p>
|
||||
{% if memory_proposal_groups %}
|
||||
<div class="columns is-multiline" style="margin: 0 -0.25rem;">
|
||||
{% for group in memory_proposal_groups %}
|
||||
<div class="column is-12-mobile is-6-tablet" style="padding: 0.25rem;">
|
||||
<article class="box" style="padding: 0.45rem; margin-bottom: 0; border: 1px solid rgba(0, 0, 0, 0.12); box-shadow: none;">
|
||||
<p class="is-size-7 has-text-weight-semibold" style="margin-bottom: 0.3rem;">{{ group.title }}</p>
|
||||
<ul style="margin: 0 0 0.25rem 1.1rem;">
|
||||
{% for proposal in group.items %}
|
||||
<li class="is-size-7" style="margin-bottom: 0.22rem; white-space: pre-wrap;">
|
||||
{{ proposal.content }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</article>
|
||||
<p class="is-size-7 has-text-weight-semibold" style="margin-bottom: 0.3rem;">{{ group.title }}</p>
|
||||
<ul style="margin: 0 0 0.25rem 1.1rem;">
|
||||
{% for proposal in group.items %}
|
||||
<li class="is-size-7" style="margin-bottom: 0.22rem; white-space: pre-wrap;">
|
||||
{{ proposal.content }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -214,7 +212,7 @@
|
||||
</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</article>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if citations %}
|
||||
|
||||
@@ -63,29 +63,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="tabs is-small is-toggle is-toggle-rounded" style="margin-bottom: 0.55rem;">
|
||||
<ul>
|
||||
<li id="mitigation-tab-btn-{{ person.id }}-plan_board" class="is-active">
|
||||
<a onclick="giaMitigationShowTab('{{ person.id }}', 'plan_board'); return false;">Rules & Games</a>
|
||||
</li>
|
||||
<li id="mitigation-tab-btn-{{ person.id }}-corrections">
|
||||
<a onclick="giaMitigationShowTab('{{ person.id }}', 'corrections'); return false;">Corrections</a>
|
||||
</li>
|
||||
<li id="mitigation-tab-btn-{{ person.id }}-engage">
|
||||
<a onclick="giaMitigationShowTab('{{ person.id }}', 'engage'); return false;">Engage</a>
|
||||
</li>
|
||||
<li id="mitigation-tab-btn-{{ person.id }}-fundamentals">
|
||||
<a onclick="giaMitigationShowTab('{{ person.id }}', 'fundamentals'); return false;">Fundamentals</a>
|
||||
</li>
|
||||
<li id="mitigation-tab-btn-{{ person.id }}-auto">
|
||||
<a onclick="giaMitigationShowTab('{{ person.id }}', 'auto'); return false;">Auto</a>
|
||||
</li>
|
||||
<li id="mitigation-tab-btn-{{ person.id }}-ask_ai">
|
||||
<a onclick="giaMitigationShowTab('{{ person.id }}', 'ask_ai'); return false;">Ask AI</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="mitigation-tab-{{ person.id }}-plan_board" class="mitigation-tab-pane">
|
||||
<div class="is-flex is-justify-content-space-between is-align-items-center" style="gap: 0.5rem; margin-bottom: 0.45rem; flex-wrap: wrap;">
|
||||
<p class="is-size-7">Two lanes by type: rules on the left, games on the right.</p>
|
||||
@@ -685,10 +662,12 @@
|
||||
["plan_board", "corrections", "engage", "fundamentals", "auto", "ask_ai"].forEach(function(name) {
|
||||
const pane = document.getElementById("mitigation-tab-" + personId + "-" + name);
|
||||
const tab = document.getElementById("mitigation-tab-btn-" + personId + "-" + name);
|
||||
if (!pane || !tab) return;
|
||||
if (!pane) return;
|
||||
const active = name === tabName;
|
||||
pane.style.display = active ? "block" : "none";
|
||||
tab.classList.toggle("is-active", active);
|
||||
if (tab) {
|
||||
tab.classList.toggle("is-active", active);
|
||||
}
|
||||
});
|
||||
setActiveTabHiddenFields(tabName);
|
||||
};
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
<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>
|
||||
@@ -38,19 +42,33 @@
|
||||
</div>
|
||||
{% with participants=workspace_conversation.participants.all %}
|
||||
{% if participants %}
|
||||
<p class="is-size-7" style="margin-top: 0.35rem; margin-bottom: 0;">
|
||||
Participants:
|
||||
{% for participant in participants %}
|
||||
{% if not forloop.first %}, {% endif %}
|
||||
{{ participant.name }}
|
||||
{% endfor %}
|
||||
</p>
|
||||
<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 workspace_conversation.participant_feedback %}
|
||||
<p class="is-size-7" style="margin-top: 0.35rem; margin-bottom: 0;">
|
||||
Participant Feedback: {{ workspace_conversation.participant_feedback }}
|
||||
</p>
|
||||
{% 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;">
|
||||
@@ -96,17 +114,32 @@
|
||||
<div class="is-flex is-justify-content-space-between is-align-items-center" style="margin-bottom: 0.4rem;">
|
||||
<div class="tabs is-small is-toggle is-toggle-rounded" style="margin-bottom: 0;">
|
||||
<ul>
|
||||
<li id="ai-tab-{{ person.id }}-artifacts">
|
||||
<a onclick="giaWorkspaceRun('{{ person.id }}', 'artifacts', false); return false;">Plan</a>
|
||||
<li id="ai-tab-{{ person.id }}-plan_board" class="is-active">
|
||||
<a onclick="giaWorkspaceOpenTab('{{ person.id }}', 'plan_board', false); return false;">Plan</a>
|
||||
</li>
|
||||
<li id="ai-tab-{{ person.id }}-corrections">
|
||||
<a onclick="giaWorkspaceOpenTab('{{ person.id }}', 'corrections', false); return false;">Corrections</a>
|
||||
</li>
|
||||
<li id="ai-tab-{{ person.id }}-engage">
|
||||
<a onclick="giaWorkspaceOpenTab('{{ person.id }}', 'engage', false); return false;">Engage</a>
|
||||
</li>
|
||||
<li id="ai-tab-{{ person.id }}-fundamentals">
|
||||
<a onclick="giaWorkspaceOpenTab('{{ person.id }}', 'fundamentals', false); return false;">Fundamentals</a>
|
||||
</li>
|
||||
<li id="ai-tab-{{ person.id }}-auto">
|
||||
<a onclick="giaWorkspaceOpenTab('{{ person.id }}', 'auto', false); return false;">Auto</a>
|
||||
</li>
|
||||
<li id="ai-tab-{{ person.id }}-ask_ai">
|
||||
<a onclick="giaWorkspaceOpenTab('{{ person.id }}', 'ask_ai', false); return false;">Ask AI</a>
|
||||
</li>
|
||||
<li id="ai-tab-{{ person.id }}-summarise">
|
||||
<a onclick="giaWorkspaceRun('{{ person.id }}', 'summarise', false); return false;">Summary</a>
|
||||
<a onclick="giaWorkspaceOpenTab('{{ person.id }}', 'summarise', false); return false;">Summary</a>
|
||||
</li>
|
||||
<li id="ai-tab-{{ person.id }}-draft_reply" class="is-active">
|
||||
<a onclick="giaWorkspaceRun('{{ person.id }}', 'draft_reply', false); return false;">Draft</a>
|
||||
<li id="ai-tab-{{ person.id }}-draft_reply">
|
||||
<a onclick="giaWorkspaceOpenTab('{{ person.id }}', 'draft_reply', false); return false;">Draft</a>
|
||||
</li>
|
||||
<li id="ai-tab-{{ person.id }}-extract_patterns">
|
||||
<a onclick="giaWorkspaceRun('{{ person.id }}', 'extract_patterns', false); return false;">Patterns</a>
|
||||
<a onclick="giaWorkspaceOpenTab('{{ person.id }}', 'extract_patterns', false); return false;">Patterns</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -163,7 +196,8 @@
|
||||
// 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");
|
||||
return JSON.parse(localStorage.getItem("gia_workspace_cache_v3") || "{}");
|
||||
localStorage.removeItem("gia_workspace_cache_v3");
|
||||
return JSON.parse(localStorage.getItem("gia_workspace_cache_v4") || "{}");
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
@@ -171,7 +205,7 @@
|
||||
|
||||
function persistCache() {
|
||||
try {
|
||||
localStorage.setItem("gia_workspace_cache_v3", JSON.stringify(window.giaWorkspaceCache));
|
||||
localStorage.setItem("gia_workspace_cache_v4", JSON.stringify(window.giaWorkspaceCache));
|
||||
} catch (e) {
|
||||
// Ignore storage write issues.
|
||||
}
|
||||
@@ -282,6 +316,46 @@
|
||||
indicator.style.display = show ? "inline-flex" : "none";
|
||||
}
|
||||
|
||||
const OPERATION_TABS = ["summarise", "draft_reply", "extract_patterns"];
|
||||
const MITIGATION_TABS = ["plan_board", "corrections", "engage", "fundamentals", "auto", "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;
|
||||
@@ -303,29 +377,35 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
window.giaWorkspaceShowTab = function(pid, operation) {
|
||||
window.giaWorkspaceShowTab = function(pid, operation, tabKey) {
|
||||
if (pid !== personId) {
|
||||
return;
|
||||
}
|
||||
["artifacts", "summarise", "draft_reply", "extract_patterns"].forEach(function(op) {
|
||||
const tab = document.getElementById("ai-tab-" + personId + "-" + op);
|
||||
const pane = document.getElementById("ai-pane-" + personId + "-" + op);
|
||||
if (!tab || !pane) {
|
||||
return;
|
||||
}
|
||||
if (op === operation) {
|
||||
tab.classList.add("is-active");
|
||||
pane.style.display = "block";
|
||||
} else {
|
||||
tab.classList.remove("is-active");
|
||||
pane.style.display = "none";
|
||||
}
|
||||
});
|
||||
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) {
|
||||
@@ -339,11 +419,25 @@
|
||||
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);
|
||||
window.giaWorkspaceShowTab(personId, operation, targetTabKey);
|
||||
if (operation === "artifacts") {
|
||||
applyMitigationTabSelection();
|
||||
}
|
||||
if (window.giaWorkspaceState[personId]) {
|
||||
window.giaWorkspaceState[personId].pendingTabKey = "";
|
||||
}
|
||||
return;
|
||||
}
|
||||
window.giaWorkspaceShowTab(personId, operation);
|
||||
window.giaWorkspaceShowTab(personId, operation, targetTabKey);
|
||||
if (operation === "artifacts") {
|
||||
applyMitigationTabSelection();
|
||||
}
|
||||
|
||||
const key = cacheKey(operation);
|
||||
const entry = getCacheEntry(operation);
|
||||
@@ -360,6 +454,12 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -391,6 +491,12 @@
|
||||
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>';
|
||||
@@ -401,8 +507,13 @@
|
||||
if (pid !== personId) {
|
||||
return;
|
||||
}
|
||||
const current = (window.giaWorkspaceState[personId] && window.giaWorkspaceState[personId].current) || "summarise";
|
||||
window.giaWorkspaceRun(personId, current, true);
|
||||
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) {
|
||||
@@ -503,12 +614,14 @@
|
||||
names.forEach(function(name) {
|
||||
const pane = document.getElementById("mitigation-tab-" + pid + "-" + name);
|
||||
const tab = document.getElementById("mitigation-tab-btn-" + pid + "-" + name);
|
||||
if (!pane || !tab) {
|
||||
if (!pane) {
|
||||
return;
|
||||
}
|
||||
const active = (name === tabName);
|
||||
pane.style.display = active ? "block" : "none";
|
||||
tab.classList.toggle("is-active", active);
|
||||
if (tab) {
|
||||
tab.classList.toggle("is-active", active);
|
||||
}
|
||||
});
|
||||
const shell = document.getElementById("mitigation-shell-" + pid);
|
||||
if (!shell) {
|
||||
@@ -584,6 +697,6 @@
|
||||
};
|
||||
}
|
||||
|
||||
window.giaWorkspaceRun(personId, "artifacts", false);
|
||||
window.giaWorkspaceOpenTab(personId, "plan_board", false);
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -121,6 +121,25 @@
|
||||
data-engage-send-url="{{ compose_engage_send_url }}">
|
||||
{% for msg in serialized_messages %}
|
||||
<div class="compose-row {% if msg.outgoing %}is-out{% else %}is-in{% endif %}" data-ts="{{ msg.ts }}">
|
||||
{% if msg.gap_fragments %}
|
||||
<div class="compose-gap-artifacts">
|
||||
{% for frag in msg.gap_fragments %}
|
||||
<article class="compose-artifact compose-artifact-gap">
|
||||
<p class="compose-artifact-head">
|
||||
<span class="icon is-small"><i class="fa-solid fa-hourglass-half"></i></span>
|
||||
<span>{{ frag.focus }} · {{ frag.lag }}</span>
|
||||
<span class="compose-artifact-score">Score {{ frag.score }}</span>
|
||||
</p>
|
||||
{% if frag.calculation %}
|
||||
<p class="compose-artifact-detail">How: {{ frag.calculation }}</p>
|
||||
{% endif %}
|
||||
{% if frag.psychology %}
|
||||
<p class="compose-artifact-detail">Meaning: {{ frag.psychology }}</p>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<article class="compose-bubble {% if msg.outgoing %}is-out{% else %}is-in{% endif %}">
|
||||
{% if msg.image_urls %}
|
||||
{% for image_url in msg.image_urls %}
|
||||
@@ -152,6 +171,21 @@
|
||||
{{ msg.display_ts }}{% if msg.author %} · {{ msg.author }}{% endif %}
|
||||
</p>
|
||||
</article>
|
||||
{% if msg.metric_fragments %}
|
||||
<div class="compose-metric-artifacts">
|
||||
{% for frag in msg.metric_fragments %}
|
||||
<article
|
||||
class="compose-artifact compose-artifact-metric"
|
||||
title="How it is calculated: {{ frag.calculation }}{% if frag.psychology %} | Psychological interpretation: {{ frag.psychology }}{% endif %}">
|
||||
<p class="compose-artifact-head">
|
||||
<span class="icon is-small"><i class="fa-solid fa-chart-line"></i></span>
|
||||
<span>{{ frag.title }}</span>
|
||||
<span class="compose-artifact-score">{{ frag.value }}</span>
|
||||
</p>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="compose-empty">No stored messages for this contact yet.</p>
|
||||
@@ -219,13 +253,15 @@
|
||||
}
|
||||
#{{ panel_id }} .compose-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
#{{ panel_id }} .compose-row.is-in {
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
#{{ panel_id }} .compose-row.is-out {
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
}
|
||||
#{{ panel_id }} .compose-bubble {
|
||||
max-width: min(85%, 46rem);
|
||||
@@ -265,6 +301,49 @@
|
||||
#{{ panel_id }} .compose-msg-meta {
|
||||
margin: 0;
|
||||
}
|
||||
#{{ panel_id }} .compose-gap-artifacts {
|
||||
align-self: center;
|
||||
width: min(92%, 34rem);
|
||||
}
|
||||
#{{ panel_id }} .compose-metric-artifacts {
|
||||
width: min(86%, 46rem);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(9.4rem, 1fr));
|
||||
gap: 0.28rem;
|
||||
}
|
||||
#{{ panel_id }} .compose-artifact {
|
||||
border: 1px dashed rgba(0, 0, 0, 0.16);
|
||||
border-radius: 8px;
|
||||
background: rgba(252, 253, 255, 0.96);
|
||||
padding: 0.28rem 0.38rem;
|
||||
}
|
||||
#{{ panel_id }} .compose-artifact.compose-artifact-gap {
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
#{{ panel_id }} .compose-artifact-head {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
gap: 0.3rem;
|
||||
align-items: center;
|
||||
color: #3f4f67;
|
||||
font-size: 0.68rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
#{{ panel_id }} .compose-artifact-head .icon {
|
||||
color: #6a88b4;
|
||||
}
|
||||
#{{ panel_id }} .compose-artifact-score {
|
||||
margin-left: auto;
|
||||
color: #2f4f7a;
|
||||
font-weight: 700;
|
||||
font-size: 0.66rem;
|
||||
}
|
||||
#{{ panel_id }} .compose-artifact-detail {
|
||||
margin: 0.15rem 0 0;
|
||||
color: #637185;
|
||||
font-size: 0.64rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
#{{ panel_id }} .compose-empty {
|
||||
margin: 0;
|
||||
color: #6f6f6f;
|
||||
@@ -456,6 +535,7 @@
|
||||
border-radius: 8px;
|
||||
padding: 0.35rem 0.42rem;
|
||||
background: #fff;
|
||||
min-width: 0;
|
||||
}
|
||||
#{{ panel_id }} .compose-qi-chip p {
|
||||
margin: 0;
|
||||
@@ -468,9 +548,22 @@
|
||||
#{{ panel_id }} .compose-qi-chip .v {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
min-width: 0;
|
||||
gap: 0.28rem;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
#{{ panel_id }} .compose-qi-chip .v > span:last-child {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-all;
|
||||
}
|
||||
#{{ panel_id }} .compose-qi-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.36rem;
|
||||
}
|
||||
#{{ panel_id }} .compose-qi-row {
|
||||
@@ -478,6 +571,7 @@
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
padding: 0.42rem 0.46rem;
|
||||
height: 100%;
|
||||
}
|
||||
#{{ panel_id }} .compose-qi-row-head {
|
||||
display: flex;
|
||||
@@ -750,6 +844,88 @@
|
||||
row.className = "compose-row " + (outgoing ? "is-out" : "is-in");
|
||||
row.dataset.ts = String(msg.ts || 0);
|
||||
|
||||
const appendGapArtifacts = function (fragments) {
|
||||
if (!Array.isArray(fragments) || !fragments.length) {
|
||||
return;
|
||||
}
|
||||
const wrap = document.createElement("div");
|
||||
wrap.className = "compose-gap-artifacts";
|
||||
fragments.forEach(function (fragment) {
|
||||
const artifact = document.createElement("article");
|
||||
artifact.className = "compose-artifact compose-artifact-gap";
|
||||
const head = document.createElement("p");
|
||||
head.className = "compose-artifact-head";
|
||||
const icon = document.createElement("span");
|
||||
icon.className = "icon is-small";
|
||||
icon.innerHTML = '<i class="fa-solid fa-hourglass-half"></i>';
|
||||
const focus = document.createElement("span");
|
||||
const focusText = String(fragment.focus || "Response gap");
|
||||
const lagText = String(fragment.lag || "");
|
||||
focus.textContent = lagText ? (focusText + " · " + lagText) : focusText;
|
||||
const score = document.createElement("span");
|
||||
score.className = "compose-artifact-score";
|
||||
score.textContent = "Score " + String(fragment.score || "-");
|
||||
head.appendChild(icon);
|
||||
head.appendChild(focus);
|
||||
head.appendChild(score);
|
||||
artifact.appendChild(head);
|
||||
if (fragment.calculation) {
|
||||
const calc = document.createElement("p");
|
||||
calc.className = "compose-artifact-detail";
|
||||
calc.textContent = "How: " + String(fragment.calculation || "");
|
||||
artifact.appendChild(calc);
|
||||
}
|
||||
if (fragment.psychology) {
|
||||
const psych = document.createElement("p");
|
||||
psych.className = "compose-artifact-detail";
|
||||
psych.textContent = "Meaning: " + String(fragment.psychology || "");
|
||||
artifact.appendChild(psych);
|
||||
}
|
||||
wrap.appendChild(artifact);
|
||||
});
|
||||
row.appendChild(wrap);
|
||||
};
|
||||
|
||||
const appendMetricArtifacts = function (fragments) {
|
||||
if (!Array.isArray(fragments) || !fragments.length) {
|
||||
return;
|
||||
}
|
||||
const wrap = document.createElement("div");
|
||||
wrap.className = "compose-metric-artifacts";
|
||||
fragments.forEach(function (fragment) {
|
||||
const artifact = document.createElement("article");
|
||||
artifact.className = "compose-artifact compose-artifact-metric";
|
||||
const calc = String(fragment.calculation || "");
|
||||
const psych = String(fragment.psychology || "");
|
||||
const tips = [];
|
||||
if (calc) {
|
||||
tips.push("How it is calculated: " + calc);
|
||||
}
|
||||
if (psych) {
|
||||
tips.push("Psychological interpretation: " + psych);
|
||||
}
|
||||
artifact.title = tips.join(" | ");
|
||||
const head = document.createElement("p");
|
||||
head.className = "compose-artifact-head";
|
||||
const icon = document.createElement("span");
|
||||
icon.className = "icon is-small";
|
||||
icon.innerHTML = '<i class="fa-solid fa-chart-line"></i>';
|
||||
const title = document.createElement("span");
|
||||
title.textContent = String(fragment.title || "Metric");
|
||||
const value = document.createElement("span");
|
||||
value.className = "compose-artifact-score";
|
||||
value.textContent = String(fragment.value || "-");
|
||||
head.appendChild(icon);
|
||||
head.appendChild(title);
|
||||
head.appendChild(value);
|
||||
artifact.appendChild(head);
|
||||
wrap.appendChild(artifact);
|
||||
});
|
||||
row.appendChild(wrap);
|
||||
};
|
||||
|
||||
appendGapArtifacts(msg.gap_fragments);
|
||||
|
||||
const bubble = document.createElement("article");
|
||||
bubble.className = "compose-bubble " + (outgoing ? "is-out" : "is-in");
|
||||
|
||||
@@ -787,6 +963,7 @@
|
||||
bubble.appendChild(meta);
|
||||
|
||||
row.appendChild(bubble);
|
||||
appendMetricArtifacts(msg.metric_fragments);
|
||||
const empty = thread.querySelector(".compose-empty");
|
||||
if (empty) {
|
||||
empty.remove();
|
||||
@@ -1144,20 +1321,83 @@
|
||||
const docs = Array.isArray(payload.docs) ? payload.docs : [];
|
||||
container.innerHTML = "";
|
||||
|
||||
const stateFaceMeta = function (stateText) {
|
||||
const state = String(stateText || "").toLowerCase();
|
||||
if (state.includes("balanced")) {
|
||||
return {
|
||||
icon: "fa-regular fa-face-smile",
|
||||
className: "has-text-success",
|
||||
label: "Balanced"
|
||||
};
|
||||
}
|
||||
if (state.includes("withdrawing")) {
|
||||
return {
|
||||
icon: "fa-regular fa-face-frown",
|
||||
className: "has-text-danger",
|
||||
label: "Withdrawing"
|
||||
};
|
||||
}
|
||||
if (state.includes("overextending")) {
|
||||
return {
|
||||
icon: "fa-regular fa-face-meh",
|
||||
className: "has-text-warning",
|
||||
label: "Overextending"
|
||||
};
|
||||
}
|
||||
if (state.includes("stable")) {
|
||||
return {
|
||||
icon: "fa-regular fa-face-smile",
|
||||
className: "has-text-success",
|
||||
label: "Positive"
|
||||
};
|
||||
}
|
||||
if (state.includes("watch")) {
|
||||
return {
|
||||
icon: "fa-regular fa-face-meh",
|
||||
className: "has-text-warning",
|
||||
label: "Mixed"
|
||||
};
|
||||
}
|
||||
if (state.includes("fragile")) {
|
||||
return {
|
||||
icon: "fa-regular fa-face-frown",
|
||||
className: "has-text-danger",
|
||||
label: "Strained"
|
||||
};
|
||||
}
|
||||
return {
|
||||
icon: "fa-regular fa-face-meh-blank",
|
||||
className: "has-text-grey",
|
||||
label: "Unknown"
|
||||
};
|
||||
};
|
||||
const stateFace = stateFaceMeta(summary.state);
|
||||
|
||||
const head = document.createElement("div");
|
||||
head.className = "compose-qi-head";
|
||||
[
|
||||
["Platform", summary.platform || "-"],
|
||||
["State", summary.state || "-"],
|
||||
["Data Points", String(summary.snapshot_count || 0)],
|
||||
["Thread", summary.thread || "-"],
|
||||
{ key: "Platform", value: summary.platform || "-" },
|
||||
{
|
||||
key: "Participant State",
|
||||
value: summary.state || "-",
|
||||
icon: stateFace.icon,
|
||||
className: stateFace.className,
|
||||
},
|
||||
{ key: "Data Points", value: String(summary.snapshot_count || 0) },
|
||||
{ key: "Thread", value: summary.thread || "-" },
|
||||
].forEach(function (pair) {
|
||||
const chip = document.createElement("div");
|
||||
chip.className = "compose-qi-chip";
|
||||
chip.innerHTML = (
|
||||
'<p class="k">' + pair[0] + "</p>"
|
||||
+ '<p class="v">' + pair[1] + "</p>"
|
||||
);
|
||||
let valueHtml = String(pair.value || "-");
|
||||
if (pair.icon) {
|
||||
valueHtml = (
|
||||
'<span class="' + String(pair.className || "") + '">'
|
||||
+ '<span class="icon is-small"><i class="' + String(pair.icon) + '"></i></span>'
|
||||
+ "</span>"
|
||||
+ "<span>" + valueHtml + "</span>"
|
||||
);
|
||||
}
|
||||
chip.innerHTML = '<p class="k">' + pair.key + "</p>" + '<p class="v">' + valueHtml + "</p>";
|
||||
head.appendChild(chip);
|
||||
});
|
||||
container.appendChild(head);
|
||||
@@ -1205,15 +1445,6 @@
|
||||
});
|
||||
container.appendChild(docsList);
|
||||
}
|
||||
const openBtn = document.createElement("a");
|
||||
openBtn.className = "button is-light is-small is-rounded";
|
||||
openBtn.href = "{{ ai_workspace_url }}";
|
||||
openBtn.innerHTML = (
|
||||
'<span class="icon is-small"><i class="{{ manual_icon_class }}"></i></span>'
|
||||
+ "<span>Open Minimal AI Workspace</span>"
|
||||
);
|
||||
openBtn.style.marginTop = "0.45rem";
|
||||
container.appendChild(openBtn);
|
||||
} catch (err) {
|
||||
setCardLoading(card, false);
|
||||
card.querySelector(".compose-ai-content").textContent =
|
||||
|
||||
@@ -51,12 +51,46 @@
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<div class="osint-results-meta">
|
||||
<div class="osint-results-meta-left">
|
||||
<div class="dropdown is-hoverable" id="{{ osint_table_id }}-columns-dropdown">
|
||||
<div class="dropdown-trigger">
|
||||
<button class="button is-small is-light" aria-haspopup="true" aria-controls="{{ osint_table_id }}-columns-menu">
|
||||
<span>Show/Hide Fields</span>
|
||||
<span class="icon is-small"><i class="fa-solid fa-angle-down"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-menu" id="{{ osint_table_id }}-columns-menu" role="menu">
|
||||
<div class="dropdown-content">
|
||||
{% for column in osint_columns %}
|
||||
<a
|
||||
class="dropdown-item osint-col-toggle"
|
||||
data-col-index="{{ forloop.counter0 }}"
|
||||
href="#"
|
||||
onclick="return false;">
|
||||
<span class="icon is-small"><i class="fa-solid fa-check"></i></span>
|
||||
<span>{{ column.label }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="button is-small is-light" type="button" disabled>
|
||||
<span class="icon is-small"><i class="fa-solid fa-database"></i></span>
|
||||
<span>Static</span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="osint-results-count">
|
||||
fetched {{ osint_result_count }} result{% if osint_result_count != 1 %}s{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="table-container osint-results-table-wrap">
|
||||
<table class="table is-fullwidth is-hoverable osint-results-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for column in osint_columns %}
|
||||
<th>
|
||||
<th data-osint-col="{{ forloop.counter0 }}" class="osint-col-{{ forloop.counter0 }}">
|
||||
{% if column.sortable %}
|
||||
<a
|
||||
class="osint-sort-link"
|
||||
@@ -87,7 +121,7 @@
|
||||
{% for row in osint_rows %}
|
||||
<tr>
|
||||
{% for cell in row.cells %}
|
||||
<td>
|
||||
<td data-osint-col="{{ forloop.counter0 }}" class="osint-col-{{ forloop.counter0 }}">
|
||||
{% if cell.kind == "id_copy" %}
|
||||
<a
|
||||
class="button is-small has-text-grey"
|
||||
@@ -219,7 +253,95 @@
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
<p class="help has-text-grey">
|
||||
{{ osint_result_count }} result{% if osint_result_count != 1 %}s{% endif %}
|
||||
</p>
|
||||
<style>
|
||||
#{{ osint_table_id }} .osint-results-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.6rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
#{{ osint_table_id }} .osint-results-meta-left {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.42rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
#{{ osint_table_id }} .osint-results-count {
|
||||
margin: 0;
|
||||
color: #6c757d;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
#{{ osint_table_id }} .osint-col-toggle .icon {
|
||||
color: #3273dc;
|
||||
}
|
||||
#{{ osint_table_id }} .osint-col-toggle.is-hidden-col .icon {
|
||||
color: #b5b5b5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const tableId = "{{ osint_table_id|escapejs }}";
|
||||
const shell = document.getElementById(tableId);
|
||||
if (!shell) {
|
||||
return;
|
||||
}
|
||||
const storageKey = "gia_osint_hidden_cols_v1:" + tableId;
|
||||
let hidden = [];
|
||||
try {
|
||||
hidden = JSON.parse(localStorage.getItem(storageKey) || "[]");
|
||||
} catch (e) {
|
||||
hidden = [];
|
||||
}
|
||||
if (!Array.isArray(hidden)) {
|
||||
hidden = [];
|
||||
}
|
||||
hidden = hidden.map(String);
|
||||
|
||||
const applyVisibility = function () {
|
||||
const hiddenSet = new Set(hidden);
|
||||
shell.querySelectorAll("[data-osint-col]").forEach(function (node) {
|
||||
const idx = String(node.getAttribute("data-osint-col") || "");
|
||||
node.style.display = hiddenSet.has(idx) ? "none" : "";
|
||||
});
|
||||
shell.querySelectorAll(".osint-col-toggle").forEach(function (toggle) {
|
||||
const idx = String(toggle.getAttribute("data-col-index") || "");
|
||||
const isHidden = hiddenSet.has(idx);
|
||||
toggle.classList.toggle("is-hidden-col", isHidden);
|
||||
const icon = toggle.querySelector("i");
|
||||
if (icon) {
|
||||
icon.className = isHidden ? "fa-solid fa-xmark" : "fa-solid fa-check";
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const persist = function () {
|
||||
try {
|
||||
localStorage.setItem(storageKey, JSON.stringify(hidden));
|
||||
} catch (e) {
|
||||
// Ignore storage failures.
|
||||
}
|
||||
};
|
||||
|
||||
shell.querySelectorAll(".osint-col-toggle").forEach(function (toggle) {
|
||||
toggle.addEventListener("click", function () {
|
||||
const idx = String(toggle.getAttribute("data-col-index") || "");
|
||||
if (!idx) {
|
||||
return;
|
||||
}
|
||||
if (hidden.includes(idx)) {
|
||||
hidden = hidden.filter(function (item) { return item !== idx; });
|
||||
} else {
|
||||
hidden.push(idx);
|
||||
}
|
||||
persist();
|
||||
applyVisibility();
|
||||
});
|
||||
});
|
||||
|
||||
applyVisibility();
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user