feat: add AI-assisted ISAC authoring

This commit is contained in:
JB
2026-07-27 14:53:36 -04:00
parent 4eb754e7c8
commit efb0ccdc7e
37 changed files with 3531 additions and 50 deletions
+2 -1
View File
@@ -90,7 +90,8 @@ export const load: PageServerLoad = async ({ cookies, url }) => {
SUM(CASE WHEN entity_type IN ('user','user_access','user_moderation','identity') THEN 1 ELSE 0 END) AS users,
SUM(CASE WHEN entity_type IN ('role','role_management') THEN 1 ELSE 0 END) AS access,
SUM(CASE WHEN entity_type='system_configuration' THEN 1 ELSE 0 END) AS system_configuration,
SUM(CASE WHEN entity_type='calculation_configuration' THEN 1 ELSE 0 END) AS calculation_configuration
SUM(CASE WHEN entity_type='calculation_configuration' THEN 1 ELSE 0 END) AS calculation_configuration,
SUM(CASE WHEN entity_type='ai_assistance' THEN 1 ELSE 0 END) AS ai_assistance
FROM audit_log`)).get()
]);
const pagination = getPagination(url, Number(total?.total ?? 0));
+1
View File
@@ -34,6 +34,7 @@
{value:'',label:'ALL EVENTS',count:data.summary.total,description:'Complete audit history'},
{value:'game_data',label:'GAME DATA',count:data.summary.game_data,description:'Builder-source mutations'},
{value:'calculation_configuration',label:'CALCULATION RULES',count:data.summary.calculation_configuration,description:'Combat model revisions and calibration'},
{value:'ai_assistance',label:'AI ASSISTANCE',count:data.summary.ai_assistance,description:'Permission-gated mechanic drafting'},
{value:'data_moderation',label:'DATA MODERATION',count:data.summary.data_moderation,description:'Proposal lifecycle decisions'},
{value:'user',label:'USER OPERATIONS',count:data.summary.users,description:'Access, moderation, identities'},
{value:'access_policy',label:'ACCESS POLICY',count:data.summary.access,description:'Role definition changes'}
+16 -3
View File
@@ -7,6 +7,7 @@
import SegmentedChoice from '$lib/components/SegmentedChoice.svelte';
import MechanicLanguageHelp from '$lib/components/MechanicLanguageHelp.svelte';
import PseudoScript from '$lib/components/PseudoScript.svelte';
import CalculationAuthoringAssistant from '$lib/components/isac/CalculationAuthoringAssistant.svelte';
import { createDraft } from '$lib/builder/loadout';
import { formatWeaponCalculationScript } from '$lib/calculations/calculation-script';
@@ -22,10 +23,10 @@
statAttributeId: string;
stageKey: string;
weight: number | string;
softCap: number | string;
hardCap: number | string;
softCap: number | string | null;
hardCap: number | string | null;
curveType: string;
curveParameter: number | string;
curveParameter: number | string | null;
weaponCategory: string;
};
type SkillProfileEditor = {
@@ -177,6 +178,14 @@
[stages[index], stages[target]] = [stages[target], stages[index]];
revisionEditing.stages = stages.map((stage, stageIndex) => ({ ...stage, displayOrder: stageIndex + 1 }));
}
function applyAiRevision(draft: Omit<RevisionEditor, 'id' | 'basedOnRevisionId'>) {
if (!revisionEditing) return;
revisionEditing = {
...draft,
id: revisionEditing.id,
basedOnRevisionId: revisionEditing.basedOnRevisionId
};
}
$effect(() => {
const token = String(form?.resultToken ?? '');
@@ -243,6 +252,10 @@
<input type="hidden" name="stagesJson" value={JSON.stringify(revisionEditing.stages)}/>
<input type="hidden" name="profilesJson" value={JSON.stringify(revisionEditing.profiles)}/>
<input type="hidden" name="skillProfilesJson" value={JSON.stringify(revisionEditing.skillProfiles)}/>
<CalculationAuthoringAssistant
currentRevision={revisionEditing}
onapply={applyAiRevision}
/>
<div class="calculation-revision-identity"><label class="field isac-record-name">REVISION NAME<input name="name" bind:value={revisionEditing.name} maxlength="120" required/></label><label class="field">NOTES<textarea name="notes" bind:value={revisionEditing.notes} maxlength="4000"></textarea></label></div>
<div class="selection-head"><div><span>ORDERED CALCULATION STAGES</span><small>BASE AND FIRING-CYCLE POSITIONS ARE LOCKED</small></div><b>{revisionEditing.stages.length} STAGES</b></div>
<div class="calculation-stage-list">{#each [...revisionEditing.stages].sort((left, right) => left.displayOrder - right.displayOrder) as stage, index}
+5
View File
@@ -174,6 +174,11 @@
attributes={data.stats}
valueColumns={talentValueColumns}
idPrefix={`protocol-talent-${editing.talent.id || 'new'}`}
aiContext={{
sourceKind: 'os_protocol_talent',
sourceName: editing?.talent?.name ?? '',
sourceDescription: editing?.talent?.description_template ?? ''
}}
allowProtocolUpgradeScaling
bind:rule={editing.talent.conditionalRule}
/>
+2 -2
View File
@@ -31,7 +31,7 @@ export const load: PageServerLoad = async ({ cookies, url }) => {
}, 'name');
const permissionPrefix = filter === 'isac' ? 'isac.%' : filter === 'data' ? 'data.%' : filter === 'users' ? 'users.%' : filter === 'audit' ? 'audit.%' : filter === 'system' ? 'system.%' : '';
const relatedPermissionFilter = filter === 'data'
? " OR p.name LIKE 'calculation.%'"
? " OR p.name LIKE 'calculation.%' OR p.name LIKE 'ai.%'"
: filter === 'system'
? " OR p.name LIKE 'site.%' OR p.name LIKE 'metrics.%' OR p.name LIKE 'images.%'"
: '';
@@ -47,7 +47,7 @@ export const load: PageServerLoad = async ({ cookies, url }) => {
const [summary, filtered, permissions] = await Promise.all([
(await database.prepare(`SELECT COUNT(*) AS total,
SUM(CASE WHEN EXISTS (SELECT 1 FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id WHERE rp.role_id=roles.id AND p.name LIKE 'isac.%') THEN 1 ELSE 0 END) AS isac,
SUM(CASE WHEN EXISTS (SELECT 1 FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id WHERE rp.role_id=roles.id AND (p.name LIKE 'data.%' OR p.name LIKE 'calculation.%')) THEN 1 ELSE 0 END) AS data,
SUM(CASE WHEN EXISTS (SELECT 1 FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id WHERE rp.role_id=roles.id AND (p.name LIKE 'data.%' OR p.name LIKE 'calculation.%' OR p.name LIKE 'ai.%')) THEN 1 ELSE 0 END) AS data,
SUM(CASE WHEN EXISTS (SELECT 1 FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id WHERE rp.role_id=roles.id AND p.name LIKE 'users.%') THEN 1 ELSE 0 END) AS users,
SUM(CASE WHEN EXISTS (SELECT 1 FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id WHERE rp.role_id=roles.id AND p.name LIKE 'audit.%') THEN 1 ELSE 0 END) AS audit
,SUM(CASE WHEN EXISTS (SELECT 1 FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id WHERE rp.role_id=roles.id AND (p.name LIKE 'system.%' OR p.name LIKE 'site.%' OR p.name LIKE 'metrics.%' OR p.name LIKE 'images.%')) THEN 1 ELSE 0 END) AS system
+5
View File
@@ -121,6 +121,11 @@
allowAssignedAbility
valueColumns={talentValueColumns}
idPrefix={`skill-chip-talent-${editing.talent.id || 'new'}`}
aiContext={{
sourceKind: 'skill_chip_talent',
sourceName: editing?.talent?.name ?? '',
sourceDescription: editing?.talent?.description_template ?? ''
}}
bind:rule={editing.talent.conditionalRule}
/>
</section>
@@ -574,6 +574,11 @@
attributes={data.attributes}
valueColumns={conditionalValueColumns}
idPrefix={`specialization-rank-${rank.rank}-conditional-${combatIndex}`}
aiContext={{
sourceKind: 'specialization_tree_rank',
sourceName: combatEffectById(combatEffect.effectId)?.name ?? `${nodeEditing?.name ?? 'Tree node'} · Rank ${rank.rank}`,
sourceDescription: rank?.description ?? ''
}}
bind:rule={combatEffect.conditionalRule}
/>
</section>
+5
View File
@@ -102,6 +102,11 @@
attributes={data.stats}
valueColumns={[{key:'tierI',variantKey:'I',label:'TIER I'},{key:'tierII',variantKey:'II',label:'TIER II'}]}
idPrefix={`talent-${talent.id || 'new'}`}
aiContext={{
sourceKind: 'talent',
sourceName: talent?.name ?? '',
sourceDescription: talent?.descriptionTemplate ?? ''
}}
bind:rule={talent.conditionalRule}
/>
{#if form?.talentError}<p class="error">{form.talentError}</p>{/if}
+5
View File
@@ -123,6 +123,11 @@
attributes={data.stats}
valueColumns={[{key:'tierI',variantKey:'I',label:'TIER I'},{key:'tierII',variantKey:'II',label:'TIER II'}]}
idPrefix={`weapon-exotic-${editing.exoticTalent.id || 'new'}`}
aiContext={{
sourceKind: 'exotic_weapon_talent',
sourceName: editing?.exoticTalent?.name ?? '',
sourceDescription: editing?.exoticTalent?.descriptionTemplate ?? ''
}}
bind:rule={editing.exoticTalent.conditionalRule}
/>
</section>