Files
GIA/core/templates/partials/ai-workspace-mitigation-panel.html

928 lines
47 KiB
HTML

<div id="mitigation-shell-{{ person.id }}" style="margin-top: 0.7rem;">
<div class="is-flex is-justify-content-space-between is-align-items-start mitigation-header" style="gap: 0.5rem; margin-bottom: 0.5rem;">
<div class="mitigation-header-main">
<p class="is-size-7 has-text-weight-semibold">Pattern Mitigation</p>
<h4 class="title is-6" style="margin-bottom: 0.2rem;">{{ plan.title|default:"Mitigation Plan" }}</h4>
{% if plan.objective %}
<p class="is-size-7">{{ plan.objective }}</p>
{% endif %}
</div>
<div class="is-flex is-flex-direction-column mitigation-header-meta" style="gap: 0.35rem;">
<form
class="box mitigation-artifact-card mitigation-editable-shell"
style="padding: 0.45rem; margin: 0; border: 1px solid rgba(0, 0, 0, 0.12); box-shadow: none;"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_meta_save' type='widget' person_id=person.id plan_id=plan.id %}"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">
<input type="hidden" name="active_tab" value="{{ active_tab|default:'plan_board' }}">
<div class="mitigation-artifact-headline">
<p class="mitigation-artifact-title">Plan Details</p>
<div class="mitigation-artifact-actions">
<button
type="button"
class="button is-small is-link is-light is-rounded mitigation-edit-btn"
data-edit-state="view"
title="Edit plan details"
onclick="giaMitigationToggleEdit(this); return false;">
<span class="icon is-small"><i class="fa-solid fa-pen"></i></span>
</button>
</div>
</div>
<p class="mitigation-artifact-meta">Created {{ plan.created_at }} · Updated {{ plan.updated_at }}</p>
<p class="mitigation-artifact-preview">
{{ plan.creation_mode|title }} / {{ plan.status|title }}
{% if plan.source_ai_result_id %}
· Source Result {{ plan.source_ai_result_id }}
{% endif %}
</p>
<div class="mitigation-edit-fields">
<div class="field" style="margin-bottom: 0.3rem;">
<div class="control">
<input class="input is-small" type="text" name="title" value="{{ plan.title }}" placeholder="Plan title" data-editable="1" readonly>
</div>
</div>
<div class="field" style="margin-bottom: 0.3rem;">
<div class="control">
<textarea class="textarea is-small" rows="2" name="objective" placeholder="Plan objective" data-editable="1" readonly>{{ plan.objective }}</textarea>
</div>
</div>
<div class="field is-grouped is-grouped-right" style="margin: 0; gap: 0.3rem;">
<div class="control">
<div class="select is-small">
<select name="creation_mode" data-editable-toggle="1" disabled>
{% for value, label in plan_creation_mode_choices %}
<option value="{{ value }}" {% if plan.creation_mode == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="control">
<div class="select is-small">
<select name="status" data-editable-toggle="1" disabled>
{% for value, label in plan_status_choices %}
<option value="{{ value }}" {% if plan.status == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
{% if notice_message %}
<div class="notification is-{{ notice_level|default:'info' }} is-light" style="padding: 0.5rem 0.65rem; margin-bottom: 0.55rem;">
{{ notice_message }}
</div>
{% endif %}
<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>
<div class="buttons are-small" style="margin: 0;">
<button
class="button is-light"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_artifact_create' type='widget' person_id=person.id plan_id=plan.id kind='rule' %}"
hx-vals='{"active_tab":"plan_board"}'
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">
<span class="icon is-small"><i class="fa-solid fa-plus"></i></span>
<span>Rule</span>
</button>
<button
class="button is-light"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_artifact_create' type='widget' person_id=person.id plan_id=plan.id kind='game' %}"
hx-vals='{"active_tab":"plan_board"}'
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">
<span class="icon is-small"><i class="fa-solid fa-plus"></i></span>
<span>Game</span>
</button>
</div>
</div>
<div class="columns is-multiline" style="margin: 0 -0.35rem;">
<div class="column is-12-mobile is-6-tablet" style="padding: 0.35rem;">
<div style="min-height: 14rem;">
<div class="is-flex is-justify-content-space-between is-align-items-center" style="gap: 0.4rem; margin-bottom: 0.45rem;">
<p class="is-size-7 has-text-weight-bold" style="letter-spacing: 0.04em; margin: 0;">RULES</p>
<button
type="button"
class="button is-small is-danger is-light"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_artifact_delete_all' type='widget' person_id=person.id plan_id=plan.id kind='rule' %}"
hx-vals='{"active_tab":"plan_board"}'
hx-confirm="Delete all rules?"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">Delete All</button>
</div>
{% for rule in rules %}
<article class="box mitigation-artifact-card" style="padding: 0.45rem; margin-bottom: 0.35rem; border: 1px solid rgba(0, 0, 0, 0.12); box-shadow: none;">
<form
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_artifact_save' type='widget' person_id=person.id plan_id=plan.id kind='rule' artifact_id=rule.id %}"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">
<input type="hidden" name="active_tab" value="{{ active_tab|default:'plan_board' }}">
<div class="mitigation-artifact-headline">
<p class="mitigation-artifact-title">{{ rule.title }}</p>
<div class="mitigation-artifact-actions">
<label class="checkbox is-size-7 mitigation-artifact-enabled">
<input type="checkbox" name="enabled" value="1" data-editable-toggle="1" disabled {% if rule.enabled %}checked{% endif %}>
On
</label>
<button
type="button"
class="button is-small is-link is-light is-rounded mitigation-edit-btn"
data-edit-state="view"
title="Edit rule"
onclick="giaMitigationToggleEdit(this); return false;">
<span class="icon is-small"><i class="fa-solid fa-pen"></i></span>
</button>
<button
type="button"
class="button is-small is-danger is-light is-rounded"
title="Delete rule"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_artifact_delete' type='widget' person_id=person.id plan_id=plan.id kind='rule' artifact_id=rule.id %}"
hx-vals='{"active_tab":"plan_board"}'
hx-confirm="Delete this rule?"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">
<span class="icon is-small"><i class="fa-solid fa-trash"></i></span>
</button>
</div>
</div>
<p class="mitigation-artifact-meta">Created {{ rule.created_at }}</p>
<p class="mitigation-artifact-preview">{{ rule.content }}</p>
<div class="mitigation-edit-fields">
<div class="field" style="margin-bottom: 0.3rem;">
<input class="input is-small" type="text" name="title" value="{{ rule.title }}" data-editable="1" readonly>
</div>
<div class="field" style="margin-bottom: 0;">
<textarea class="textarea is-small" rows="2" name="body" data-editable="1" readonly>{{ rule.content }}</textarea>
</div>
</div>
</form>
</article>
{% empty %}
<article class="box" style="padding: 0.65rem; border: 1px dashed rgba(0, 0, 0, 0.2); box-shadow: none;">
<p class="is-size-7 has-text-grey">No rules yet.</p>
</article>
{% endfor %}
</div>
</div>
<div class="column is-12-mobile is-6-tablet" style="padding: 0.35rem;">
<div style="min-height: 14rem;">
<div class="is-flex is-justify-content-space-between is-align-items-center" style="gap: 0.4rem; margin-bottom: 0.45rem;">
<p class="is-size-7 has-text-weight-bold" style="letter-spacing: 0.04em; margin: 0;">GAMES</p>
<button
type="button"
class="button is-small is-danger is-light"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_artifact_delete_all' type='widget' person_id=person.id plan_id=plan.id kind='game' %}"
hx-vals='{"active_tab":"plan_board"}'
hx-confirm="Delete all games?"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">Delete All</button>
</div>
{% for game in games %}
<article class="box mitigation-artifact-card" style="padding: 0.45rem; margin-bottom: 0.35rem; border: 1px solid rgba(0, 0, 0, 0.12); box-shadow: none;">
<form
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_artifact_save' type='widget' person_id=person.id plan_id=plan.id kind='game' artifact_id=game.id %}"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">
<input type="hidden" name="active_tab" value="{{ active_tab|default:'plan_board' }}">
<div class="mitigation-artifact-headline">
<p class="mitigation-artifact-title">{{ game.title }}</p>
<div class="mitigation-artifact-actions">
<label class="checkbox is-size-7 mitigation-artifact-enabled">
<input type="checkbox" name="enabled" value="1" data-editable-toggle="1" disabled {% if game.enabled %}checked{% endif %}>
On
</label>
<button
type="button"
class="button is-small is-link is-light is-rounded mitigation-edit-btn"
data-edit-state="view"
title="Edit game"
onclick="giaMitigationToggleEdit(this); return false;">
<span class="icon is-small"><i class="fa-solid fa-pen"></i></span>
</button>
<button
type="button"
class="button is-small is-danger is-light is-rounded"
title="Delete game"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_artifact_delete' type='widget' person_id=person.id plan_id=plan.id kind='game' artifact_id=game.id %}"
hx-vals='{"active_tab":"plan_board"}'
hx-confirm="Delete this game?"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">
<span class="icon is-small"><i class="fa-solid fa-trash"></i></span>
</button>
</div>
</div>
<p class="mitigation-artifact-meta">Created {{ game.created_at }}</p>
<p class="mitigation-artifact-preview">{{ game.instructions }}</p>
<div class="mitigation-edit-fields">
<div class="field" style="margin-bottom: 0.3rem;">
<input class="input is-small" type="text" name="title" value="{{ game.title }}" data-editable="1" readonly>
</div>
<div class="field" style="margin-bottom: 0;">
<textarea class="textarea is-small" rows="2" name="body" data-editable="1" readonly>{{ game.instructions }}</textarea>
</div>
</div>
</form>
</article>
{% empty %}
<article class="box" style="padding: 0.65rem; border: 1px dashed rgba(0, 0, 0, 0.2); box-shadow: none;">
<p class="is-size-7 has-text-grey">No games yet.</p>
</article>
{% endfor %}
</div>
</div>
</div>
</div>
<div id="mitigation-tab-{{ person.id }}-corrections" class="mitigation-tab-pane" style="display: none;">
<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">Corrections capture situation-specific clarification points.</p>
<div class="buttons are-small" style="margin: 0;">
<button
class="button is-small is-light"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_artifact_create' type='widget' person_id=person.id plan_id=plan.id kind='correction' %}"
hx-vals='{"active_tab":"corrections"}'
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">
<span class="icon is-small"><i class="fa-solid fa-plus"></i></span>
<span>Correction</span>
</button>
<button
type="button"
class="button is-small is-danger is-light"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_artifact_delete_all' type='widget' person_id=person.id plan_id=plan.id kind='correction' %}"
hx-vals='{"active_tab":"corrections"}'
hx-confirm="Delete all corrections?"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">Delete All</button>
</div>
</div>
{% if corrections %}
{% for correction in corrections %}
<article class="box mitigation-artifact-card mitigation-editable-shell" style="padding: 0.45rem; margin-bottom: 0.35rem; border: 1px solid rgba(0, 0, 0, 0.12); box-shadow: none;">
<form
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_artifact_save' type='widget' person_id=person.id plan_id=plan.id kind='correction' artifact_id=correction.id %}"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">
<input type="hidden" name="active_tab" value="{{ active_tab|default:'corrections' }}">
<div class="mitigation-artifact-headline">
<p class="mitigation-artifact-title">{{ correction.title }}</p>
<div class="mitigation-artifact-actions">
<label class="checkbox is-size-7 mitigation-artifact-enabled">
<input type="checkbox" name="enabled" value="1" data-editable-toggle="1" disabled {% if correction.enabled %}checked{% endif %}>
On
</label>
<button
type="button"
class="button is-small is-link is-light is-rounded mitigation-edit-btn"
data-edit-state="view"
title="Edit correction"
onclick="giaMitigationToggleEdit(this); return false;">
<span class="icon is-small"><i class="fa-solid fa-pen"></i></span>
</button>
<button
type="button"
class="button is-small is-danger is-light is-rounded"
title="Delete correction"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_artifact_delete' type='widget' person_id=person.id plan_id=plan.id kind='correction' artifact_id=correction.id %}"
hx-vals='{"active_tab":"corrections"}'
hx-confirm="Delete this correction?"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">
<span class="icon is-small"><i class="fa-solid fa-trash"></i></span>
</button>
</div>
</div>
<p class="mitigation-artifact-meta">Created {{ correction.created_at }}</p>
<p class="mitigation-artifact-preview">{{ correction.clarification }}</p>
<div class="mitigation-edit-fields">
<div class="field" style="margin-bottom: 0.3rem;">
<input class="input is-small" type="text" name="title" value="{{ correction.title }}" data-editable="1" readonly>
</div>
<div class="field" style="margin-bottom: 0.3rem;">
<label class="label is-small" style="margin-bottom: 0.2rem;">Message Context</label>
<textarea class="textarea is-small" rows="2" name="source_phrase" data-editable="1" readonly>{{ correction.source_phrase }}</textarea>
</div>
<div class="field" style="margin-bottom: 0.3rem;">
<label class="label is-small" style="margin-bottom: 0.2rem;">Insight</label>
<textarea class="textarea is-small" rows="2" name="body" data-editable="1" readonly>{{ correction.clarification }}</textarea>
</div>
<div class="columns is-multiline" style="margin: 0 -0.3rem;">
<div class="column is-12-mobile is-4-tablet" style="padding: 0.3rem;">
<label class="label is-small" style="margin-bottom: 0.2rem;">Perspective</label>
<div class="select is-small is-fullwidth">
<select name="perspective" data-editable-toggle="1" disabled>
{% for value, label in correction.PERSPECTIVE_CHOICES %}
<option value="{{ value }}" {% if correction.perspective == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="column is-12-mobile is-4-tablet" style="padding: 0.3rem;">
<label class="label is-small" style="margin-bottom: 0.2rem;">Share Target</label>
<div class="select is-small is-fullwidth">
<select name="share_target" data-editable-toggle="1" disabled>
{% for value, label in correction.SHARE_TARGET_CHOICES %}
<option value="{{ value }}" {% if correction.share_target == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="column is-12-mobile is-4-tablet" style="padding: 0.3rem;">
<label class="label is-small" style="margin-bottom: 0.2rem;">Language Style</label>
<div class="select is-small is-fullwidth">
<select name="language_style" data-editable-toggle="1" disabled>
{% for value, label in correction.LANGUAGE_STYLE_CHOICES %}
<option value="{{ value }}" {% if correction.language_style == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
</form>
</article>
{% endfor %}
{% else %}
<article class="box" style="padding: 0.65rem; border: 1px dashed rgba(0, 0, 0, 0.2); box-shadow: none;">
<p class="is-size-7 has-text-grey">No corrections yet.</p>
</article>
{% endif %}
</div>
<div id="mitigation-tab-{{ person.id }}-engage" class="mitigation-tab-pane" style="display: none;">
<article class="box" style="padding: 0.65rem; border: 1px solid rgba(0, 0, 0, 0.12); box-shadow: none; margin-bottom: 0.55rem;">
<p class="is-size-7" style="margin-bottom: 0.45rem;">
Build a share-ready message from a rule, game, or correction. Voice framing now lives here.
</p>
<p class="is-size-7" style="margin-bottom: 0;"><strong>Send:</strong> {{ send_state.text }}</p>
</article>
<form
id="engage-form-{{ person.id }}"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_engage_share' type='widget' person_id=person.id plan_id=plan.id %}"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">
<input type="hidden" name="active_tab" value="{{ active_tab|default:'engage' }}">
<input type="hidden" id="engage-action-input-{{ person.id }}" name="action" value="preview">
<input type="hidden" id="engage-force-send-{{ person.id }}" name="force_send" value="0">
<div class="columns is-multiline" style="margin: 0 -0.3rem;">
<div class="column is-12-mobile is-6-tablet" style="padding: 0.3rem;">
<label class="label is-small" style="margin-bottom: 0.25rem;">Source</label>
<div class="select is-small is-fullwidth">
<select name="source_ref" required onchange="giaEngageAutoPreview('{{ person.id }}');">
{% if engage_options %}
{% for option in engage_options %}
<option value="{{ option.value }}" {% if option.value == engage_form.source_ref %}selected{% endif %}>{{ option.label }}</option>
{% endfor %}
{% else %}
{% for rule in rules %}
<option value="rule:{{ rule.id }}">Rule: {{ rule.title }}</option>
{% endfor %}
{% for game in games %}
<option value="game:{{ game.id }}">Game: {{ game.title }}</option>
{% endfor %}
{% for correction in corrections %}
<option value="correction:{{ correction.id }}">Correction: {{ correction.title }}</option>
{% endfor %}
{% endif %}
</select>
</div>
</div>
<div class="column is-12-mobile is-6-tablet" style="padding: 0.3rem;">
<label class="label is-small" style="margin-bottom: 0.25rem;">Framing</label>
<input type="hidden" id="engage-framing-input-{{ person.id }}" name="framing" value="{{ engage_form.framing|default:'dont_change' }}">
<div id="engage-framing-tabs-{{ person.id }}" class="tabs is-small is-toggle is-toggle-rounded" style="margin-bottom: 0;">
<ul>
<li class="{% if engage_form.framing == 'dont_change' or engage_form.framing == 'neutral' or engage_form.framing == 'named' or not engage_form.framing %}is-active{% endif %}">
<a onclick="giaEngageSelect('{{ person.id }}', 'framing', 'dont_change', this); return false;">Don't Change</a>
</li>
<li class="{% if engage_form.framing == 'shared' %}is-active{% endif %}">
<a onclick="giaEngageSelect('{{ person.id }}', 'framing', 'shared', this); return false;">Shared (We/Us)</a>
</li>
</ul>
</div>
</div>
<div class="column is-12-mobile is-6-tablet" style="padding: 0.3rem;">
<label class="label is-small" style="margin-bottom: 0.25rem;">Share With</label>
<input type="hidden" id="engage-share-input-{{ person.id }}" name="share_target" value="{{ engage_form.share_target|default:'self' }}">
<div id="engage-share-tabs-{{ person.id }}" class="tabs is-small is-toggle is-toggle-rounded" style="margin-bottom: 0;">
<ul>
<li class="{% if engage_form.share_target == 'self' or not engage_form.share_target %}is-active{% endif %}">
<a onclick="giaEngageSelect('{{ person.id }}', 'share', 'self', this); return false;">Me</a>
</li>
<li class="{% if engage_form.share_target == 'other' %}is-active{% endif %}">
<a onclick="giaEngageSelect('{{ person.id }}', 'share', 'other', this); return false;">Other Party</a>
</li>
<li class="{% if engage_form.share_target == 'both' %}is-active{% endif %}">
<a onclick="giaEngageSelect('{{ person.id }}', 'share', 'both', this); return false;">Both Parties</a>
</li>
</ul>
</div>
</div>
<div class="column is-12" style="padding: 0.3rem;">
<label class="label is-small" style="margin-bottom: 0.25rem;">Context (optional)</label>
<textarea class="textarea is-small" rows="2" name="context_note" placeholder="One additional note for this share.">{{ engage_form.context_note }}</textarea>
</div>
</div>
<div class="buttons are-small" style="margin-top: 0.15rem;">
<button id="engage-send-btn-{{ person.id }}" type="submit" class="button is-link is-light" onclick="giaEngageSetAction('{{ person.id }}', 'send');" {% if not send_state.can_send %}disabled{% endif %}>
<span class="icon is-small"><i class="fa-solid fa-paper-plane"></i></span>
<span>Send</span>
</button>
<button type="submit" class="button is-info is-light" onclick="giaEngageSetAction('{{ person.id }}', 'queue');">
<span class="icon is-small"><i class="fa-solid fa-inbox-in"></i></span>
<span>Add To Queue</span>
</button>
</div>
</form>
{% if engage_preview %}
<article class="box {% if engage_preview_flash %}engage-preview-flash{% endif %}" style="margin-top: 0.6rem; padding: 0.65rem; border: 1px solid rgba(0, 0, 0, 0.14); box-shadow: none;">
<p class="is-size-7 has-text-weight-semibold" style="margin-bottom: 0.35rem;">Preview</p>
<pre style="margin: 0; white-space: pre-wrap; font-size: 0.78rem; line-height: 1.36;">{{ engage_preview }}</pre>
</article>
{% else %}
<article class="box" style="margin-top: 0.6rem; padding: 0.65rem; border: 1px dashed rgba(0, 0, 0, 0.2); box-shadow: none;">
<p class="is-size-7 has-text-grey">No preview yet.</p>
</article>
{% endif %}
</div>
<div id="mitigation-tab-{{ person.id }}-fundamentals" class="mitigation-tab-pane" style="display: none;">
<div class="columns is-multiline" style="margin: 0 -0.35rem;">
<div class="column is-12-mobile is-5-tablet" style="padding: 0.35rem;">
<article class="box" style="padding: 0.65rem; border: 1px solid rgba(0, 0, 0, 0.12); box-shadow: none; height: 100%;">
<p class="is-size-7 has-text-weight-semibold" style="margin-bottom: 0.4rem;">Current Fundamentals</p>
{% if plan.fundamental_items %}
<div class="content" style="margin-bottom: 0;">
<ul style="margin-top: 0;">
{% for item in plan.fundamental_items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
</div>
{% else %}
<p class="is-size-7 has-text-grey">No fundamentals yet.</p>
{% endif %}
</article>
</div>
<div class="column is-12-mobile is-7-tablet" style="padding: 0.35rem;">
<article class="box" style="padding: 0.65rem; border: 1px solid rgba(0, 0, 0, 0.12); box-shadow: none;">
<form
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_fundamentals_save' type='widget' person_id=person.id plan_id=plan.id %}"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">
<input type="hidden" name="active_tab" value="{{ active_tab|default:'fundamentals' }}">
<div class="field" style="margin-bottom: 0.45rem;">
<label class="label is-small">Edit Fundamentals (one per line)</label>
<textarea class="textarea is-small" rows="10" name="fundamentals_text">{{ fundamentals_text }}</textarea>
</div>
<button class="button is-small is-link is-light">Save Fundamentals</button>
</form>
</article>
</div>
</div>
</div>
<div id="mitigation-tab-{{ person.id }}-auto" class="mitigation-tab-pane" style="display: none;">
<article class="box" style="padding: 0.65rem; border: 1px solid rgba(0, 0, 0, 0.12); box-shadow: none; margin-bottom: 0.55rem;">
<p class="is-size-7" style="margin-bottom: 0.35rem;">
Auto checks read recent message rows and can write linked mitigation objects for this workspace conversation.
</p>
<p class="is-size-7" style="margin-bottom: 0;">
Last run: {% if auto_settings.last_run_at %}{{ auto_settings.last_run_at }}{% else %}Never{% endif %}
</p>
<p class="is-size-7" style="margin-bottom: 0;">
Created: {{ auto_settings.created_at }} | Updated: {{ auto_settings.updated_at }}
</p>
<p class="is-size-7" style="margin-bottom: 0;">
Last checked event ts: {{ auto_settings.last_checked_event_ts|default:"None" }}
</p>
{% if auto_settings.last_result_summary %}
<p class="is-size-7" style="margin-top: 0.35rem; margin-bottom: 0;">{{ auto_settings.last_result_summary }}</p>
{% endif %}
</article>
<form
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_auto' type='widget' person_id=person.id plan_id=plan.id %}"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">
<input type="hidden" name="active_tab" value="auto">
<div class="columns is-multiline" style="margin: 0 -0.3rem;">
<div class="column is-12-mobile is-6-tablet" style="padding: 0.3rem;">
<label class="checkbox is-size-7"><input type="checkbox" name="enabled" value="1" {% if auto_settings.enabled %}checked{% endif %}> Enable auto checks for this Conversation</label>
<p class="is-size-7 has-text-grey" style="margin-top: 0.2rem; margin-bottom: 0;">
Master gate. When off, automatic checks return early and no auto plan, correction, or notification actions run for this conversation.
</p>
</div>
<div class="column is-12-mobile is-6-tablet" style="padding: 0.3rem;">
<label class="checkbox is-size-7"><input type="checkbox" name="auto_pattern_recognition" value="1" {% if auto_settings.auto_pattern_recognition %}checked{% endif %}> Detect pattern signals from Message rows</label>
<p class="is-size-7 has-text-grey" style="margin-top: 0.2rem; margin-bottom: 0;">
Controls background trigger behavior only. If disabled, auto-triggered scans are skipped; manual "Run Check Now" can still run.
</p>
</div>
<div class="column is-12-mobile is-6-tablet" style="padding: 0.3rem;">
<label class="checkbox is-size-7"><input type="checkbox" name="auto_create_mitigation" value="1" {% if auto_settings.auto_create_mitigation %}checked{% endif %}> Create a Plan when the Conversation has none</label>
<p class="is-size-7 has-text-grey" style="margin-top: 0.2rem; margin-bottom: 0;">
On AI pane load, if no plan exists and automation is enabled, a baseline plan is auto-created from recent messages.
</p>
</div>
<div class="column is-12-mobile is-6-tablet" style="padding: 0.3rem;">
<label class="checkbox is-size-7"><input type="checkbox" name="auto_create_corrections" value="1" {% if auto_settings.auto_create_corrections %}checked{% endif %}> Create Correction rows linked to the Plan</label>
<p class="is-size-7 has-text-grey" style="margin-top: 0.2rem; margin-bottom: 0;">
Writes up to 8 detected correction candidates per run, deduplicated by title + clarification, and links them to this plan.
</p>
</div>
<div class="column is-12-mobile is-6-tablet" style="padding: 0.3rem;">
<label class="checkbox is-size-7"><input type="checkbox" name="auto_notify_enabled" value="1" {% if auto_settings.auto_notify_enabled %}checked{% endif %}> Notify when auto writes new Correction rows</label>
<p class="is-size-7 has-text-grey" style="margin-top: 0.2rem; margin-bottom: 0;">
Sends a notification when violations are found (with count + top preview), using NTFY overrides if provided, otherwise default notifications.
</p>
</div>
<div class="column is-12-mobile is-6-tablet" style="padding: 0.3rem;">
<label class="label is-small" style="margin-bottom: 0.25rem;">Message rows per check</label>
<input class="input is-small" type="number" min="10" max="200" name="sample_message_window" value="{{ auto_settings.sample_message_window }}">
</div>
<div class="column is-12-mobile is-6-tablet" style="padding: 0.3rem;">
<label class="label is-small" style="margin-bottom: 0.25rem;">Cooldown seconds between checks</label>
<input class="input is-small" type="number" min="0" max="86400" name="check_cooldown_seconds" value="{{ auto_settings.check_cooldown_seconds }}">
</div>
<div class="column is-12-mobile is-6-tablet" style="padding: 0.3rem;">
<label class="label is-small" style="margin-bottom: 0.25rem;">NTFY topic override for auto</label>
<input class="input is-small" type="text" name="ntfy_topic_override" value="{{ auto_settings.ntfy_topic_override|default:'' }}" placeholder="Optional topic override">
</div>
<div class="column is-12" style="padding: 0.3rem;">
<label class="label is-small" style="margin-bottom: 0.25rem;">NTFY URL override for auto</label>
<input class="input is-small" type="text" name="ntfy_url_override" value="{{ auto_settings.ntfy_url_override|default:'' }}" placeholder="Optional NTFY URL override">
</div>
<div class="column is-12" style="padding: 0.3rem;">
<p class="is-size-7 has-text-grey">If overrides are empty, notifications fall back to Notification Settings topic/url.</p>
</div>
</div>
<div class="buttons are-small" style="margin-top: 0.2rem;">
<button class="button is-link is-light" name="action" value="save">Save Auto Controls</button>
<button class="button is-primary is-light" name="action" value="run_now">Run Check Now</button>
</div>
</form>
</div>
<div id="mitigation-tab-{{ person.id }}-ask_ai" class="mitigation-tab-pane" style="display: none;">
<form
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_export' type='widget' person_id=person.id plan_id=plan.id %}"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML"
style="margin-bottom: 0.55rem;">
<input type="hidden" name="active_tab" value="{{ active_tab|default:'ask_ai' }}">
<div class="field is-grouped is-grouped-multiline is-align-items-flex-end" style="margin-bottom: 0; gap: 0.35rem;">
<div class="control">
<label class="label is-small" style="margin-bottom: 0.25rem;">Bundle</label>
<div class="select is-small">
<select name="artifact_type">
{% for value, label in artifact_type_choices %}
<option value="{{ value }}">{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="control">
<label class="label is-small" style="margin-bottom: 0.25rem;">Format</label>
<div class="select is-small">
<select name="export_format">
{% for value, label in artifact_format_choices %}
<option value="{{ value }}">{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="control">
<button class="button is-small is-link is-light" style="margin-top: 1.35rem;">
<span class="icon is-small"><i class="fa-solid fa-file-export"></i></span>
<span>Export</span>
</button>
</div>
</div>
</form>
{% if latest_export %}
<article class="box" style="padding: 0.55rem; margin-bottom: 0.6rem; border: 1px dashed rgba(0, 0, 0, 0.25); box-shadow: none;">
<p class="is-size-7 has-text-weight-semibold" style="margin-bottom: 0.3rem;">
Last Export: {{ latest_export.artifact_type|title }} ({{ latest_export.export_format|upper }})
</p>
<p class="is-size-7" style="margin-bottom: 0.3rem;">
Created {{ latest_export.created_at }} |
Protocol {{ latest_export.protocol_version }} |
Meta {{ latest_export.meta }}
</p>
<pre style="max-height: 14rem; overflow: auto; margin: 0; white-space: pre-wrap; font-size: 0.72rem; line-height: 1.28;">{{ latest_export.payload }}</pre>
</article>
{% endif %}
<article class="box" style="padding: 0.65rem; 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.4rem;">Ask AI</p>
<div style="max-height: 12rem; overflow-y: auto; margin-bottom: 0.55rem; padding-right: 0.2rem;">
{% for message in mitigation_messages %}
<div style="margin-bottom: 0.45rem;">
<span class="tag is-light is-small">{{ message.role }}</span>
<span class="tag is-light is-small">{{ message.created_at }}</span>
<div style="margin-top: 0.15rem; white-space: pre-wrap;">{{ message.text }}</div>
</div>
{% empty %}
<p class="is-size-7 has-text-grey">No messages yet.</p>
{% endfor %}
</div>
<form
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-post="{% url 'ai_workspace_mitigation_chat' type='widget' person_id=person.id plan_id=plan.id %}"
hx-target="#mitigation-shell-{{ person.id }}"
hx-swap="outerHTML">
<input type="hidden" name="active_tab" value="{{ active_tab|default:'ask_ai' }}">
<div class="field" style="margin-bottom: 0.5rem;">
<div class="control">
<textarea name="message" class="textarea is-small" rows="2" placeholder="Refine the plan or request a new lens..."></textarea>
</div>
</div>
<button class="button is-small is-primary is-light">
<span class="icon is-small"><i class="fa-solid fa-comments"></i></span>
<span>Ask AI</span>
</button>
</form>
</article>
</div>
</div>
<style>
@keyframes engagePreviewPulse {
0% { background-color: rgba(255, 255, 255, 1); }
45% { background-color: rgba(236, 246, 255, 1); }
100% { background-color: rgba(255, 255, 255, 1); }
}
#mitigation-shell-{{ person.id }} .mitigation-header {
flex-wrap: wrap;
}
#mitigation-shell-{{ person.id }} .mitigation-artifact-card {
border-radius: 8px;
}
#mitigation-shell-{{ person.id }} .mitigation-artifact-headline {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 0.35rem;
}
#mitigation-shell-{{ person.id }} .mitigation-artifact-title {
margin: 0;
font-size: 0.78rem;
font-weight: 600;
line-height: 1.2;
overflow-wrap: anywhere;
word-break: break-word;
}
#mitigation-shell-{{ person.id }} .mitigation-artifact-actions {
display: inline-flex;
align-items: center;
gap: 0.25rem;
flex-shrink: 0;
}
#mitigation-shell-{{ person.id }} .mitigation-artifact-enabled {
margin-right: 0.1rem;
color: #6d7583;
}
#mitigation-shell-{{ person.id }} .mitigation-artifact-meta {
margin: 0.18rem 0 0.2rem;
font-size: 0.67rem;
color: #7b8492;
}
#mitigation-shell-{{ person.id }} .mitigation-artifact-preview {
margin: 0;
font-size: 0.73rem;
color: #4c5665;
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
#mitigation-shell-{{ person.id }} .mitigation-edit-fields {
display: none;
margin-top: 0.32rem;
}
#mitigation-shell-{{ person.id }} .mitigation-artifact-card.is-editing .mitigation-edit-fields {
display: block;
}
#mitigation-shell-{{ person.id }} .mitigation-artifact-card.is-editing .mitigation-artifact-preview {
display: none;
}
#mitigation-shell-{{ person.id }} .mitigation-header-main,
#mitigation-shell-{{ person.id }} .mitigation-header-meta {
min-width: 0;
flex: 1 1 16rem;
}
#mitigation-shell-{{ person.id }} .mitigation-header-meta .tag {
white-space: normal;
word-break: break-word;
overflow-wrap: anywhere;
height: auto;
line-height: 1.2;
max-width: 100%;
padding-top: 0.24em;
padding-bottom: 0.24em;
}
#mitigation-shell-{{ person.id }} .columns,
#mitigation-shell-{{ person.id }} .column {
min-width: 0;
}
#mitigation-shell-{{ person.id }} .input,
#mitigation-shell-{{ person.id }} .textarea,
#mitigation-shell-{{ person.id }} .select,
#mitigation-shell-{{ person.id }} .select select {
max-width: 100%;
}
#mitigation-shell-{{ person.id }} pre {
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-word;
}
@media (max-width: 768px) {
#mitigation-shell-{{ person.id }} .mitigation-header-main,
#mitigation-shell-{{ person.id }} .mitigation-header-meta {
flex-basis: 100%;
width: 100%;
}
#mitigation-shell-{{ person.id }} .field.is-grouped {
flex-wrap: wrap;
}
#mitigation-shell-{{ person.id }} .field.is-grouped > .control {
width: 100%;
}
#mitigation-shell-{{ person.id }} .buttons {
flex-wrap: wrap;
}
#mitigation-shell-{{ person.id }} .tabs ul {
flex-wrap: wrap;
}
}
#mitigation-shell-{{ person.id }} .engage-preview-flash {
animation: engagePreviewPulse 850ms ease-in-out 1;
}
</style>
<script>
(function() {
const personId = "{{ person.id }}";
const canSend = "{{ send_state.can_send|yesno:'1,0' }}" === "1";
function resizeEditableTextareas(root) {
if (!root) return;
root.querySelectorAll('textarea[data-editable="1"]').forEach(function(area) {
area.style.height = "auto";
area.style.height = Math.max(area.scrollHeight, 72) + "px";
});
}
window.giaEngageSyncSendOverride = function(pid) {
if (pid !== personId) return;
const forceInput = document.getElementById("engage-force-send-" + pid);
const sendBtn = document.getElementById("engage-send-btn-" + pid);
const force =
!!(window.giaWorkspaceState
&& window.giaWorkspaceState[pid]
&& window.giaWorkspaceState[pid].forceSend);
if (forceInput) {
forceInput.value = force ? "1" : "0";
}
if (sendBtn) {
sendBtn.disabled = !canSend && !force;
}
};
function setActiveTabHiddenFields(tabName) {
const root = document.getElementById("mitigation-shell-" + personId);
if (!root) return;
root.querySelectorAll('input[name="active_tab"]').forEach(function(input) {
input.value = tabName;
});
resizeEditableTextareas(root);
}
window.giaMitigationShowTab = function(pid, tabName) {
if (pid !== personId) return;
["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) return;
const active = name === tabName;
pane.style.display = active ? "block" : "none";
if (tab) {
tab.classList.toggle("is-active", active);
}
});
setActiveTabHiddenFields(tabName);
};
window.giaMitigationToggleEdit = function(button) {
const form = button.closest("form");
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>';
resizeEditableTextareas(form);
} else {
form.requestSubmit();
}
};
window.giaEngageSetAction = function(pid, action) {
if (pid !== personId) return;
const actionInput = document.getElementById("engage-action-input-" + pid);
if (actionInput) {
actionInput.value = action;
}
if (action === "send") {
window.giaEngageSyncSendOverride(pid);
}
};
window.giaEngageAutoPreview = function(pid) {
if (pid !== personId) return;
const form = document.getElementById("engage-form-" + pid);
if (!form) return;
window.giaEngageSetAction(pid, "preview");
form.requestSubmit();
};
window.giaEngageSelect = function(pid, kind, value, node) {
if (pid !== personId) return;
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) return;
const ul = li.parentElement;
if (!ul) return;
Array.from(ul.children).forEach(function(child) {
child.classList.remove("is-active");
});
li.classList.add("is-active");
window.giaEngageAutoPreview(pid);
};
window.giaMitigationShowTab(personId, "{{ active_tab|default:'plan_board' }}");
resizeEditableTextareas(document.getElementById("mitigation-shell-" + personId));
window.giaEngageSyncSendOverride(personId);
})();
</script>