934 lines
48 KiB
HTML
934 lines
48 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">
|
|
<input type="hidden" id="engage-target-input-{{ person.id }}" name="target_identifier_id" value="{{ engage_form.target_identifier_id }}">
|
|
<div class="columns is-multiline" style="margin: 0 -0.3rem;">
|
|
{% if send_target_bundle.options %}
|
|
<div class="column is-12-mobile is-6-tablet" style="padding: 0.3rem;">
|
|
<label class="label is-small" style="margin-bottom: 0.25rem;">Target Platform</label>
|
|
<div class="select is-small is-fullwidth">
|
|
<select id="engage-target-select-{{ person.id }}" onchange="giaEngageSetTarget('{{ person.id }}', this.value);">
|
|
{% for option in send_target_bundle.options %}
|
|
<option value="{{ option.id }}" {% if option.id == engage_form.target_identifier_id %}selected{% endif %}>
|
|
{{ option.service_label }} · {{ option.identifier }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<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";
|
|
const TAB_NAMES = ["plan_board", "corrections", "engage", "fundamentals", "auto", "ask_ai"];
|
|
const defineGlobal = function(name, handler) {
|
|
if (typeof window[name] !== "function") {
|
|
window[name] = handler;
|
|
}
|
|
};
|
|
const resizeEditableTextareas = function(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";
|
|
});
|
|
};
|
|
|
|
const syncActiveTab = function(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.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;
|
|
}
|
|
};
|
|
|
|
window.giaMitigationShowTab = function(pid, tabName) {
|
|
if (pid !== personId) return;
|
|
TAB_NAMES.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);
|
|
});
|
|
syncActiveTab(tabName);
|
|
};
|
|
|
|
defineGlobal("giaMitigationToggleEdit", function(button) {
|
|
const form = button && button.closest ? button.closest("form") : null;
|
|
if (!form) return;
|
|
const editing = button.dataset.editState === "edit";
|
|
if (!editing) {
|
|
form.querySelectorAll('[data-editable="1"]').forEach(function(field) { field.removeAttribute("readonly"); });
|
|
form.querySelectorAll('[data-editable-toggle="1"]').forEach(function(field) { field.removeAttribute("disabled"); });
|
|
const card = form.closest(".mitigation-artifact-card");
|
|
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);
|
|
return;
|
|
}
|
|
form.requestSubmit();
|
|
});
|
|
|
|
defineGlobal("giaEngageSetAction", function(pid, action) {
|
|
const actionInput = document.getElementById("engage-action-input-" + pid);
|
|
if (actionInput) actionInput.value = action;
|
|
if (action === "send") window.giaEngageSyncSendOverride(pid);
|
|
});
|
|
|
|
defineGlobal("giaEngageAutoPreview", function(pid) {
|
|
const form = document.getElementById("engage-form-" + pid);
|
|
if (!form) return;
|
|
window.giaEngageSetAction(pid, "preview");
|
|
form.requestSubmit();
|
|
});
|
|
|
|
window.giaEngageSetTarget = function(pid, targetId) {
|
|
if (pid !== personId) return;
|
|
const normalized = String(targetId || "").trim();
|
|
const input = document.getElementById("engage-target-input-" + pid);
|
|
if (input) input.value = normalized;
|
|
const parentSelect = document.getElementById("ai-target-select-" + pid);
|
|
if (parentSelect && normalized) parentSelect.value = normalized;
|
|
};
|
|
|
|
defineGlobal("giaEngageSelect", function(pid, kind, value, node) {
|
|
const inputId = kind === "share" ? ("engage-share-input-" + pid) : (kind === "framing" ? ("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) return;
|
|
Array.from(li.parentElement.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));
|
|
const parentTarget = document.getElementById("ai-target-select-" + personId);
|
|
if (parentTarget) {
|
|
window.giaEngageSetTarget(personId, parentTarget.value);
|
|
}
|
|
const localTarget = document.getElementById("engage-target-select-" + personId);
|
|
if (localTarget) {
|
|
window.giaEngageSetTarget(personId, localTarget.value);
|
|
}
|
|
window.giaEngageSyncSendOverride(personId);
|
|
})();
|
|
</script>
|