@resolveio/client-lib-aicoder-dashboard 21.0.4 → 21.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import*as i0 from"@angular/core";import{Injectable,EventEmitter,Output,Input,Component,NgModule}from"@angular/core";import*as i1 from"@angular/common/http";import{HttpHeaders,HttpClientModule}from"@angular/common/http";import*as i2 from"@resolveio/client-lib-core";import{Observable,throwError,interval}from"rxjs";import{switchMap,map,tap,shareReplay}from"rxjs/operators";import*as i2$1 from"@angular/forms";import{FormsModule}from"@angular/forms";import{CommonModule}from"@angular/common";class AICoderDashboardService{buildDefaultSummary(){return{overview:{app_name:"ResolveIO App",app_status:"Active",plan_tier:"small",max_users:10},database:{name:"resolveio",host:"127.0.0.1",status:"Ready"},infrastructure:{current_tier:"small",available_tiers:["small","medium","large"],max_users:10,backend_status:"Ready"},code_changes:{release_notes:"Latest updates are live and available.",last_deployed_at:"",recent_updates:["Overview tab highlights app status and plan tier.","Infrastructure tab shows Small, Medium, and Large options.","Code Changes tab summarizes customer-facing updates."]},subscription:{current_package:"Small",current_tier:"small",package_price_label:"$99 / month",billing_status:"Paid",billing_mode:"Subscription",subscription_status:"active",current_period_end:"",manage_url:"",can_downgrade:!0,downgrade_reason:""}}}buildTierOptions(){return[{id:"small",label:"Small",description:"Great for focused teams and lighter usage."},{id:"medium",label:"Medium",description:"Balanced capacity for growing teams and workflows."},{id:"large",label:"Large",description:"Higher capacity for heavy usage and larger teams."}]}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardService,deps:[],target:i0.ɵɵFactoryTarget.Injectable});static"ɵprov"=i0.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardService,providedIn:"root"})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardService,decorators:[{type:Injectable,args:[{providedIn:"root"}]}]});class AICoderDashboardComponent{dashboardService;summary;loading=!1;errorMessage="";activeTab="overview";tierOptions;tabChange=new EventEmitter;retry=new EventEmitter;tierChange=new EventEmitter;manageSubscription=new EventEmitter;constructor(t){this.dashboardService=t,this.summary=this.dashboardService.buildDefaultSummary(),this.tierOptions=this.dashboardService.buildTierOptions()}setTab(t){this.activeTab!==t&&this.tabChange.emit(t)}requestRetry(){this.retry.emit()}requestTierChange(t){this.tierChange.emit(t)}requestManageSubscription(){this.manageSubscription.emit(this.resolveManageSubscriptionUrl())}isCurrentTier(t){return String(this.summary?.infrastructure?.current_tier||"").trim().toLowerCase()===t}hasManageSubscriptionUrl(){return!!this.resolveManageSubscriptionUrl()}resolveManageSubscriptionUrl(){return String(this.summary?.subscription?.manage_url||"").trim()}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardComponent,deps:[{token:AICoderDashboardService}],target:i0.ɵɵFactoryTarget.Component});static"ɵcmp"=i0.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"21.1.2",type:AICoderDashboardComponent,isStandalone:!1,selector:"resolveio-client-lib-aicoder-dashboard",inputs:{summary:"summary",loading:"loading",errorMessage:"errorMessage",activeTab:"activeTab",tierOptions:"tierOptions"},outputs:{tabChange:"tabChange",retry:"retry",tierChange:"tierChange",manageSubscription:"manageSubscription"},ngImport:i0,template:'<div class="aicoder-dashboard">\n\t<div class="aicoder-dashboard__tabs">\n\t\t<button type="button" [class.is-active]="activeTab === \'overview\'" (click)="setTab(\'overview\')">Overview</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'database\'" (click)="setTab(\'database\')">Database</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'infrastructure\'" (click)="setTab(\'infrastructure\')">Infrastructure</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'code_changes\'" (click)="setTab(\'code_changes\')">Code Changes</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'subscription\'" (click)="setTab(\'subscription\')">Subscription</button>\n\t</div>\n\n\t@if (loading) {\n\t\t<div class="aicoder-dashboard__state">Loading dashboard details...</div>\n\t} @else if (errorMessage) {\n\t\t<div class="aicoder-dashboard__state aicoder-dashboard__state--error">\n\t\t\t<span>{{ errorMessage }}</span>\n\t\t\t<button type="button" (click)="requestRetry()">Retry</button>\n\t\t</div>\n\t} @else {\n\t\t@if (activeTab === \'overview\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>App</span><strong>{{ summary.overview.app_name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.overview.app_status }}</strong></div>\n\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.overview.plan_tier }}</strong></div>\n\t\t\t\t<div class="tile"><span>Max users</span><strong>{{ summary.overview.max_users }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'database\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>Name</span><strong>{{ summary.database.name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Host</span><strong>{{ summary.database.host }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.database.status }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'infrastructure\') {\n\t\t\t<div class="aicoder-dashboard__tiers">\n\t\t\t\t@for (tier of tierOptions; track tier.id) {\n\t\t\t\t\t<div class="tier" [class.tier--active]="isCurrentTier(tier.id)">\n\t\t\t\t\t\t<h4>{{ tier.label }}</h4>\n\t\t\t\t\t\t<p>{{ tier.description }}</p>\n\t\t\t\t\t\t<button type="button" (click)="requestTierChange(tier.id)">Choose {{ tier.label }}</button>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'code_changes\') {\n\t\t\t<div class="aicoder-dashboard__changes">\n\t\t\t\t<p>{{ summary.code_changes.release_notes }}</p>\n\t\t\t\t<ul>\n\t\t\t\t\t@for (item of summary.code_changes.recent_updates; track item) {\n\t\t\t\t\t\t<li>{{ item }}</li>\n\t\t\t\t\t}\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'subscription\') {\n\t\t\t<div class="aicoder-dashboard__subscription">\n\t\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t\t<div class="tile"><span>Package</span><strong>{{ summary.subscription?.current_package || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.subscription?.current_tier || summary.overview.plan_tier || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Price</span><strong>{{ summary.subscription?.package_price_label || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing status</span><strong>{{ summary.subscription?.billing_status || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing mode</span><strong>{{ summary.subscription?.billing_mode || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Subscription</span><strong>{{ summary.subscription?.subscription_status || \'N/A\' }}</strong></div>\n\t\t\t\t</div>\n\t\t\t\t@if (summary.subscription?.current_period_end) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint">Current period ends: {{ summary.subscription?.current_period_end }}</p>\n\t\t\t\t}\n\t\t\t\t@if (summary.subscription?.downgrade_reason) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint aicoder-dashboard__hint--warning">{{ summary.subscription?.downgrade_reason }}</p>\n\t\t\t\t}\n\t\t\t\t<div class="aicoder-dashboard__subscription-actions">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype="button"\n\t\t\t\t\t\tclass="aicoder-dashboard__primary-action"\n\t\t\t\t\t\t(click)="requestManageSubscription()"\n\t\t\t\t\t\t[disabled]="!hasManageSubscriptionUrl()"\n\t\t\t\t\t>\n\t\t\t\t\t\tUpgrade Or Manage Plan\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t}\n\t}\n</div>\n',styles:[".aicoder-dashboard{--aicoder-primary: #0f4c81;font-family:inherit}.aicoder-dashboard__tabs{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:.75rem}.aicoder-dashboard__tabs button{border:1px solid rgba(15,23,42,.2);background:#fff;padding:.45rem .75rem;border-radius:8px;font-weight:600}.aicoder-dashboard__tabs button.is-active{border-color:var(--aicoder-primary);background:#0f4c811a}.aicoder-dashboard__state{padding:.85rem;border-radius:10px;background:#f8fafc}.aicoder-dashboard__state--error{display:flex;justify-content:space-between;align-items:center;gap:.5rem;background:#fff7ed}.aicoder-dashboard__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem}.tile{padding:.8rem;border:1px solid rgba(15,23,42,.12);border-radius:10px;background:#fff}.tile span{display:block;font-size:.74rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#0f172a9e}.tile strong{display:block;margin-top:.3rem;font-size:1rem}.aicoder-dashboard__tiers{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:.75rem}.tier{padding:.85rem;border-radius:10px;border:1px solid rgba(15,23,42,.12);background:#f8fafc}.tier--active{border-color:var(--aicoder-primary);background:#0f4c8114}.tier h4{margin:0}.tier p{margin:.45rem 0}.aicoder-dashboard__changes ul{margin:0;padding-left:1.1rem}.aicoder-dashboard__subscription{display:grid;gap:.75rem}.aicoder-dashboard__subscription-actions{display:flex;flex-wrap:wrap;gap:.5rem}.aicoder-dashboard__primary-action{border:1px solid var(--aicoder-primary);background:var(--aicoder-primary);color:#fff;padding:.5rem .85rem;border-radius:8px;font-weight:700}.aicoder-dashboard__primary-action[disabled]{opacity:.55;cursor:not-allowed}.aicoder-dashboard__hint{margin:0;color:#334155;font-size:.88rem}.aicoder-dashboard__hint--warning{color:#9a3412}\n"]})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardComponent,decorators:[{type:Component,args:[{selector:"resolveio-client-lib-aicoder-dashboard",standalone:!1,template:'<div class="aicoder-dashboard">\n\t<div class="aicoder-dashboard__tabs">\n\t\t<button type="button" [class.is-active]="activeTab === \'overview\'" (click)="setTab(\'overview\')">Overview</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'database\'" (click)="setTab(\'database\')">Database</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'infrastructure\'" (click)="setTab(\'infrastructure\')">Infrastructure</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'code_changes\'" (click)="setTab(\'code_changes\')">Code Changes</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'subscription\'" (click)="setTab(\'subscription\')">Subscription</button>\n\t</div>\n\n\t@if (loading) {\n\t\t<div class="aicoder-dashboard__state">Loading dashboard details...</div>\n\t} @else if (errorMessage) {\n\t\t<div class="aicoder-dashboard__state aicoder-dashboard__state--error">\n\t\t\t<span>{{ errorMessage }}</span>\n\t\t\t<button type="button" (click)="requestRetry()">Retry</button>\n\t\t</div>\n\t} @else {\n\t\t@if (activeTab === \'overview\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>App</span><strong>{{ summary.overview.app_name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.overview.app_status }}</strong></div>\n\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.overview.plan_tier }}</strong></div>\n\t\t\t\t<div class="tile"><span>Max users</span><strong>{{ summary.overview.max_users }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'database\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>Name</span><strong>{{ summary.database.name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Host</span><strong>{{ summary.database.host }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.database.status }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'infrastructure\') {\n\t\t\t<div class="aicoder-dashboard__tiers">\n\t\t\t\t@for (tier of tierOptions; track tier.id) {\n\t\t\t\t\t<div class="tier" [class.tier--active]="isCurrentTier(tier.id)">\n\t\t\t\t\t\t<h4>{{ tier.label }}</h4>\n\t\t\t\t\t\t<p>{{ tier.description }}</p>\n\t\t\t\t\t\t<button type="button" (click)="requestTierChange(tier.id)">Choose {{ tier.label }}</button>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'code_changes\') {\n\t\t\t<div class="aicoder-dashboard__changes">\n\t\t\t\t<p>{{ summary.code_changes.release_notes }}</p>\n\t\t\t\t<ul>\n\t\t\t\t\t@for (item of summary.code_changes.recent_updates; track item) {\n\t\t\t\t\t\t<li>{{ item }}</li>\n\t\t\t\t\t}\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'subscription\') {\n\t\t\t<div class="aicoder-dashboard__subscription">\n\t\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t\t<div class="tile"><span>Package</span><strong>{{ summary.subscription?.current_package || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.subscription?.current_tier || summary.overview.plan_tier || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Price</span><strong>{{ summary.subscription?.package_price_label || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing status</span><strong>{{ summary.subscription?.billing_status || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing mode</span><strong>{{ summary.subscription?.billing_mode || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Subscription</span><strong>{{ summary.subscription?.subscription_status || \'N/A\' }}</strong></div>\n\t\t\t\t</div>\n\t\t\t\t@if (summary.subscription?.current_period_end) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint">Current period ends: {{ summary.subscription?.current_period_end }}</p>\n\t\t\t\t}\n\t\t\t\t@if (summary.subscription?.downgrade_reason) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint aicoder-dashboard__hint--warning">{{ summary.subscription?.downgrade_reason }}</p>\n\t\t\t\t}\n\t\t\t\t<div class="aicoder-dashboard__subscription-actions">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype="button"\n\t\t\t\t\t\tclass="aicoder-dashboard__primary-action"\n\t\t\t\t\t\t(click)="requestManageSubscription()"\n\t\t\t\t\t\t[disabled]="!hasManageSubscriptionUrl()"\n\t\t\t\t\t>\n\t\t\t\t\t\tUpgrade Or Manage Plan\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t}\n\t}\n</div>\n',styles:[".aicoder-dashboard{--aicoder-primary: #0f4c81;font-family:inherit}.aicoder-dashboard__tabs{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:.75rem}.aicoder-dashboard__tabs button{border:1px solid rgba(15,23,42,.2);background:#fff;padding:.45rem .75rem;border-radius:8px;font-weight:600}.aicoder-dashboard__tabs button.is-active{border-color:var(--aicoder-primary);background:#0f4c811a}.aicoder-dashboard__state{padding:.85rem;border-radius:10px;background:#f8fafc}.aicoder-dashboard__state--error{display:flex;justify-content:space-between;align-items:center;gap:.5rem;background:#fff7ed}.aicoder-dashboard__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem}.tile{padding:.8rem;border:1px solid rgba(15,23,42,.12);border-radius:10px;background:#fff}.tile span{display:block;font-size:.74rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#0f172a9e}.tile strong{display:block;margin-top:.3rem;font-size:1rem}.aicoder-dashboard__tiers{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:.75rem}.tier{padding:.85rem;border-radius:10px;border:1px solid rgba(15,23,42,.12);background:#f8fafc}.tier--active{border-color:var(--aicoder-primary);background:#0f4c8114}.tier h4{margin:0}.tier p{margin:.45rem 0}.aicoder-dashboard__changes ul{margin:0;padding-left:1.1rem}.aicoder-dashboard__subscription{display:grid;gap:.75rem}.aicoder-dashboard__subscription-actions{display:flex;flex-wrap:wrap;gap:.5rem}.aicoder-dashboard__primary-action{border:1px solid var(--aicoder-primary);background:var(--aicoder-primary);color:#fff;padding:.5rem .85rem;border-radius:8px;font-weight:700}.aicoder-dashboard__primary-action[disabled]{opacity:.55;cursor:not-allowed}.aicoder-dashboard__hint{margin:0;color:#334155;font-size:.88rem}.aicoder-dashboard__hint--warning{color:#9a3412}\n"]}]}],ctorParameters:()=>[{type:AICoderDashboardService}],propDecorators:{summary:[{type:Input}],loading:[{type:Input}],errorMessage:[{type:Input}],activeTab:[{type:Input}],tierOptions:[{type:Input}],tabChange:[{type:Output}],retry:[{type:Output}],tierChange:[{type:Output}],manageSubscription:[{type:Output}]}});class AICoderWorkbenchApiService{http;tokenManager;sessionHeaderName="X-AI-Coder-Session";appTokenHeaderName="X-AI-Coder-App-Token";sessionRefreshSkewMs=6e4;defaultSessionTtlMs=144e5;sessionByApiBase=new Map;exchangeByApiBase=new Map;constructor(t,e){this.http=t,this.tokenManager=e}get(t,e,a=""){return this.authHeaders(t,a).pipe(switchMap(a=>this.http.get(this.buildUrl(t,e),{headers:a})))}post(t,e,a={},o=""){return this.authHeaders(t,o).pipe(switchMap(o=>this.http.post(this.buildUrl(t,e),a,{headers:o})))}patch(t,e,a={},o=""){return this.authHeaders(t,o).pipe(switchMap(o=>this.http.patch(this.buildUrl(t,e),a,{headers:o})))}resolveDefaultApiBase(){const t=String(window?.location?.hostname||"").trim().toLowerCase();return"aicoder.resolveio.com"===t||t.endsWith(".aicoder.resolveio.com")?"https://backend.aicoder.resolveio.com":""}authHeaders(t,e=""){const a=String(e||"").trim();return a?new Observable(t=>{t.next(new HttpHeaders({[this.appTokenHeaderName]:a})),t.complete()}):this.ensureSessionToken(t).pipe(map(t=>new HttpHeaders({[this.sessionHeaderName]:t})))}ensureSessionToken(t){const e=this.normalizeApiBase(t),a=this.sessionByApiBase.get(e);if(a&&a.token&&a.expiresAtMs>Date.now()+this.sessionRefreshSkewMs)return new Observable(t=>{t.next(a.token),t.complete()});const o=this.exchangeByApiBase.get(e);if(o)return o;const r=String(this.tokenManager.getToken("accessToken")||"").trim();if(!r)return throwError(()=>new Error("Sign in before using AICoder."));const i=new HttpHeaders({Authorization:`Bearer ${r}`}),n=this.http.post(this.buildUrl(e,"/api/ai-coder/auth/session"),{},{headers:i}).pipe(map(t=>{const a=String(t?.token||"").trim();if(!a)throw new Error("AICoder session response did not include a token.");return this.sessionByApiBase.set(e,{token:a,expiresAtMs:this.resolveSessionExpiresAtMs(t)}),a}),tap({error:()=>this.sessionByApiBase.delete(e),complete:()=>this.exchangeByApiBase.delete(e)}),shareReplay(1));return this.exchangeByApiBase.set(e,n),n}resolveSessionExpiresAtMs(t){const e=t?.expires_at?new Date(t.expires_at).getTime():0;if(Number.isFinite(e)&&e>Date.now())return e;const a=1e3*Number(t?.expires_in_seconds||0);return Date.now()+(a>0?a:this.defaultSessionTtlMs)}buildUrl(t,e){const a=this.normalizeApiBase(t),o=String(e||"").trim();return a?`${a}${o.startsWith("/")?o:`/${o}`}`:o}normalizeApiBase(t){return String(t||"").trim().replace(/\/+$/,"")}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,deps:[{token:i1.HttpClient},{token:i2.TokenManagerService}],target:i0.ɵɵFactoryTarget.Injectable});static"ɵprov"=i0.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,providedIn:"root"})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,decorators:[{type:Injectable,args:[{providedIn:"root"}]}],ctorParameters:()=>[{type:i1.HttpClient},{type:i2.TokenManagerService}]});class AICoderWorkbenchComponent{api;appId="";appName="AICoder App";apiBase="";appToken="";defaultTab="cases";compact=!1;showHeader=!0;autoRefresh=!0;activeTab="cases";jobs=[];selectedJob=null;jobLogs=[];deployOperations=[];deployJobs=[];gitStatus=null;isLoading=!1;isSubmitting=!1;errorMessage="";successMessage="";requestTitle="";requestMessage="";chatMessage="";workflowMode="qa_playbook";requestKind="bug_or_feature";dataChangePolicy="review_before_write";deploymentTarget="branch_pr";evidenceNotes="";qaFocus="";riskNotes="";refreshSub;tabs=[{id:"cases",label:"Cases"},{id:"chat",label:"Chat + PR"},{id:"qa",label:"QA Playbook"},{id:"deploy",label:"Deploy"},{id:"git",label:"Git"}];constructor(t){this.api=t}ngOnInit(){this.activeTab=this.defaultTab||"cases",this.apiBase||(this.apiBase=this.api.resolveDefaultApiBase()),this.refresh(),this.startAutoRefresh()}ngOnChanges(t){t.defaultTab&&this.defaultTab&&(this.activeTab=this.defaultTab),(t.appId&&!t.appId.firstChange||t.apiBase&&!t.apiBase.firstChange)&&this.refresh()}ngOnDestroy(){this.refreshSub?.unsubscribe()}setTab(t){this.activeTab=t,"git"!==t||this.gitStatus||this.loadGitStatus(),"deploy"!==t||this.deployOperations.length||this.loadDeployHistory()}refresh(){this.normalizedAppId?(this.loadJobs(),this.loadDeployHistory(),this.loadGitStatus()):this.errorMessage="AICoder app id is missing."}selectJob(t){this.selectedJob=t,this.loadJobLogs(t._id)}submitRequest(){if(!this.normalizedAppId||this.isSubmitting)return;const t=String(this.requestMessage||"").trim();t?(this.isSubmitting=!0,this.clearMessages(),this.api.post(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/requests`,{title:String(this.requestTitle||"").trim(),message:t,workflow_mode:this.workflowMode,request_kind:this.requestKind,data_change_policy:this.dataChangePolicy,deployment_target:this.deploymentTarget,evidence_notes:this.evidenceNotes,qa_focus:this.qaFocus,risk_notes:this.riskNotes},this.normalizedAppToken).subscribe({next:t=>{this.successMessage="AICoder case queued.",this.requestTitle="",this.requestMessage="",t?.job&&(this.jobs=[t.job,...this.jobs.filter(e=>e._id!==t.job._id)],this.selectJob(t.job))},error:t=>this.handleError(t,"Could not queue the AICoder case."),complete:()=>this.isSubmitting=!1})):this.errorMessage="Describe the requested change before queueing it."}sendChatMessage(){const t=this.selectedJobId,e=String(this.chatMessage||"").trim();t&&e&&!this.isSubmitting&&(this.isSubmitting=!0,this.clearMessages(),this.api.post(this.apiBase,`/api/ai-coder/jobs/${t}/prompt`,{message:e},this.normalizedAppToken).subscribe({next:t=>{this.successMessage="Message sent to AICoder.",this.chatMessage="",t?.job&&(this.mergeJob(t.job),this.selectJob(t.job))},error:t=>this.handleError(t,"Could not send the message."),complete:()=>this.isSubmitting=!1}))}approveSelectedJob(){this.runJobAction("approve","Case approved.")}createPullRequest(){this.runJobAction("create-pr","Pull request requested.")}publishSelectedJob(){this.runJobAction("publish","Publish requested.")}queueDeploy(t){this.normalizedAppId&&!this.isSubmitting&&(this.isSubmitting=!0,this.clearMessages(),this.api.post(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/deploy`,{deploy_client:"full"===t||"client"===t,deploy_backend:"full"===t||"backend"===t,clear_destination:!0,invalidate:!0},this.normalizedAppToken).subscribe({next:()=>{this.successMessage="full"===t?"Full deploy queued.":("client"===t?"Client":"Server")+" deploy queued.",this.loadDeployHistory()},error:t=>this.handleError(t,"Could not queue deploy."),complete:()=>this.isSubmitting=!1}))}openPr(t=this.selectedJob){const e=this.resolvePrUrl(t);e&&window.open(e,"_blank","noopener")}loadJobs(){this.normalizedAppId&&(this.isLoading=!0,this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/jobs`,this.normalizedAppToken).subscribe({next:t=>{this.jobs=(t?.jobs||[]).map(t=>this.normalizeJob(t)),!this.selectedJob&&this.jobs.length&&this.selectJob(this.jobs[0])},error:t=>this.handleError(t,"Could not load AICoder cases."),complete:()=>this.isLoading=!1}))}loadJobLogs(t){const e=String(t||"").trim();e?this.api.get(this.apiBase,`/api/ai-coder/jobs/${e}/logs?limit=80`,this.normalizedAppToken).subscribe({next:t=>this.jobLogs=t?.logs||[],error:t=>this.handleError(t,"Could not load case logs.")}):this.jobLogs=[]}loadDeployHistory(){this.normalizedAppId&&this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/deploy/history`,this.normalizedAppToken).subscribe({next:t=>{this.deployOperations=t?.operations||[],this.deployJobs=t?.jobs||t?.deploy_jobs||[]},error:t=>this.handleError(t,"Could not load deploy history.")})}loadGitStatus(){this.normalizedAppId&&this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/git`,this.normalizedAppToken).subscribe({next:t=>this.gitStatus=t?.status||t||null,error:t=>this.handleError(t,"Could not load git status.")})}get normalizedAppId(){return String(this.appId||"").trim()}get selectedJobId(){return String(this.selectedJob?._id||"").trim()}get normalizedAppToken(){return String(this.appToken||"").trim()}get activeDeploys(){return this.deployOperations.filter(t=>"In Progress"===String(t?.status||"").trim())}get recentDeploys(){return this.deployOperations.filter(t=>"In Progress"!==String(t?.status||"").trim()).slice(0,6)}isActiveJob(t){const e=String(t?.status||"").toLowerCase();return"queued"===e||"in progress"===e||"running"===e||"pending"===e}resolvePrUrl(t){return String(t?.pr_url||t?.pull_request_url||"").trim()}formatDate(t){if(!t)return"Not recorded";const e=t instanceof Date?t:new Date(t);return Number.isNaN(e.getTime())?String(t):e.toLocaleString()}runJobAction(t,e){const a=this.selectedJobId;a&&!this.isSubmitting&&(this.isSubmitting=!0,this.clearMessages(),this.api.post(this.apiBase,`/api/ai-coder/jobs/${a}/${t}`,{},this.normalizedAppToken).subscribe({next:t=>{this.successMessage=e,t?.job&&(this.mergeJob(t.job),this.selectJob(t.job))},error:e=>this.handleError(e,`Could not ${t.replace("-"," ")}.`),complete:()=>this.isSubmitting=!1}))}mergeJob(t){const e=this.normalizeJob(t);this.jobs=[e,...this.jobs.filter(t=>t._id!==e._id)],this.selectedJob=e}normalizeJob(t){return{...t,_id:String(t?._id||"").trim(),title:String(t?.title||"AICoder Case").trim(),status:String(t?.status||"").trim()||"Queued"}}startAutoRefresh(){this.refreshSub?.unsubscribe(),this.autoRefresh&&(this.refreshSub=interval(15e3).subscribe(()=>{(this.jobs.some(t=>this.isActiveJob(t))||this.activeDeploys.length)&&this.refresh()}))}clearMessages(){this.errorMessage="",this.successMessage=""}handleError(t,e){this.errorMessage=String(t?.error?.message||t?.message||e),this.successMessage="",this.isSubmitting=!1,this.isLoading=!1}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchComponent,deps:[{token:AICoderWorkbenchApiService}],target:i0.ɵɵFactoryTarget.Component});static"ɵcmp"=i0.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"21.1.2",type:AICoderWorkbenchComponent,isStandalone:!1,selector:"resolveio-client-lib-aicoder-workbench",inputs:{appId:"appId",appName:"appName",apiBase:"apiBase",appToken:"appToken",defaultTab:"defaultTab",compact:"compact",showHeader:"showHeader",autoRefresh:"autoRefresh"},usesOnChanges:!0,ngImport:i0,template:'<section class="aicoder-workbench" [class.aicoder-workbench--compact]="compact">\n\t@if (showHeader) {\n\t\t<header class="aicoder-workbench__header">\n\t\t\t<div>\n\t\t\t\t<p>AICoder</p>\n\t\t\t\t<h2>{{ appName || \'AICoder App\' }}</h2>\n\t\t\t</div>\n\t\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh</button>\n\t\t</header>\n\t}\n\n\t<nav class="aicoder-workbench__tabs" aria-label="AICoder workbench tabs">\n\t\t@for (tab of tabs; track tab.id) {\n\t\t\t<button type="button" [class.is-active]="activeTab === tab.id" (click)="setTab(tab.id)">\n\t\t\t\t{{ tab.label }}\n\t\t\t</button>\n\t\t}\n\t</nav>\n\n\t@if (errorMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--error">{{ errorMessage }}</div>\n\t}\n\t@if (successMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--success">{{ successMessage }}</div>\n\t}\n\n\t@if (activeTab === \'cases\') {\n\t\t<div class="aicoder-workbench__layout">\n\t\t\t<section class="aicoder-workbench__panel">\n\t\t\t\t<h3>New Case</h3>\n\t\t\t\t<input class="aicoder-workbench__input" [(ngModel)]="requestTitle" placeholder="Case title" />\n\t\t\t\t<textarea class="aicoder-workbench__textarea" rows="7" [(ngModel)]="requestMessage" placeholder="Describe what should change, what evidence to inspect, and what done looks like."></textarea>\n\t\t\t\t<div class="aicoder-workbench__grid">\n\t\t\t\t\t<label>\n\t\t\t\t\t\t<span>Workflow</span>\n\t\t\t\t\t\t<select [(ngModel)]="workflowMode">\n\t\t\t\t\t\t\t<option value="qa_playbook">QA playbook</option>\n\t\t\t\t\t\t\t<option value="implementation">Implementation</option>\n\t\t\t\t\t\t\t<option value="data_change">Data change</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t</label>\n\t\t\t\t\t<label>\n\t\t\t\t\t\t<span>Deploy target</span>\n\t\t\t\t\t\t<select [(ngModel)]="deploymentTarget">\n\t\t\t\t\t\t\t<option value="branch_pr">Branch + PR</option>\n\t\t\t\t\t\t\t<option value="test_server">Test server</option>\n\t\t\t\t\t\t\t<option value="live_after_approval">Live after approval</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="submitRequest()" [disabled]="isSubmitting || !requestMessage.trim()">Queue Case</button>\n\t\t\t</section>\n\n\t\t\t<section class="aicoder-workbench__panel">\n\t\t\t\t<h3>Cases</h3>\n\t\t\t\t@if (!jobs.length && !isLoading) {\n\t\t\t\t\t<p class="aicoder-workbench__muted">No cases yet.</p>\n\t\t\t\t}\n\t\t\t\t<div class="aicoder-workbench__case-list">\n\t\t\t\t\t@for (job of jobs; track job._id) {\n\t\t\t\t\t\t<button type="button" class="aicoder-workbench__case" [class.is-active]="selectedJob?._id === job._id" (click)="selectJob(job)">\n\t\t\t\t\t\t\t<strong>{{ job.title || \'AICoder Case\' }}</strong>\n\t\t\t\t\t\t\t<span>{{ job.status || \'Queued\' }}{{ job.stage ? \' - \' + job.stage : \'\' }}</span>\n\t\t\t\t\t\t\t<small>{{ formatDate(job.updatedAt || job.createdAt) }}</small>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t</section>\n\t\t</div>\n\t}\n\n\t@if (activeTab === \'chat\') {\n\t\t<div class="aicoder-workbench__layout">\n\t\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--wide">\n\t\t\t\t<h3>{{ selectedJob?.title || \'Select a case\' }}</h3>\n\t\t\t\t<div class="aicoder-workbench__toolbar">\n\t\t\t\t\t<button type="button" (click)="approveSelectedJob()" [disabled]="!selectedJobId || isSubmitting">Approve</button>\n\t\t\t\t\t<button type="button" (click)="createPullRequest()" [disabled]="!selectedJobId || isSubmitting">Create PR</button>\n\t\t\t\t\t<button type="button" (click)="publishSelectedJob()" [disabled]="!selectedJobId || isSubmitting">Publish</button>\n\t\t\t\t\t<button type="button" (click)="openPr()" [disabled]="!resolvePrUrl(selectedJob)">Open PR</button>\n\t\t\t\t</div>\n\t\t\t\t<div class="aicoder-workbench__log">\n\t\t\t\t\t@if (!jobLogs.length) {\n\t\t\t\t\t\t<p class="aicoder-workbench__muted">No log entries loaded.</p>\n\t\t\t\t\t}\n\t\t\t\t\t@for (log of jobLogs; track log._id || log.createdAt || log.message) {\n\t\t\t\t\t\t<div class="aicoder-workbench__log-row">\n\t\t\t\t\t\t\t<span>{{ formatDate(log.createdAt || log.date_created) }}</span>\n\t\t\t\t\t\t\t<p>{{ log.message || log.stage || log.level || \'Log entry\' }}</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t\t<textarea class="aicoder-workbench__textarea" rows="4" [(ngModel)]="chatMessage" placeholder="Send a follow-up to this case."></textarea>\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendChatMessage()" [disabled]="!selectedJobId || !chatMessage.trim() || isSubmitting">Send</button>\n\t\t\t</section>\n\t\t</div>\n\t}\n\n\t@if (activeTab === \'qa\') {\n\t\t<section class="aicoder-workbench__panel">\n\t\t\t<h3>QA Playbook</h3>\n\t\t\t<div class="aicoder-workbench__grid">\n\t\t\t\t<label>\n\t\t\t\t\t<span>Evidence notes</span>\n\t\t\t\t\t<textarea rows="5" [(ngModel)]="evidenceNotes"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<label>\n\t\t\t\t\t<span>QA focus</span>\n\t\t\t\t\t<textarea rows="5" [(ngModel)]="qaFocus"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<label>\n\t\t\t\t\t<span>Risk notes</span>\n\t\t\t\t\t<textarea rows="5" [(ngModel)]="riskNotes"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<label>\n\t\t\t\t\t<span>Data change policy</span>\n\t\t\t\t\t<select [(ngModel)]="dataChangePolicy">\n\t\t\t\t\t\t<option value="review_before_write">Review before write</option>\n\t\t\t\t\t\t<option value="safe_seed_only">Safe seed only</option>\n\t\t\t\t\t\t<option value="no_data_changes">No data changes</option>\n\t\t\t\t\t</select>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t</section>\n\t}\n\n\t@if (activeTab === \'deploy\') {\n\t\t<section class="aicoder-workbench__panel">\n\t\t\t<h3>Deploy</h3>\n\t\t\t<div class="aicoder-workbench__toolbar">\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="queueDeploy(\'full\')" [disabled]="isSubmitting">Full Deploy</button>\n\t\t\t\t<button type="button" (click)="queueDeploy(\'client\')" [disabled]="isSubmitting">Client</button>\n\t\t\t\t<button type="button" (click)="queueDeploy(\'backend\')" [disabled]="isSubmitting">Server</button>\n\t\t\t</div>\n\t\t\t<h4>Current</h4>\n\t\t\t@if (!activeDeploys.length) {\n\t\t\t\t<p class="aicoder-workbench__muted">No active deploys.</p>\n\t\t\t}\n\t\t\t@for (op of activeDeploys; track op.id || op.jobId) {\n\t\t\t\t<div class="aicoder-workbench__deploy-row">\n\t\t\t\t\t<strong>{{ op.type || \'Deploy\' }} - {{ op.status }}</strong>\n\t\t\t\t\t<span>{{ op.aws_build_current_phase || op.backend_deploy_step || op.message }}</span>\n\t\t\t\t</div>\n\t\t\t}\n\t\t\t<h4>Recent</h4>\n\t\t\t@for (op of recentDeploys; track op.id || op.jobId) {\n\t\t\t\t<div class="aicoder-workbench__deploy-row">\n\t\t\t\t\t<strong>{{ op.type || \'Deploy\' }} - {{ op.status }}</strong>\n\t\t\t\t\t<span>{{ op.message || op.backend_deploy_step || \'Deploy finished\' }}</span>\n\t\t\t\t\t<small>{{ formatDate(op.updatedAt || op.createdAt) }}</small>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</section>\n\t}\n\n\t@if (activeTab === \'git\') {\n\t\t<section class="aicoder-workbench__panel">\n\t\t\t<h3>Git</h3>\n\t\t\t<div class="aicoder-workbench__grid">\n\t\t\t\t<div><span>Repo</span><strong>{{ gitStatus?.repo || \'Not loaded\' }}</strong></div>\n\t\t\t\t<div><span>Main branch</span><strong>{{ gitStatus?.default_branch || \'main\' }}</strong></div>\n\t\t\t\t<div><span>Pushes</span><strong>{{ gitStatus?.push_count ?? \'N/A\' }}</strong></div>\n\t\t\t\t<div><span>Last push</span><strong>{{ formatDate(gitStatus?.last_push) }}</strong></div>\n\t\t\t</div>\n\t\t\t<div class="aicoder-workbench__toolbar">\n\t\t\t\t@if (gitStatus?.repo_url) {\n\t\t\t\t\t<a [href]="gitStatus?.repo_url" target="_blank" rel="noopener">Open Repo</a>\n\t\t\t\t}\n\t\t\t\t<button type="button" (click)="loadGitStatus()">Refresh Git</button>\n\t\t\t</div>\n\t\t</section>\n\t}\n</section>\n',styles:[".aicoder-workbench{display:flex;flex-direction:column;gap:1rem;color:#1f2933}.aicoder-workbench__header{display:flex;align-items:center;justify-content:space-between;gap:1rem}.aicoder-workbench__header p{margin:0 0 .25rem;color:#64748b;font-size:.75rem;font-weight:800;letter-spacing:0;text-transform:uppercase}.aicoder-workbench__header h2{margin:0;font-size:1.35rem;font-weight:800}.aicoder-workbench__tabs,.aicoder-workbench__toolbar{display:flex;flex-wrap:wrap;gap:.5rem}.aicoder-workbench__tabs button,.aicoder-workbench__toolbar button,.aicoder-workbench__toolbar a,.aicoder-workbench__ghost,.aicoder-workbench__primary{border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:#1f2933;font-weight:700;padding:.55rem .8rem;text-decoration:none}.aicoder-workbench__tabs button.is-active,.aicoder-workbench__primary{border-color:#2f7d73;background:#2f7d73;color:#fff}.aicoder-workbench__tabs button:disabled,.aicoder-workbench__toolbar button:disabled,.aicoder-workbench__primary:disabled{cursor:not-allowed;opacity:.55}.aicoder-workbench__layout{display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,.7fr);gap:1rem}.aicoder-workbench__panel{border:1px solid #dbe3ec;border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__panel--wide{grid-column:1/-1}.aicoder-workbench__panel h3,.aicoder-workbench__panel h4{margin:0 0 .75rem;font-size:1rem;font-weight:800}.aicoder-workbench__panel h4{margin-top:1rem;color:#64748b;font-size:.8rem;text-transform:uppercase}.aicoder-workbench__input,.aicoder-workbench__textarea,.aicoder-workbench select,.aicoder-workbench textarea{width:100%;border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:#1f2933;padding:.65rem .75rem}.aicoder-workbench__textarea,.aicoder-workbench textarea{resize:vertical}.aicoder-workbench__input,.aicoder-workbench__textarea{margin-bottom:.75rem}.aicoder-workbench__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:.75rem;margin-bottom:.75rem}.aicoder-workbench__grid span,.aicoder-workbench__grid label span{display:block;margin-bottom:.3rem;color:#64748b;font-size:.75rem;font-weight:800;text-transform:uppercase}.aicoder-workbench__case-list,.aicoder-workbench__log{display:flex;flex-direction:column;gap:.5rem;max-height:460px;overflow:auto}.aicoder-workbench__case,.aicoder-workbench__deploy-row,.aicoder-workbench__log-row{display:flex;flex-direction:column;align-items:flex-start;gap:.2rem;width:100%;border:1px solid #e2e8f0;border-radius:7px;background:#f8fafc;padding:.75rem;text-align:left}.aicoder-workbench__case.is-active{border-color:#2f7d73;background:#ecfdf5}.aicoder-workbench__case span,.aicoder-workbench__case small,.aicoder-workbench__deploy-row span,.aicoder-workbench__deploy-row small,.aicoder-workbench__log-row span{color:#64748b;font-size:.82rem}.aicoder-workbench__log{min-height:220px;margin-bottom:.75rem}.aicoder-workbench__log-row p{margin:0}.aicoder-workbench__notice{border-radius:6px;padding:.75rem .9rem;font-weight:700}.aicoder-workbench__notice--error{border:1px solid #fecaca;background:#fff1f2;color:#991b1b}.aicoder-workbench__notice--success{border:1px solid #bbf7d0;background:#f0fdf4;color:#166534}.aicoder-workbench__muted{margin:0;color:#64748b}.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:1fr}@media(max-width:900px){.aicoder-workbench__layout{grid-template-columns:1fr}}\n"],dependencies:[{kind:"directive",type:i2$1.NgSelectOption,selector:"option",inputs:["ngValue","value"]},{kind:"directive",type:i2$1.ɵNgSelectMultipleOption,selector:"option",inputs:["ngValue","value"]},{kind:"directive",type:i2$1.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:i2$1.SelectControlValueAccessor,selector:"select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]",inputs:["compareWith"]},{kind:"directive",type:i2$1.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:i2$1.NgModel,selector:"[ngModel]:not([formControlName]):not([formControl])",inputs:["name","disabled","ngModel","ngModelOptions"],outputs:["ngModelChange"],exportAs:["ngModel"]}]})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchComponent,decorators:[{type:Component,args:[{selector:"resolveio-client-lib-aicoder-workbench",standalone:!1,template:'<section class="aicoder-workbench" [class.aicoder-workbench--compact]="compact">\n\t@if (showHeader) {\n\t\t<header class="aicoder-workbench__header">\n\t\t\t<div>\n\t\t\t\t<p>AICoder</p>\n\t\t\t\t<h2>{{ appName || \'AICoder App\' }}</h2>\n\t\t\t</div>\n\t\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh</button>\n\t\t</header>\n\t}\n\n\t<nav class="aicoder-workbench__tabs" aria-label="AICoder workbench tabs">\n\t\t@for (tab of tabs; track tab.id) {\n\t\t\t<button type="button" [class.is-active]="activeTab === tab.id" (click)="setTab(tab.id)">\n\t\t\t\t{{ tab.label }}\n\t\t\t</button>\n\t\t}\n\t</nav>\n\n\t@if (errorMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--error">{{ errorMessage }}</div>\n\t}\n\t@if (successMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--success">{{ successMessage }}</div>\n\t}\n\n\t@if (activeTab === \'cases\') {\n\t\t<div class="aicoder-workbench__layout">\n\t\t\t<section class="aicoder-workbench__panel">\n\t\t\t\t<h3>New Case</h3>\n\t\t\t\t<input class="aicoder-workbench__input" [(ngModel)]="requestTitle" placeholder="Case title" />\n\t\t\t\t<textarea class="aicoder-workbench__textarea" rows="7" [(ngModel)]="requestMessage" placeholder="Describe what should change, what evidence to inspect, and what done looks like."></textarea>\n\t\t\t\t<div class="aicoder-workbench__grid">\n\t\t\t\t\t<label>\n\t\t\t\t\t\t<span>Workflow</span>\n\t\t\t\t\t\t<select [(ngModel)]="workflowMode">\n\t\t\t\t\t\t\t<option value="qa_playbook">QA playbook</option>\n\t\t\t\t\t\t\t<option value="implementation">Implementation</option>\n\t\t\t\t\t\t\t<option value="data_change">Data change</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t</label>\n\t\t\t\t\t<label>\n\t\t\t\t\t\t<span>Deploy target</span>\n\t\t\t\t\t\t<select [(ngModel)]="deploymentTarget">\n\t\t\t\t\t\t\t<option value="branch_pr">Branch + PR</option>\n\t\t\t\t\t\t\t<option value="test_server">Test server</option>\n\t\t\t\t\t\t\t<option value="live_after_approval">Live after approval</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="submitRequest()" [disabled]="isSubmitting || !requestMessage.trim()">Queue Case</button>\n\t\t\t</section>\n\n\t\t\t<section class="aicoder-workbench__panel">\n\t\t\t\t<h3>Cases</h3>\n\t\t\t\t@if (!jobs.length && !isLoading) {\n\t\t\t\t\t<p class="aicoder-workbench__muted">No cases yet.</p>\n\t\t\t\t}\n\t\t\t\t<div class="aicoder-workbench__case-list">\n\t\t\t\t\t@for (job of jobs; track job._id) {\n\t\t\t\t\t\t<button type="button" class="aicoder-workbench__case" [class.is-active]="selectedJob?._id === job._id" (click)="selectJob(job)">\n\t\t\t\t\t\t\t<strong>{{ job.title || \'AICoder Case\' }}</strong>\n\t\t\t\t\t\t\t<span>{{ job.status || \'Queued\' }}{{ job.stage ? \' - \' + job.stage : \'\' }}</span>\n\t\t\t\t\t\t\t<small>{{ formatDate(job.updatedAt || job.createdAt) }}</small>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t</section>\n\t\t</div>\n\t}\n\n\t@if (activeTab === \'chat\') {\n\t\t<div class="aicoder-workbench__layout">\n\t\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--wide">\n\t\t\t\t<h3>{{ selectedJob?.title || \'Select a case\' }}</h3>\n\t\t\t\t<div class="aicoder-workbench__toolbar">\n\t\t\t\t\t<button type="button" (click)="approveSelectedJob()" [disabled]="!selectedJobId || isSubmitting">Approve</button>\n\t\t\t\t\t<button type="button" (click)="createPullRequest()" [disabled]="!selectedJobId || isSubmitting">Create PR</button>\n\t\t\t\t\t<button type="button" (click)="publishSelectedJob()" [disabled]="!selectedJobId || isSubmitting">Publish</button>\n\t\t\t\t\t<button type="button" (click)="openPr()" [disabled]="!resolvePrUrl(selectedJob)">Open PR</button>\n\t\t\t\t</div>\n\t\t\t\t<div class="aicoder-workbench__log">\n\t\t\t\t\t@if (!jobLogs.length) {\n\t\t\t\t\t\t<p class="aicoder-workbench__muted">No log entries loaded.</p>\n\t\t\t\t\t}\n\t\t\t\t\t@for (log of jobLogs; track log._id || log.createdAt || log.message) {\n\t\t\t\t\t\t<div class="aicoder-workbench__log-row">\n\t\t\t\t\t\t\t<span>{{ formatDate(log.createdAt || log.date_created) }}</span>\n\t\t\t\t\t\t\t<p>{{ log.message || log.stage || log.level || \'Log entry\' }}</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t\t<textarea class="aicoder-workbench__textarea" rows="4" [(ngModel)]="chatMessage" placeholder="Send a follow-up to this case."></textarea>\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendChatMessage()" [disabled]="!selectedJobId || !chatMessage.trim() || isSubmitting">Send</button>\n\t\t\t</section>\n\t\t</div>\n\t}\n\n\t@if (activeTab === \'qa\') {\n\t\t<section class="aicoder-workbench__panel">\n\t\t\t<h3>QA Playbook</h3>\n\t\t\t<div class="aicoder-workbench__grid">\n\t\t\t\t<label>\n\t\t\t\t\t<span>Evidence notes</span>\n\t\t\t\t\t<textarea rows="5" [(ngModel)]="evidenceNotes"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<label>\n\t\t\t\t\t<span>QA focus</span>\n\t\t\t\t\t<textarea rows="5" [(ngModel)]="qaFocus"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<label>\n\t\t\t\t\t<span>Risk notes</span>\n\t\t\t\t\t<textarea rows="5" [(ngModel)]="riskNotes"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<label>\n\t\t\t\t\t<span>Data change policy</span>\n\t\t\t\t\t<select [(ngModel)]="dataChangePolicy">\n\t\t\t\t\t\t<option value="review_before_write">Review before write</option>\n\t\t\t\t\t\t<option value="safe_seed_only">Safe seed only</option>\n\t\t\t\t\t\t<option value="no_data_changes">No data changes</option>\n\t\t\t\t\t</select>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t</section>\n\t}\n\n\t@if (activeTab === \'deploy\') {\n\t\t<section class="aicoder-workbench__panel">\n\t\t\t<h3>Deploy</h3>\n\t\t\t<div class="aicoder-workbench__toolbar">\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="queueDeploy(\'full\')" [disabled]="isSubmitting">Full Deploy</button>\n\t\t\t\t<button type="button" (click)="queueDeploy(\'client\')" [disabled]="isSubmitting">Client</button>\n\t\t\t\t<button type="button" (click)="queueDeploy(\'backend\')" [disabled]="isSubmitting">Server</button>\n\t\t\t</div>\n\t\t\t<h4>Current</h4>\n\t\t\t@if (!activeDeploys.length) {\n\t\t\t\t<p class="aicoder-workbench__muted">No active deploys.</p>\n\t\t\t}\n\t\t\t@for (op of activeDeploys; track op.id || op.jobId) {\n\t\t\t\t<div class="aicoder-workbench__deploy-row">\n\t\t\t\t\t<strong>{{ op.type || \'Deploy\' }} - {{ op.status }}</strong>\n\t\t\t\t\t<span>{{ op.aws_build_current_phase || op.backend_deploy_step || op.message }}</span>\n\t\t\t\t</div>\n\t\t\t}\n\t\t\t<h4>Recent</h4>\n\t\t\t@for (op of recentDeploys; track op.id || op.jobId) {\n\t\t\t\t<div class="aicoder-workbench__deploy-row">\n\t\t\t\t\t<strong>{{ op.type || \'Deploy\' }} - {{ op.status }}</strong>\n\t\t\t\t\t<span>{{ op.message || op.backend_deploy_step || \'Deploy finished\' }}</span>\n\t\t\t\t\t<small>{{ formatDate(op.updatedAt || op.createdAt) }}</small>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</section>\n\t}\n\n\t@if (activeTab === \'git\') {\n\t\t<section class="aicoder-workbench__panel">\n\t\t\t<h3>Git</h3>\n\t\t\t<div class="aicoder-workbench__grid">\n\t\t\t\t<div><span>Repo</span><strong>{{ gitStatus?.repo || \'Not loaded\' }}</strong></div>\n\t\t\t\t<div><span>Main branch</span><strong>{{ gitStatus?.default_branch || \'main\' }}</strong></div>\n\t\t\t\t<div><span>Pushes</span><strong>{{ gitStatus?.push_count ?? \'N/A\' }}</strong></div>\n\t\t\t\t<div><span>Last push</span><strong>{{ formatDate(gitStatus?.last_push) }}</strong></div>\n\t\t\t</div>\n\t\t\t<div class="aicoder-workbench__toolbar">\n\t\t\t\t@if (gitStatus?.repo_url) {\n\t\t\t\t\t<a [href]="gitStatus?.repo_url" target="_blank" rel="noopener">Open Repo</a>\n\t\t\t\t}\n\t\t\t\t<button type="button" (click)="loadGitStatus()">Refresh Git</button>\n\t\t\t</div>\n\t\t</section>\n\t}\n</section>\n',styles:[".aicoder-workbench{display:flex;flex-direction:column;gap:1rem;color:#1f2933}.aicoder-workbench__header{display:flex;align-items:center;justify-content:space-between;gap:1rem}.aicoder-workbench__header p{margin:0 0 .25rem;color:#64748b;font-size:.75rem;font-weight:800;letter-spacing:0;text-transform:uppercase}.aicoder-workbench__header h2{margin:0;font-size:1.35rem;font-weight:800}.aicoder-workbench__tabs,.aicoder-workbench__toolbar{display:flex;flex-wrap:wrap;gap:.5rem}.aicoder-workbench__tabs button,.aicoder-workbench__toolbar button,.aicoder-workbench__toolbar a,.aicoder-workbench__ghost,.aicoder-workbench__primary{border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:#1f2933;font-weight:700;padding:.55rem .8rem;text-decoration:none}.aicoder-workbench__tabs button.is-active,.aicoder-workbench__primary{border-color:#2f7d73;background:#2f7d73;color:#fff}.aicoder-workbench__tabs button:disabled,.aicoder-workbench__toolbar button:disabled,.aicoder-workbench__primary:disabled{cursor:not-allowed;opacity:.55}.aicoder-workbench__layout{display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,.7fr);gap:1rem}.aicoder-workbench__panel{border:1px solid #dbe3ec;border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__panel--wide{grid-column:1/-1}.aicoder-workbench__panel h3,.aicoder-workbench__panel h4{margin:0 0 .75rem;font-size:1rem;font-weight:800}.aicoder-workbench__panel h4{margin-top:1rem;color:#64748b;font-size:.8rem;text-transform:uppercase}.aicoder-workbench__input,.aicoder-workbench__textarea,.aicoder-workbench select,.aicoder-workbench textarea{width:100%;border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:#1f2933;padding:.65rem .75rem}.aicoder-workbench__textarea,.aicoder-workbench textarea{resize:vertical}.aicoder-workbench__input,.aicoder-workbench__textarea{margin-bottom:.75rem}.aicoder-workbench__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:.75rem;margin-bottom:.75rem}.aicoder-workbench__grid span,.aicoder-workbench__grid label span{display:block;margin-bottom:.3rem;color:#64748b;font-size:.75rem;font-weight:800;text-transform:uppercase}.aicoder-workbench__case-list,.aicoder-workbench__log{display:flex;flex-direction:column;gap:.5rem;max-height:460px;overflow:auto}.aicoder-workbench__case,.aicoder-workbench__deploy-row,.aicoder-workbench__log-row{display:flex;flex-direction:column;align-items:flex-start;gap:.2rem;width:100%;border:1px solid #e2e8f0;border-radius:7px;background:#f8fafc;padding:.75rem;text-align:left}.aicoder-workbench__case.is-active{border-color:#2f7d73;background:#ecfdf5}.aicoder-workbench__case span,.aicoder-workbench__case small,.aicoder-workbench__deploy-row span,.aicoder-workbench__deploy-row small,.aicoder-workbench__log-row span{color:#64748b;font-size:.82rem}.aicoder-workbench__log{min-height:220px;margin-bottom:.75rem}.aicoder-workbench__log-row p{margin:0}.aicoder-workbench__notice{border-radius:6px;padding:.75rem .9rem;font-weight:700}.aicoder-workbench__notice--error{border:1px solid #fecaca;background:#fff1f2;color:#991b1b}.aicoder-workbench__notice--success{border:1px solid #bbf7d0;background:#f0fdf4;color:#166534}.aicoder-workbench__muted{margin:0;color:#64748b}.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:1fr}@media(max-width:900px){.aicoder-workbench__layout{grid-template-columns:1fr}}\n"]}]}],ctorParameters:()=>[{type:AICoderWorkbenchApiService}],propDecorators:{appId:[{type:Input}],appName:[{type:Input}],apiBase:[{type:Input}],appToken:[{type:Input}],defaultTab:[{type:Input}],compact:[{type:Input}],showHeader:[{type:Input}],autoRefresh:[{type:Input}]}});class AICoderDashboardModule{static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,deps:[],target:i0.ɵɵFactoryTarget.NgModule});static"ɵmod"=i0.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,declarations:[AICoderDashboardComponent,AICoderWorkbenchComponent],imports:[CommonModule,FormsModule,HttpClientModule],exports:[AICoderDashboardComponent,AICoderWorkbenchComponent]});static"ɵinj"=i0.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,imports:[CommonModule,FormsModule,HttpClientModule]})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,decorators:[{type:NgModule,args:[{imports:[CommonModule,FormsModule,HttpClientModule],declarations:[AICoderDashboardComponent,AICoderWorkbenchComponent],exports:[AICoderDashboardComponent,AICoderWorkbenchComponent]}]}]});export{AICoderDashboardComponent,AICoderDashboardModule,AICoderDashboardService,AICoderWorkbenchApiService,AICoderWorkbenchComponent};
1
+ import*as i0 from"@angular/core";import{Injectable,EventEmitter,Output,Input,Component,NgModule}from"@angular/core";import*as i1 from"@angular/common/http";import{HttpHeaders,HttpClientModule}from"@angular/common/http";import*as i2 from"@resolveio/client-lib-core";import{Observable,throwError,interval}from"rxjs";import{switchMap,map,tap,shareReplay}from"rxjs/operators";import*as i2$1 from"@angular/forms";import{FormsModule}from"@angular/forms";import{CommonModule}from"@angular/common";class AICoderDashboardService{buildDefaultSummary(){return{overview:{app_name:"ResolveIO App",app_status:"Active",plan_tier:"small",max_users:10},database:{name:"resolveio",host:"127.0.0.1",status:"Ready"},infrastructure:{current_tier:"small",available_tiers:["small","medium","large"],max_users:10,backend_status:"Ready"},code_changes:{release_notes:"Latest updates are live and available.",last_deployed_at:"",recent_updates:["Overview tab highlights app status and plan tier.","Infrastructure tab shows Small, Medium, and Large options.","Code Changes tab summarizes customer-facing updates."]},subscription:{current_package:"Small",current_tier:"small",package_price_label:"$99 / month",billing_status:"Paid",billing_mode:"Subscription",subscription_status:"active",current_period_end:"",manage_url:"",can_downgrade:!0,downgrade_reason:""}}}buildTierOptions(){return[{id:"small",label:"Small",description:"Great for focused teams and lighter usage."},{id:"medium",label:"Medium",description:"Balanced capacity for growing teams and workflows."},{id:"large",label:"Large",description:"Higher capacity for heavy usage and larger teams."}]}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardService,deps:[],target:i0.ɵɵFactoryTarget.Injectable});static"ɵprov"=i0.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardService,providedIn:"root"})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardService,decorators:[{type:Injectable,args:[{providedIn:"root"}]}]});class AICoderDashboardComponent{dashboardService;summary;loading=!1;errorMessage="";activeTab="overview";tierOptions;tabChange=new EventEmitter;retry=new EventEmitter;tierChange=new EventEmitter;manageSubscription=new EventEmitter;constructor(t){this.dashboardService=t,this.summary=this.dashboardService.buildDefaultSummary(),this.tierOptions=this.dashboardService.buildTierOptions()}setTab(t){this.activeTab!==t&&this.tabChange.emit(t)}requestRetry(){this.retry.emit()}requestTierChange(t){this.tierChange.emit(t)}requestManageSubscription(){this.manageSubscription.emit(this.resolveManageSubscriptionUrl())}isCurrentTier(t){return String(this.summary?.infrastructure?.current_tier||"").trim().toLowerCase()===t}hasManageSubscriptionUrl(){return!!this.resolveManageSubscriptionUrl()}resolveManageSubscriptionUrl(){return String(this.summary?.subscription?.manage_url||"").trim()}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardComponent,deps:[{token:AICoderDashboardService}],target:i0.ɵɵFactoryTarget.Component});static"ɵcmp"=i0.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"21.1.2",type:AICoderDashboardComponent,isStandalone:!1,selector:"resolveio-client-lib-aicoder-dashboard",inputs:{summary:"summary",loading:"loading",errorMessage:"errorMessage",activeTab:"activeTab",tierOptions:"tierOptions"},outputs:{tabChange:"tabChange",retry:"retry",tierChange:"tierChange",manageSubscription:"manageSubscription"},ngImport:i0,template:'<div class="aicoder-dashboard">\n\t<div class="aicoder-dashboard__tabs">\n\t\t<button type="button" [class.is-active]="activeTab === \'overview\'" (click)="setTab(\'overview\')">Overview</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'database\'" (click)="setTab(\'database\')">Database</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'infrastructure\'" (click)="setTab(\'infrastructure\')">Infrastructure</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'code_changes\'" (click)="setTab(\'code_changes\')">Code Changes</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'subscription\'" (click)="setTab(\'subscription\')">Subscription</button>\n\t</div>\n\n\t@if (loading) {\n\t\t<div class="aicoder-dashboard__state">Loading dashboard details...</div>\n\t} @else if (errorMessage) {\n\t\t<div class="aicoder-dashboard__state aicoder-dashboard__state--error">\n\t\t\t<span>{{ errorMessage }}</span>\n\t\t\t<button type="button" (click)="requestRetry()">Retry</button>\n\t\t</div>\n\t} @else {\n\t\t@if (activeTab === \'overview\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>App</span><strong>{{ summary.overview.app_name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.overview.app_status }}</strong></div>\n\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.overview.plan_tier }}</strong></div>\n\t\t\t\t<div class="tile"><span>Max users</span><strong>{{ summary.overview.max_users }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'database\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>Name</span><strong>{{ summary.database.name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Host</span><strong>{{ summary.database.host }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.database.status }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'infrastructure\') {\n\t\t\t<div class="aicoder-dashboard__tiers">\n\t\t\t\t@for (tier of tierOptions; track tier.id) {\n\t\t\t\t\t<div class="tier" [class.tier--active]="isCurrentTier(tier.id)">\n\t\t\t\t\t\t<h4>{{ tier.label }}</h4>\n\t\t\t\t\t\t<p>{{ tier.description }}</p>\n\t\t\t\t\t\t<button type="button" (click)="requestTierChange(tier.id)">Choose {{ tier.label }}</button>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'code_changes\') {\n\t\t\t<div class="aicoder-dashboard__changes">\n\t\t\t\t<p>{{ summary.code_changes.release_notes }}</p>\n\t\t\t\t<ul>\n\t\t\t\t\t@for (item of summary.code_changes.recent_updates; track item) {\n\t\t\t\t\t\t<li>{{ item }}</li>\n\t\t\t\t\t}\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'subscription\') {\n\t\t\t<div class="aicoder-dashboard__subscription">\n\t\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t\t<div class="tile"><span>Package</span><strong>{{ summary.subscription?.current_package || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.subscription?.current_tier || summary.overview.plan_tier || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Price</span><strong>{{ summary.subscription?.package_price_label || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing status</span><strong>{{ summary.subscription?.billing_status || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing mode</span><strong>{{ summary.subscription?.billing_mode || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Subscription</span><strong>{{ summary.subscription?.subscription_status || \'N/A\' }}</strong></div>\n\t\t\t\t</div>\n\t\t\t\t@if (summary.subscription?.current_period_end) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint">Current period ends: {{ summary.subscription?.current_period_end }}</p>\n\t\t\t\t}\n\t\t\t\t@if (summary.subscription?.downgrade_reason) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint aicoder-dashboard__hint--warning">{{ summary.subscription?.downgrade_reason }}</p>\n\t\t\t\t}\n\t\t\t\t<div class="aicoder-dashboard__subscription-actions">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype="button"\n\t\t\t\t\t\tclass="aicoder-dashboard__primary-action"\n\t\t\t\t\t\t(click)="requestManageSubscription()"\n\t\t\t\t\t\t[disabled]="!hasManageSubscriptionUrl()"\n\t\t\t\t\t>\n\t\t\t\t\t\tUpgrade Or Manage Plan\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t}\n\t}\n</div>\n',styles:[".aicoder-dashboard{--aicoder-primary: #0f4c81;font-family:inherit}.aicoder-dashboard__tabs{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:.75rem}.aicoder-dashboard__tabs button{border:1px solid rgba(15,23,42,.2);background:#fff;padding:.45rem .75rem;border-radius:8px;font-weight:600}.aicoder-dashboard__tabs button.is-active{border-color:var(--aicoder-primary);background:#0f4c811a}.aicoder-dashboard__state{padding:.85rem;border-radius:10px;background:#f8fafc}.aicoder-dashboard__state--error{display:flex;justify-content:space-between;align-items:center;gap:.5rem;background:#fff7ed}.aicoder-dashboard__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem}.tile{padding:.8rem;border:1px solid rgba(15,23,42,.12);border-radius:10px;background:#fff}.tile span{display:block;font-size:.74rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#0f172a9e}.tile strong{display:block;margin-top:.3rem;font-size:1rem}.aicoder-dashboard__tiers{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:.75rem}.tier{padding:.85rem;border-radius:10px;border:1px solid rgba(15,23,42,.12);background:#f8fafc}.tier--active{border-color:var(--aicoder-primary);background:#0f4c8114}.tier h4{margin:0}.tier p{margin:.45rem 0}.aicoder-dashboard__changes ul{margin:0;padding-left:1.1rem}.aicoder-dashboard__subscription{display:grid;gap:.75rem}.aicoder-dashboard__subscription-actions{display:flex;flex-wrap:wrap;gap:.5rem}.aicoder-dashboard__primary-action{border:1px solid var(--aicoder-primary);background:var(--aicoder-primary);color:#fff;padding:.5rem .85rem;border-radius:8px;font-weight:700}.aicoder-dashboard__primary-action[disabled]{opacity:.55;cursor:not-allowed}.aicoder-dashboard__hint{margin:0;color:#334155;font-size:.88rem}.aicoder-dashboard__hint--warning{color:#9a3412}\n"]})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardComponent,decorators:[{type:Component,args:[{selector:"resolveio-client-lib-aicoder-dashboard",standalone:!1,template:'<div class="aicoder-dashboard">\n\t<div class="aicoder-dashboard__tabs">\n\t\t<button type="button" [class.is-active]="activeTab === \'overview\'" (click)="setTab(\'overview\')">Overview</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'database\'" (click)="setTab(\'database\')">Database</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'infrastructure\'" (click)="setTab(\'infrastructure\')">Infrastructure</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'code_changes\'" (click)="setTab(\'code_changes\')">Code Changes</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'subscription\'" (click)="setTab(\'subscription\')">Subscription</button>\n\t</div>\n\n\t@if (loading) {\n\t\t<div class="aicoder-dashboard__state">Loading dashboard details...</div>\n\t} @else if (errorMessage) {\n\t\t<div class="aicoder-dashboard__state aicoder-dashboard__state--error">\n\t\t\t<span>{{ errorMessage }}</span>\n\t\t\t<button type="button" (click)="requestRetry()">Retry</button>\n\t\t</div>\n\t} @else {\n\t\t@if (activeTab === \'overview\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>App</span><strong>{{ summary.overview.app_name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.overview.app_status }}</strong></div>\n\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.overview.plan_tier }}</strong></div>\n\t\t\t\t<div class="tile"><span>Max users</span><strong>{{ summary.overview.max_users }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'database\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>Name</span><strong>{{ summary.database.name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Host</span><strong>{{ summary.database.host }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.database.status }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'infrastructure\') {\n\t\t\t<div class="aicoder-dashboard__tiers">\n\t\t\t\t@for (tier of tierOptions; track tier.id) {\n\t\t\t\t\t<div class="tier" [class.tier--active]="isCurrentTier(tier.id)">\n\t\t\t\t\t\t<h4>{{ tier.label }}</h4>\n\t\t\t\t\t\t<p>{{ tier.description }}</p>\n\t\t\t\t\t\t<button type="button" (click)="requestTierChange(tier.id)">Choose {{ tier.label }}</button>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'code_changes\') {\n\t\t\t<div class="aicoder-dashboard__changes">\n\t\t\t\t<p>{{ summary.code_changes.release_notes }}</p>\n\t\t\t\t<ul>\n\t\t\t\t\t@for (item of summary.code_changes.recent_updates; track item) {\n\t\t\t\t\t\t<li>{{ item }}</li>\n\t\t\t\t\t}\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'subscription\') {\n\t\t\t<div class="aicoder-dashboard__subscription">\n\t\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t\t<div class="tile"><span>Package</span><strong>{{ summary.subscription?.current_package || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.subscription?.current_tier || summary.overview.plan_tier || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Price</span><strong>{{ summary.subscription?.package_price_label || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing status</span><strong>{{ summary.subscription?.billing_status || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing mode</span><strong>{{ summary.subscription?.billing_mode || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Subscription</span><strong>{{ summary.subscription?.subscription_status || \'N/A\' }}</strong></div>\n\t\t\t\t</div>\n\t\t\t\t@if (summary.subscription?.current_period_end) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint">Current period ends: {{ summary.subscription?.current_period_end }}</p>\n\t\t\t\t}\n\t\t\t\t@if (summary.subscription?.downgrade_reason) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint aicoder-dashboard__hint--warning">{{ summary.subscription?.downgrade_reason }}</p>\n\t\t\t\t}\n\t\t\t\t<div class="aicoder-dashboard__subscription-actions">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype="button"\n\t\t\t\t\t\tclass="aicoder-dashboard__primary-action"\n\t\t\t\t\t\t(click)="requestManageSubscription()"\n\t\t\t\t\t\t[disabled]="!hasManageSubscriptionUrl()"\n\t\t\t\t\t>\n\t\t\t\t\t\tUpgrade Or Manage Plan\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t}\n\t}\n</div>\n',styles:[".aicoder-dashboard{--aicoder-primary: #0f4c81;font-family:inherit}.aicoder-dashboard__tabs{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:.75rem}.aicoder-dashboard__tabs button{border:1px solid rgba(15,23,42,.2);background:#fff;padding:.45rem .75rem;border-radius:8px;font-weight:600}.aicoder-dashboard__tabs button.is-active{border-color:var(--aicoder-primary);background:#0f4c811a}.aicoder-dashboard__state{padding:.85rem;border-radius:10px;background:#f8fafc}.aicoder-dashboard__state--error{display:flex;justify-content:space-between;align-items:center;gap:.5rem;background:#fff7ed}.aicoder-dashboard__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem}.tile{padding:.8rem;border:1px solid rgba(15,23,42,.12);border-radius:10px;background:#fff}.tile span{display:block;font-size:.74rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#0f172a9e}.tile strong{display:block;margin-top:.3rem;font-size:1rem}.aicoder-dashboard__tiers{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:.75rem}.tier{padding:.85rem;border-radius:10px;border:1px solid rgba(15,23,42,.12);background:#f8fafc}.tier--active{border-color:var(--aicoder-primary);background:#0f4c8114}.tier h4{margin:0}.tier p{margin:.45rem 0}.aicoder-dashboard__changes ul{margin:0;padding-left:1.1rem}.aicoder-dashboard__subscription{display:grid;gap:.75rem}.aicoder-dashboard__subscription-actions{display:flex;flex-wrap:wrap;gap:.5rem}.aicoder-dashboard__primary-action{border:1px solid var(--aicoder-primary);background:var(--aicoder-primary);color:#fff;padding:.5rem .85rem;border-radius:8px;font-weight:700}.aicoder-dashboard__primary-action[disabled]{opacity:.55;cursor:not-allowed}.aicoder-dashboard__hint{margin:0;color:#334155;font-size:.88rem}.aicoder-dashboard__hint--warning{color:#9a3412}\n"]}]}],ctorParameters:()=>[{type:AICoderDashboardService}],propDecorators:{summary:[{type:Input}],loading:[{type:Input}],errorMessage:[{type:Input}],activeTab:[{type:Input}],tierOptions:[{type:Input}],tabChange:[{type:Output}],retry:[{type:Output}],tierChange:[{type:Output}],manageSubscription:[{type:Output}]}});class AICoderWorkbenchApiService{http;tokenManager;sessionHeaderName="X-AI-Coder-Session";appTokenHeaderName="X-AI-Coder-App-Token";sessionRefreshSkewMs=6e4;defaultSessionTtlMs=144e5;sessionByApiBase=new Map;exchangeByApiBase=new Map;constructor(t,e){this.http=t,this.tokenManager=e}get(t,e,r=""){return this.authHeaders(t,r).pipe(switchMap(r=>this.http.get(this.buildUrl(t,e),{headers:r})))}post(t,e,r={},a=""){return this.authHeaders(t,a).pipe(switchMap(a=>this.http.post(this.buildUrl(t,e),r,{headers:a})))}patch(t,e,r={},a=""){return this.authHeaders(t,a).pipe(switchMap(a=>this.http.patch(this.buildUrl(t,e),r,{headers:a})))}delete(t,e,r=""){return this.authHeaders(t,r).pipe(switchMap(r=>this.http.delete(this.buildUrl(t,e),{headers:r})))}resolveDefaultApiBase(){const t=String(window?.location?.hostname||"").trim().toLowerCase();return"aicoder.resolveio.com"===t||t.endsWith(".aicoder.resolveio.com")?"https://backend.aicoder.resolveio.com":""}authHeaders(t,e=""){const r=String(e||"").trim();return r?new Observable(t=>{t.next(new HttpHeaders({[this.appTokenHeaderName]:r})),t.complete()}):this.ensureSessionToken(t).pipe(map(t=>new HttpHeaders({[this.sessionHeaderName]:t})))}ensureSessionToken(t){const e=this.normalizeApiBase(t),r=this.sessionByApiBase.get(e);if(r&&r.token&&r.expiresAtMs>Date.now()+this.sessionRefreshSkewMs)return new Observable(t=>{t.next(r.token),t.complete()});const a=this.exchangeByApiBase.get(e);if(a)return a;const i=String(this.tokenManager.getToken("accessToken")||"").trim();if(!i)return throwError(()=>new Error("Sign in before using AICoder."));const s=new HttpHeaders({Authorization:`Bearer ${i}`}),o=this.http.post(this.buildUrl(e,"/api/ai-coder/auth/session"),{},{headers:s}).pipe(map(t=>{const r=String(t?.token||"").trim();if(!r)throw new Error("AICoder session response did not include a token.");return this.sessionByApiBase.set(e,{token:r,expiresAtMs:this.resolveSessionExpiresAtMs(t)}),r}),tap({error:()=>this.sessionByApiBase.delete(e),complete:()=>this.exchangeByApiBase.delete(e)}),shareReplay(1));return this.exchangeByApiBase.set(e,o),o}resolveSessionExpiresAtMs(t){const e=t?.expires_at?new Date(t.expires_at).getTime():0;if(Number.isFinite(e)&&e>Date.now())return e;const r=1e3*Number(t?.expires_in_seconds||0);return Date.now()+(r>0?r:this.defaultSessionTtlMs)}buildUrl(t,e){const r=this.normalizeApiBase(t),a=String(e||"").trim();return r?`${r}${a.startsWith("/")?a:`/${a}`}`:a}normalizeApiBase(t){return String(t||"").trim().replace(/\/+$/,"")}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,deps:[{token:i1.HttpClient},{token:i2.TokenManagerService}],target:i0.ɵɵFactoryTarget.Injectable});static"ɵprov"=i0.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,providedIn:"root"})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,decorators:[{type:Injectable,args:[{providedIn:"root"}]}],ctorParameters:()=>[{type:i1.HttpClient},{type:i2.TokenManagerService}]});class AICoderWorkbenchComponent{api;appId="";appName="AICoder App";apiBase="";appToken="";defaultTab="chats";compact=!1;showHeader=!0;autoRefresh=!0;activeTab="chats";jobs=[];selectedJob=null;jobLogs=[];deployOperations=[];deployJobs=[];gitStatus=null;isLoading=!1;isSubmitting=!1;errorMessage="";successMessage="";requestTitle="";requestMessage="";chatMessage="";workflowMode="implementation";requestKind="bug_or_feature";dataChangePolicy="review_before_write";deploymentTarget="branch_pr";evidenceNotes="";qaFocus="";riskNotes="";refreshSub;constructor(t){this.api=t}ngOnInit(){this.activeTab=this.normalizeTab(this.defaultTab),this.apiBase||(this.apiBase=this.api.resolveDefaultApiBase()),this.refresh(),this.startAutoRefresh()}ngOnChanges(t){t.defaultTab&&this.defaultTab&&(this.activeTab=this.normalizeTab(this.defaultTab)),(t.appId&&!t.appId.firstChange||t.apiBase&&!t.apiBase.firstChange)&&this.refresh()}ngOnDestroy(){this.refreshSub?.unsubscribe()}setTab(t){this.activeTab=this.normalizeTab(t),"git"!==t||this.gitStatus||this.loadGitStatus(),"deploy"!==t||this.deployOperations.length||this.loadDeployHistory()}refresh(){this.normalizedAppId?(this.loadJobs(),this.loadDeployHistory(),this.loadGitStatus()):this.errorMessage="AICoder app id is missing."}selectJob(t){this.selectedJob=t,this.loadJobLogs(t._id)}submitRequest(){if(!this.normalizedAppId||this.isSubmitting)return;const t=String(this.requestMessage||"").trim();if(!t)return void(this.errorMessage="Describe what you want changed before starting the chat.");this.isSubmitting=!0,this.clearMessages();const e=this.isReadOnlyQuestion(t),r=e?`/api/ai-coder/apps/${this.normalizedAppId}/questions`:`/api/ai-coder/apps/${this.normalizedAppId}/requests`;this.api.post(this.apiBase,r,{title:String(this.requestTitle||"").trim(),message:t,workflow_mode:this.workflowMode,request_kind:e?"question":this.requestKind,data_change_policy:this.dataChangePolicy,deployment_target:this.deploymentTarget,evidence_notes:this.evidenceNotes,qa_focus:this.qaFocus,risk_notes:this.riskNotes},this.normalizedAppToken).subscribe({next:t=>{if(this.successMessage=e?"AI answered your question.":"AI chat started.",this.requestTitle="",this.requestMessage="",t?.job){const e=this.normalizeJob(t.job);this.jobs=[e,...this.jobs.filter(t=>t._id!==e._id)],this.selectJob(e)}},error:t=>this.handleError(t,"Could not start the AI chat."),complete:()=>this.isSubmitting=!1})}sendChatMessage(){const t=this.selectedJobId,e=String(this.chatMessage||"").trim();if(!t||!e||this.isSubmitting)return;this.isSubmitting=!0,this.clearMessages();const r=this.selectedJobIsQuestionOnly?`/api/ai-coder/jobs/${t}/question`:`/api/ai-coder/jobs/${t}/prompt`;this.api.post(this.apiBase,r,{message:e},this.normalizedAppToken).subscribe({next:t=>{this.successMessage=this.selectedJobIsQuestionOnly?"AI answered your question.":"Message sent to AICoder.",this.chatMessage="",t?.job&&(this.mergeJob(t.job),this.selectJob(t.job))},error:t=>this.handleError(t,"Could not send the message."),complete:()=>this.isSubmitting=!1})}approveSelectedJob(){this.runJobAction("approve","Approved.")}createPullRequest(){this.runJobAction("create-pr","Test review started.")}publishSelectedJob(){this.runJobAction("publish","Approved for live deploy.")}sendToTestSite(){this.canSendSelectedJobToTestSite&&this.runJobAction("test-deploy","Sent to the test site.")}approveAndDeployLive(){this.canApproveSelectedJobLive&&this.runJobAction("publish","Approved for live deploy.")}deleteSelectedChat(){const t=this.selectedJobId;t&&!this.isSubmitting&&window.confirm("Delete this AI chat?")&&(this.isSubmitting=!0,this.clearMessages(),this.api.delete(this.apiBase,`/api/ai-coder/jobs/${t}`,this.normalizedAppToken).subscribe({next:()=>{this.successMessage="AI chat deleted.",this.jobs=this.jobs.filter(e=>e._id!==t),this.selectedJob=this.jobs[0]||null,this.jobLogs=[],this.selectedJob&&this.loadJobLogs(this.selectedJob._id)},error:t=>this.handleError(t,"Could not delete the AI chat."),complete:()=>this.isSubmitting=!1}))}queueDeploy(t){this.normalizedAppId&&!this.isSubmitting&&(this.isSubmitting=!0,this.clearMessages(),this.api.post(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/deploy`,{deploy_client:"full"===t||"client"===t,deploy_backend:"full"===t||"backend"===t,clear_destination:!0,invalidate:!0},this.normalizedAppToken).subscribe({next:()=>{this.successMessage="full"===t?"Full deploy queued.":("client"===t?"Client":"Server")+" deploy queued.",this.loadDeployHistory()},error:t=>this.handleError(t,"Could not queue deploy."),complete:()=>this.isSubmitting=!1}))}openPr(t=this.selectedJob){const e=this.resolvePrUrl(t);e&&window.open(e,"_blank","noopener")}loadJobs(){this.normalizedAppId&&(this.isLoading=!0,this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/jobs`,this.normalizedAppToken).subscribe({next:t=>{const e=this.selectedJobId;this.jobs=(t?.jobs||[]).map(t=>this.normalizeJob(t));const r=e&&this.jobs.find(t=>t._id===e)||null;r?(this.selectedJob=r,this.loadJobLogs(r._id)):!this.selectedJob&&this.visibleJobs.length&&this.selectJob(this.visibleJobs[0])},error:t=>this.handleError(t,"Could not load AI chats."),complete:()=>this.isLoading=!1}))}loadJobLogs(t){const e=String(t||"").trim();e?this.api.get(this.apiBase,`/api/ai-coder/jobs/${e}/logs?limit=80`,this.normalizedAppToken).subscribe({next:t=>this.jobLogs=this.buildTimeline(this.selectedJob,t?.logs||[]),error:t=>this.handleError(t,"Could not load chat history.")}):this.jobLogs=[]}loadDeployHistory(){this.normalizedAppId&&this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/deploy/history`,this.normalizedAppToken).subscribe({next:t=>{this.deployOperations=t?.operations||[],this.deployJobs=t?.jobs||t?.deploy_jobs||[]},error:t=>this.handleError(t,"Could not load deploy history.")})}loadGitStatus(){this.normalizedAppId&&this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/git`,this.normalizedAppToken).subscribe({next:t=>this.gitStatus=t?.status||t||null,error:t=>this.handleError(t,"Could not load git status.")})}get normalizedAppId(){return String(this.appId||"").trim()}get selectedJobId(){return String(this.selectedJob?._id||"").trim()}get normalizedAppToken(){return String(this.appToken||"").trim()}get activeDeploys(){return this.deployOperations.filter(t=>"In Progress"===String(t?.status||"").trim())}get recentDeploys(){return this.deployOperations.filter(t=>"In Progress"!==String(t?.status||"").trim()).slice(0,6)}get visibleJobs(){return this.computeVisibleJobs(this.jobs)}get hasSelectedJob(){return!!this.selectedJob}get selectedJobIsWorking(){return this.isActiveJob(this.selectedJob)}get selectedJobIsQuestionOnly(){return this.isQuestionOnlyJob(this.selectedJob)}get selectedJobHasChanges(){return this.hasJobChanges(this.selectedJob)}get selectedJobHasReview(){return!!this.resolvePrUrl(this.selectedJob)}get canSendSelectedJobToTestSite(){return!!this.selectedJobId&&!this.isSubmitting&&!this.selectedJobIsWorking&&this.selectedJobHasChanges&&!this.selectedJobHasReview}get canApproveSelectedJobLive(){return!!this.selectedJobId&&!this.isSubmitting&&!this.selectedJobIsWorking&&this.selectedJobHasReview}get selectedJobNextStepMessage(){return this.selectedJob?this.selectedJobIsQuestionOnly?"":this.selectedJobIsWorking?"I am working on this. I will update this chat when there is something ready for you to review.":this.selectedJobHasReview?"A test version is ready. Open it first, then approve it when everything looks right.":this.selectedJobHasChanges?"The update is ready. Send it to the test site when you are ready.":this.isFailedJob(this.selectedJob)?"This needs your attention before it can continue. Add a reply with what you want to try next.":"This chat will update when there is something new.":""}isActiveJob(t){if(this.isQuestionOnlyJob(t)&&"COMPLETE"===String(t?.phase||"").toUpperCase())return!1;const e=String(t?.status||"").toLowerCase(),r=String(t?.phase||t?.stage||"").toLowerCase();return!(e.includes("fail")||e.includes("error")||e.includes("complete")||e.includes("passed"))&&(!r.includes("complete")&&!r.includes("review")&&("queued"===e||"in progress"===e||"running"===e||"pending"===e||["discovery","planning","execution","linting","building"].includes(r)))}resolvePrUrl(t){return String(t?.pr_url||t?.pull_request_url||"").trim()}friendlyJobStatus(t){if(this.isQuestionOnlyJob(t))return this.isActiveJob(t)?"AI is answering":"Answered";const e=String(t?.status||t?.stage||"").trim().toLowerCase(),r=String(t?.phase||"").trim().toLowerCase();return e||r?e.includes("complete")||e.includes("passed")||r.includes("complete")?this.resolvePrUrl(t)?"Ready to test":"Ready to review":e.includes("fail")||e.includes("error")?"Needs attention":e.includes("progress")||e.includes("running")||e.includes("pending")||e.includes("queued")?"AI is working":e.includes("pause")||e.includes("stopped")?"Paused":this.toTitle(e||r):"Waiting to start"}friendlyDeployStatus(t){const e=String(t?.status||"").trim(),r=String(t?.type||"").trim();if(!e&&!r)return"Website update";return`${r?this.toTitle(r.replace(/ai coder/gi,"").replace(/backend/gi,"server")):"Website update"}${e?` - ${e}`:""}`}formatDate(t){if(!t)return"";const e=t instanceof Date?t:new Date(t);return Number.isNaN(e.getTime())?String(t):e.toLocaleString()}buildTimeline(t,e){const r=[],a=this.isQuestionOnlyJob(t),i=t=>{const e=this.sanitizeUserFacingMessage(t.message);if(!e)return;const a=`${t.role}:${t.label}:${t.date||""}:${e}`;r.some(t=>t.id===a||t.message===e)||r.push({...t,id:a,message:e})};(Array.isArray(t?.conversation)&&t?.conversation||[]).forEach((e,r)=>{const s="assistant"===String(e?.role||"").toLowerCase()?"assistant":"user",o="user"===s?this.extractUserRequest(e?.message||""):String(e?.message||"");i({role:s,label:"user"===s?"You":"Assistant",message:o,date:this.formatDate(e?.timestamp||e?.createdAt),active:!1}),0===r&&"user"===s&&this.isActiveJob(t)&&i({role:"assistant",label:"Assistant",message:a?"I am checking this and will answer without changing anything.":"I am working on this. I will update this chat when there is something ready for you to review.",date:"",active:!0})});const s=this.extractQuestionAnswerFromSummary(t);return s&&i({role:"assistant",label:"Assistant",message:s,date:this.formatDate(t?.updatedAt),active:!1}),!r.length&&this.isActiveJob(t)&&i({role:"assistant",label:"Assistant",message:a?"I am reading this and will answer shortly.":"I am working on this. I will update this chat when there is something ready for you to review.",date:"",active:!0}),r}extractUserRequest(t){const e=String(t||"").trim(),r=e.match(/(?:^|\n)Request:\s*([\s\S]*?)(?:\n\n|(?:^|\n)AICoder case runner contract:|$)/i);return r?.[1]?r[1].trim():e.split("\n").filter(t=>!t.match(/^(This is a change request|App:|Template:|App root:|AICoder|Case-building|Data safety|QA playbook|Branch, PR|Constraints:|- )/i)).join("\n").trim()}sanitizeUserFacingMessage(t){const e=String(t||"").replace(/```[\s\S]*?```/g,"[details hidden]").replace(/\/var\/ai-workspace\/[^\s)]+/g,"the app workspace").replace(/ResolveIO\/aicoder-[\w-]+/g,"the app repository").replace(/apps\/[a-f0-9]{24}/gi,"the app").replace(/[a-f0-9]{24}/gi,"the app").replace(/\[([^\]]+)\]\([^)]+\)/g,(t,e)=>this.isInternalText(e)?"the app":e).replace(/\[([^\]]+)\]/g,(t,e)=>this.isInternalText(e)?"":e).trim();if(!e||this.isInternalOnlyMessage(e))return"";let r=!1;return e.split(/\r?\n/).map(t=>this.cleanUserFacingLine(t)).filter(t=>{const e=t.trim();return!!e&&(!this.isInternalLine(e)||!(!/^verification\s*:/i.test(e)||r)&&(r=!0,!0))}).map(t=>/^verification\s*:/i.test(t)?"I checked the update after making the change.":t).join("\n").replace(/\n{3,}/g,"\n\n").trim()}cleanUserFacingLine(t){return String(t||"").replace(/^\s*[-*]\s+/,"").replace(/^\[[^\]]+\]\s*/,"").replace(/`([^`]+)`/g,(t,e)=>this.isInternalText(e)?"the app":e).replace(/\btop-nav\b/gi,"top navigation").replace(/\s+from the app\b[\s\S]*$/i,".").replace(/\s+/g," ").trim()}isInternalOnlyMessage(t){const e=String(t||"").toLowerCase();return e.includes("deterministic review")||e.includes("change request final review")||e.includes("cycle 1 summary")||e.includes("gates ")||e.includes("lint:pass")||e.includes("build:pass")||e.includes("review:pass")||e.includes("validate:pass")||e.includes("modified files captured")}isInternalLine(t){const e=String(t||"").toLowerCase();return/^verification\s*:/i.test(t)||this.isInternalText(t)||e.includes("grep")||e.includes("npm ")||e.includes("lint")||e.includes("build-dev")||e.includes("build-prod")||e.includes("workspace")||e.includes("routerlink")||e.includes("navtabs")||e.includes("fixtures")||e.includes("migrations")||e.includes("schemas")||e.includes("server flows")||e.includes("package files")||e.includes("deterministic review")||e.includes("cycle ")||e.includes("gates ")}isInternalText(t){const e=String(t||"").trim();return!!e&&(/(^|[\s"'(])(?:\.?\/)?[\w.-]+\/[\w./-]+/.test(e)||/\b[\w.-]+\.(ts|tsx|js|jsx|html|scss|css|json|md|yml|yaml|lock)\b/i.test(e)||/\b(package\.json|package-lock\.json|node_modules|app workspace|app repository|git|github|branch|pull request|pr_url)\b/i.test(e)||/\b(npm|grep|routerlink|navtabs|typescript|angular|nodejs|codebuild|s3:\/\/)\b/i.test(e))}computeVisibleJobs(t){const e=new Set,r=[];return(t||[]).forEach(t=>{const a=this.jobDisplayKey(t);e.has(a)||(e.add(a),r.push(t))}),r}jobDisplayKey(t){const e=(Array.isArray(t?.conversation)?t.conversation:[]).find(t=>"assistant"!==String(t?.role||"").toLowerCase())?.message||"";return String(`${t?.title||""} ${e||t?.description||""}`||t?._id||"").toLowerCase().replace(/[^a-z0-9]+/g," ").trim().slice(0,180)}hasJobChanges(t){return!this.isQuestionOnlyJob(t)&&this.hasJobChangesWithoutQuestionGuard(t)}hasJobChangesWithoutQuestionGuard(t){const e=t?.artifacts?.modifiedFiles||{},r=t?.artifacts?.diffs||{};return Object.keys(e).length>0||Object.keys(r).length>0}isQuestionOnlyJob(t){return!0===t?.runPolicy?.questionOnly||String(t?.responseSummary||"").includes("AICODER_QUESTION_ONLY")||this.isLikelyQuestionChat(t)}isLikelyQuestionChat(t){if(!t||this.hasJobChangesWithoutQuestionGuard(t)||this.resolvePrUrl(t))return!1;const e=(Array.isArray(t.conversation)?t.conversation:[]).find(t=>"assistant"!==String(t?.role||"").toLowerCase())?.message||"";return this.isReadOnlyQuestion(String(e||t.description||t.title||""))}extractQuestionAnswerFromSummary(t){const e=String(t?.responseSummary||"").trim();return e.includes("AICODER_QUESTION_ONLY")?e.replace(/^AICODER_QUESTION_ONLY\s*/i,"").trim():""}isReadOnlyQuestion(t){const e=String(t||"").trim().toLowerCase();if(!e)return!1;if(/\b(fix|change|update|add|remove|delete|create|build|make|implement|deploy|merge|push|edit|replace|rename|turn on|turn off|enable|disable|set|move)\b/i.test(e))return!1;return e.includes("?")||/(^|\b)(what|why|how|where|when|who|which|explain|describe|tell me|walk me through|show me|does|do|is|are|can i|can you)\b/i.test(e)}isFailedJob(t){const e=String(t?.status||t?.phase||"").toLowerCase();return e.includes("fail")||e.includes("error")||e.includes("blocked")}runJobAction(t,e){const r=this.selectedJobId;r&&!this.isSubmitting&&(this.isSubmitting=!0,this.clearMessages(),this.api.post(this.apiBase,`/api/ai-coder/jobs/${r}/${t}`,{},this.normalizedAppToken).subscribe({next:t=>{this.successMessage=e,t?.job&&(this.mergeJob(t.job),this.selectJob(t.job))},error:e=>this.handleError(e,`Could not ${t.replace("-"," ")}.`),complete:()=>this.isSubmitting=!1}))}mergeJob(t){const e=this.normalizeJob(t);this.jobs=[e,...this.jobs.filter(t=>t._id!==e._id)],this.selectedJob=e}normalizeJob(t){return{...t,_id:String(t?._id||"").trim(),title:String(t?.title||"AI Chat").trim(),status:String(t?.status||"").trim()||"Queued"}}normalizeTab(t){return"deploy"===t||"git"===t?t:"chats"}toTitle(t){return String(t||"").replace(/[_-]+/g," ").replace(/\s+/g," ").trim().split(" ").filter(Boolean).map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")||"Waiting to start"}startAutoRefresh(){this.refreshSub?.unsubscribe(),this.autoRefresh&&(this.refreshSub=interval(15e3).subscribe(()=>{(this.jobs.some(t=>this.isActiveJob(t))||this.activeDeploys.length)&&this.refresh()}))}clearMessages(){this.errorMessage="",this.successMessage=""}handleError(t,e){this.errorMessage=String(t?.error?.message||t?.message||e),this.successMessage="",this.isSubmitting=!1,this.isLoading=!1}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchComponent,deps:[{token:AICoderWorkbenchApiService}],target:i0.ɵɵFactoryTarget.Component});static"ɵcmp"=i0.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"21.1.2",type:AICoderWorkbenchComponent,isStandalone:!1,selector:"resolveio-client-lib-aicoder-workbench",inputs:{appId:"appId",appName:"appName",apiBase:"apiBase",appToken:"appToken",defaultTab:"defaultTab",compact:"compact",showHeader:"showHeader",autoRefresh:"autoRefresh"},usesOnChanges:!0,ngImport:i0,template:'<section class="aicoder-workbench" [class.aicoder-workbench--compact]="compact">\n\t@if (showHeader) {\n\t\t<header class="aicoder-workbench__header">\n\t\t\t<div>\n\t\t\t\t<p>AI Chats</p>\n\t\t\t\t<h2>{{ appName || \'AICoder App\' }}</h2>\n\t\t\t</div>\n\t\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh</button>\n\t\t</header>\n\t}\n\n\t@if (errorMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--error">{{ errorMessage }}</div>\n\t}\n\t@if (successMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--success">{{ successMessage }}</div>\n\t}\n\n\t<div class="aicoder-workbench__intro">\n\t\t<div>\n\t\t\t<p class="aicoder-workbench__eyebrow">AI Assistant</p>\n\t\t\t<h3>Ask for help or tell us what to change.</h3>\n\t\t\t<p>Ask questions in plain English, or request updates. AI will answer, guide you, or prepare changes when there is work to do.</p>\n\t\t</div>\n\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh Chats</button>\n\t</div>\n\n\t<div class="aicoder-workbench__layout">\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--compose">\n\t\t\t<h3>New AI Chat</h3>\n\t\t\t<input class="aicoder-workbench__input" [(ngModel)]="requestTitle" placeholder="Short name for this chat" />\n\t\t\t<textarea class="aicoder-workbench__textarea" rows="6" [(ngModel)]="requestMessage" placeholder="Ask a question, explain what is confusing, or describe what you want changed."></textarea>\n\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="submitRequest()" [disabled]="isSubmitting || !requestMessage.trim()">\n\t\t\t\tStart AI Chat\n\t\t\t</button>\n\t\t</section>\n\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--list">\n\t\t\t<div class="aicoder-workbench__panel-head">\n\t\t\t\t<h3>Your AI Chats</h3>\n\t\t\t\t<span>{{ visibleJobs.length }}</span>\n\t\t\t</div>\n\t\t\t@if (!jobs.length && !isLoading) {\n\t\t\t\t<p class="aicoder-workbench__empty">No AI chats yet.</p>\n\t\t\t}\n\t\t\t@if (isLoading && !jobs.length) {\n\t\t\t\t<p class="aicoder-workbench__empty">Loading AI chats...</p>\n\t\t\t}\n\t\t\t<div class="aicoder-workbench__chat-list">\n\t\t\t\t@for (job of visibleJobs; track job._id) {\n\t\t\t\t\t<button type="button" class="aicoder-workbench__chat" [class.is-active]="selectedJob?._id === job._id" (click)="selectJob(job)">\n\t\t\t\t\t\t<strong>{{ job.title || \'AI Chat\' }}</strong>\n\t\t\t\t\t\t<span>{{ friendlyJobStatus(job) }}</span>\n\t\t\t\t\t\t<small>{{ formatDate(job.updatedAt || job.createdAt) }}</small>\n\t\t\t\t\t</button>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</section>\n\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--detail">\n\t\t\t@if (selectedJob) {\n\t\t\t\t<div class="aicoder-workbench__detail-head">\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<p class="aicoder-workbench__eyebrow">Open Chat</p>\n\t\t\t\t\t\t<h3>{{ selectedJob.title || \'AI Chat\' }}</h3>\n\t\t\t\t\t\t<span class="aicoder-workbench__status">{{ friendlyJobStatus(selectedJob) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<button type="button" class="aicoder-workbench__danger" (click)="deleteSelectedChat()" [disabled]="isSubmitting">Delete Chat</button>\n\t\t\t\t</div>\n\n\t\t\t\t@if (canSendSelectedJobToTestSite || canApproveSelectedJobLive || selectedJobHasReview) {\n\t\t\t\t\t<div class="aicoder-workbench__actions" aria-label="AI chat actions">\n\t\t\t\t\t\t@if (canSendSelectedJobToTestSite) {\n\t\t\t\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendToTestSite()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tSend to Test Site\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (canApproveSelectedJobLive) {\n\t\t\t\t\t\t\t<button type="button" (click)="approveAndDeployLive()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tApprove and Put Live\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (selectedJobHasReview) {\n\t\t\t\t\t\t\t<button type="button" (click)="openPr()" [disabled]="!resolvePrUrl(selectedJob)">\n\t\t\t\t\t\t\t\tOpen Review\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\t@else if (selectedJobNextStepMessage) {\n\t\t\t\t\t<div class="aicoder-workbench__next-step">\n\t\t\t\t\t\t@if (selectedJobIsWorking) {\n\t\t\t\t\t\t\t<span class="aicoder-workbench__spinner" aria-hidden="true"></span>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t<p>{{ selectedJobNextStepMessage }}</p>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\n\t\t\t\t<div class="aicoder-workbench__history">\n\t\t\t\t\t@if (!jobLogs.length) {\n\t\t\t\t\t\t<div class="aicoder-workbench__thinking">\n\t\t\t\t\t\t\t<span class="aicoder-workbench__spinner" aria-hidden="true"></span>\n\t\t\t\t\t\t\t<p>AI is getting started.</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t\t@for (log of jobLogs; track log.id) {\n\t\t\t\t\t\t<div class="aicoder-workbench__message" [class.aicoder-workbench__message--user]="log.role === \'user\'" [class.aicoder-workbench__message--assistant]="log.role === \'assistant\'" [class.aicoder-workbench__message--system]="log.role === \'system\'" [class.is-active]="log.active">\n\t\t\t\t\t\t\t<div class="aicoder-workbench__message-meta">\n\t\t\t\t\t\t\t\t<strong>{{ log.label }}</strong>\n\t\t\t\t\t\t\t\t@if (log.date) {\n\t\t\t\t\t\t\t\t\t<span>{{ log.date }}</span>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<p>{{ log.message }}</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\n\t\t\t\t<label class="aicoder-workbench__reply">\n\t\t\t\t\t<span>Reply to AI</span>\n\t\t\t\t\t<textarea class="aicoder-workbench__textarea" rows="4" [(ngModel)]="chatMessage" [placeholder]="selectedJobIsQuestionOnly ? \'Ask a follow-up question.\' : \'Add more details, ask for another change, or answer a question from AI.\'"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendChatMessage()" [disabled]="!selectedJobId || !chatMessage.trim() || isSubmitting">\n\t\t\t\t\tSend Reply\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t@else {\n\t\t\t\t<div class="aicoder-workbench__blank">\n\t\t\t\t\t<h3>Select an AI chat</h3>\n\t\t\t\t\t<p>Open a chat from the list or start a new one.</p>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</section>\n\t</div>\n\n\t@if (activeDeploys.length || recentDeploys.length) {\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--updates">\n\t\t\t<div class="aicoder-workbench__panel-head">\n\t\t\t\t<h3>Website Updates</h3>\n\t\t\t\t<span>{{ activeDeploys.length ? \'Running now\' : \'Recent\' }}</span>\n\t\t\t</div>\n\t\t\t<div class="aicoder-workbench__updates">\n\t\t\t\t@for (op of activeDeploys; track op.id || op.jobId) {\n\t\t\t\t\t<div class="aicoder-workbench__update">\n\t\t\t\t\t\t<strong>{{ friendlyDeployStatus(op) }}</strong>\n\t\t\t\t\t\t<span>{{ op.backend_deploy_step || op.aws_build_current_phase || op.message || \'Working on the update\' }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\t@for (op of recentDeploys; track op.id || op.jobId) {\n\t\t\t\t\t<div class="aicoder-workbench__update">\n\t\t\t\t\t\t<strong>{{ friendlyDeployStatus(op) }}</strong>\n\t\t\t\t\t\t<span>{{ op.message || op.backend_deploy_step || \'Update finished\' }}</span>\n\t\t\t\t\t\t<small>{{ formatDate(op.updatedAt || op.createdAt) }}</small>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</section>\n\t}\n</section>\n',styles:['.aicoder-workbench{--aicoder-accent: #2f7d73;--aicoder-accent-dark: #155e57;--aicoder-border: #d8e2ec;--aicoder-ink: #1f2933;--aicoder-muted: #64748b;--aicoder-soft: #f5f8fb;display:flex;flex-direction:column;gap:1rem;color:var(--aicoder-ink)}.aicoder-workbench__header,.aicoder-workbench__intro,.aicoder-workbench__panel-head,.aicoder-workbench__detail-head,.aicoder-workbench__actions{display:flex;align-items:center;justify-content:space-between;gap:1rem}.aicoder-workbench__header p,.aicoder-workbench__eyebrow{margin:0 0 .25rem;color:var(--aicoder-accent-dark);font-size:.75rem;font-weight:800;letter-spacing:0;text-transform:uppercase}.aicoder-workbench__header h2,.aicoder-workbench__intro h3,.aicoder-workbench__panel h3{margin:0;font-weight:800;line-height:1.2}.aicoder-workbench__header h2{font-size:1.35rem}.aicoder-workbench__intro{border:1px solid var(--aicoder-border);border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__intro h3{font-size:1.25rem}.aicoder-workbench__intro p:not(.aicoder-workbench__eyebrow),.aicoder-workbench__blank p,.aicoder-workbench__empty{margin:.35rem 0 0;color:var(--aicoder-muted);line-height:1.45}.aicoder-workbench__layout{display:grid;grid-template-columns:minmax(260px,.75fr) minmax(320px,1.35fr);grid-template-areas:"compose detail" "list detail";gap:1rem;align-items:start}.aicoder-workbench__panel{min-width:0;border:1px solid var(--aicoder-border);border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__panel--compose{grid-area:compose}.aicoder-workbench__panel--list{grid-area:list}.aicoder-workbench__panel--detail{grid-area:detail}.aicoder-workbench__panel--updates{display:grid;gap:.75rem}.aicoder-workbench__panel-head span,.aicoder-workbench__status{display:inline-flex;align-items:center;border-radius:999px;background:#e7f4f0;color:var(--aicoder-accent-dark);font-size:.78rem;font-weight:800;padding:.25rem .6rem}.aicoder-workbench__next-step,.aicoder-workbench__thinking{display:flex;align-items:center;gap:.65rem;border:1px solid #dbe7ef;border-radius:8px;background:#f7fafc;color:var(--aicoder-muted);margin-top:1rem;padding:.85rem}.aicoder-workbench__next-step p,.aicoder-workbench__thinking p{margin:0;line-height:1.4}.aicoder-workbench__spinner{display:inline-block;width:1rem;height:1rem;border:2px solid #bfd2df;border-top-color:var(--aicoder-accent);border-radius:999px;flex:0 0 auto;animation:aicoder-workbench-spin .85s linear infinite}.aicoder-workbench__input,.aicoder-workbench__textarea{width:100%;border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:var(--aicoder-ink);padding:.7rem .8rem}.aicoder-workbench__textarea{resize:vertical}.aicoder-workbench__input,.aicoder-workbench__textarea{margin:.75rem 0}.aicoder-workbench__ghost,.aicoder-workbench__primary,.aicoder-workbench__danger,.aicoder-workbench__actions button{border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:var(--aicoder-ink);cursor:pointer;font:inherit;font-weight:800;min-height:40px;padding:.6rem .85rem;text-decoration:none}.aicoder-workbench__primary{border-color:var(--aicoder-accent);background:var(--aicoder-accent);color:#fff}.aicoder-workbench__danger{border-color:#fecaca;color:#991b1b}.aicoder-workbench button:disabled{cursor:not-allowed;opacity:.55}.aicoder-workbench__chat-list,.aicoder-workbench__history,.aicoder-workbench__updates{display:flex;flex-direction:column;gap:.5rem}.aicoder-workbench__chat-list,.aicoder-workbench__history{max-height:520px;overflow:auto}.aicoder-workbench__chat,.aicoder-workbench__message,.aicoder-workbench__update{display:flex;flex-direction:column;align-items:flex-start;gap:.25rem;width:100%;border:1px solid #e2e8f0;border-radius:7px;background:var(--aicoder-soft);padding:.8rem;text-align:left}.aicoder-workbench__message{background:#fff}.aicoder-workbench__message--user{border-color:#c9d8e6;background:#f8fafc}.aicoder-workbench__message--assistant{border-color:#b7ded3;background:#f4fbf9}.aicoder-workbench__message--system{background:#fbfdff}.aicoder-workbench__message.is-active{border-color:var(--aicoder-accent)}.aicoder-workbench__message-meta{display:flex;align-items:center;justify-content:space-between;gap:.75rem;width:100%;color:var(--aicoder-muted);font-size:.8rem;line-height:1.35}.aicoder-workbench__message-meta strong{color:var(--aicoder-ink);font-size:.82rem}.aicoder-workbench__chat{cursor:pointer}.aicoder-workbench__chat.is-active{border-color:var(--aicoder-accent);background:#ecfdf5}.aicoder-workbench__chat span,.aicoder-workbench__chat small,.aicoder-workbench__message span,.aicoder-workbench__update span,.aicoder-workbench__update small{color:var(--aicoder-muted);font-size:.82rem;line-height:1.35}.aicoder-workbench__message p{margin:0;white-space:pre-wrap;overflow-wrap:anywhere}.aicoder-workbench__history{min-height:260px;margin:.75rem 0}.aicoder-workbench__actions{flex-wrap:wrap;justify-content:flex-start;margin-top:1rem}.aicoder-workbench__reply{display:block;margin-top:1rem}.aicoder-workbench__reply span{display:block;color:var(--aicoder-muted);font-size:.78rem;font-weight:800;text-transform:uppercase}.aicoder-workbench__blank{display:grid;min-height:280px;place-content:center;text-align:center}.aicoder-workbench__notice{border-radius:6px;padding:.75rem .9rem;font-weight:800}.aicoder-workbench__notice--error{border:1px solid #fecaca;background:#fff1f2;color:#991b1b}.aicoder-workbench__notice--success{border:1px solid #bbf7d0;background:#f0fdf4;color:#166534}@keyframes aicoder-workbench-spin{to{transform:rotate(360deg)}}.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:minmax(240px,.7fr) minmax(320px,1.3fr)}@media(max-width:980px){.aicoder-workbench__layout,.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:1fr;grid-template-areas:"compose" "list" "detail"}.aicoder-workbench__header,.aicoder-workbench__intro,.aicoder-workbench__detail-head{align-items:stretch;flex-direction:column}.aicoder-workbench__actions button,.aicoder-workbench__primary,.aicoder-workbench__ghost,.aicoder-workbench__danger{width:100%}}\n'],dependencies:[{kind:"directive",type:i2$1.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:i2$1.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:i2$1.NgModel,selector:"[ngModel]:not([formControlName]):not([formControl])",inputs:["name","disabled","ngModel","ngModelOptions"],outputs:["ngModelChange"],exportAs:["ngModel"]}]})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchComponent,decorators:[{type:Component,args:[{selector:"resolveio-client-lib-aicoder-workbench",standalone:!1,template:'<section class="aicoder-workbench" [class.aicoder-workbench--compact]="compact">\n\t@if (showHeader) {\n\t\t<header class="aicoder-workbench__header">\n\t\t\t<div>\n\t\t\t\t<p>AI Chats</p>\n\t\t\t\t<h2>{{ appName || \'AICoder App\' }}</h2>\n\t\t\t</div>\n\t\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh</button>\n\t\t</header>\n\t}\n\n\t@if (errorMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--error">{{ errorMessage }}</div>\n\t}\n\t@if (successMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--success">{{ successMessage }}</div>\n\t}\n\n\t<div class="aicoder-workbench__intro">\n\t\t<div>\n\t\t\t<p class="aicoder-workbench__eyebrow">AI Assistant</p>\n\t\t\t<h3>Ask for help or tell us what to change.</h3>\n\t\t\t<p>Ask questions in plain English, or request updates. AI will answer, guide you, or prepare changes when there is work to do.</p>\n\t\t</div>\n\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh Chats</button>\n\t</div>\n\n\t<div class="aicoder-workbench__layout">\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--compose">\n\t\t\t<h3>New AI Chat</h3>\n\t\t\t<input class="aicoder-workbench__input" [(ngModel)]="requestTitle" placeholder="Short name for this chat" />\n\t\t\t<textarea class="aicoder-workbench__textarea" rows="6" [(ngModel)]="requestMessage" placeholder="Ask a question, explain what is confusing, or describe what you want changed."></textarea>\n\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="submitRequest()" [disabled]="isSubmitting || !requestMessage.trim()">\n\t\t\t\tStart AI Chat\n\t\t\t</button>\n\t\t</section>\n\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--list">\n\t\t\t<div class="aicoder-workbench__panel-head">\n\t\t\t\t<h3>Your AI Chats</h3>\n\t\t\t\t<span>{{ visibleJobs.length }}</span>\n\t\t\t</div>\n\t\t\t@if (!jobs.length && !isLoading) {\n\t\t\t\t<p class="aicoder-workbench__empty">No AI chats yet.</p>\n\t\t\t}\n\t\t\t@if (isLoading && !jobs.length) {\n\t\t\t\t<p class="aicoder-workbench__empty">Loading AI chats...</p>\n\t\t\t}\n\t\t\t<div class="aicoder-workbench__chat-list">\n\t\t\t\t@for (job of visibleJobs; track job._id) {\n\t\t\t\t\t<button type="button" class="aicoder-workbench__chat" [class.is-active]="selectedJob?._id === job._id" (click)="selectJob(job)">\n\t\t\t\t\t\t<strong>{{ job.title || \'AI Chat\' }}</strong>\n\t\t\t\t\t\t<span>{{ friendlyJobStatus(job) }}</span>\n\t\t\t\t\t\t<small>{{ formatDate(job.updatedAt || job.createdAt) }}</small>\n\t\t\t\t\t</button>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</section>\n\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--detail">\n\t\t\t@if (selectedJob) {\n\t\t\t\t<div class="aicoder-workbench__detail-head">\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<p class="aicoder-workbench__eyebrow">Open Chat</p>\n\t\t\t\t\t\t<h3>{{ selectedJob.title || \'AI Chat\' }}</h3>\n\t\t\t\t\t\t<span class="aicoder-workbench__status">{{ friendlyJobStatus(selectedJob) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<button type="button" class="aicoder-workbench__danger" (click)="deleteSelectedChat()" [disabled]="isSubmitting">Delete Chat</button>\n\t\t\t\t</div>\n\n\t\t\t\t@if (canSendSelectedJobToTestSite || canApproveSelectedJobLive || selectedJobHasReview) {\n\t\t\t\t\t<div class="aicoder-workbench__actions" aria-label="AI chat actions">\n\t\t\t\t\t\t@if (canSendSelectedJobToTestSite) {\n\t\t\t\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendToTestSite()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tSend to Test Site\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (canApproveSelectedJobLive) {\n\t\t\t\t\t\t\t<button type="button" (click)="approveAndDeployLive()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tApprove and Put Live\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (selectedJobHasReview) {\n\t\t\t\t\t\t\t<button type="button" (click)="openPr()" [disabled]="!resolvePrUrl(selectedJob)">\n\t\t\t\t\t\t\t\tOpen Review\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\t@else if (selectedJobNextStepMessage) {\n\t\t\t\t\t<div class="aicoder-workbench__next-step">\n\t\t\t\t\t\t@if (selectedJobIsWorking) {\n\t\t\t\t\t\t\t<span class="aicoder-workbench__spinner" aria-hidden="true"></span>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t<p>{{ selectedJobNextStepMessage }}</p>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\n\t\t\t\t<div class="aicoder-workbench__history">\n\t\t\t\t\t@if (!jobLogs.length) {\n\t\t\t\t\t\t<div class="aicoder-workbench__thinking">\n\t\t\t\t\t\t\t<span class="aicoder-workbench__spinner" aria-hidden="true"></span>\n\t\t\t\t\t\t\t<p>AI is getting started.</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t\t@for (log of jobLogs; track log.id) {\n\t\t\t\t\t\t<div class="aicoder-workbench__message" [class.aicoder-workbench__message--user]="log.role === \'user\'" [class.aicoder-workbench__message--assistant]="log.role === \'assistant\'" [class.aicoder-workbench__message--system]="log.role === \'system\'" [class.is-active]="log.active">\n\t\t\t\t\t\t\t<div class="aicoder-workbench__message-meta">\n\t\t\t\t\t\t\t\t<strong>{{ log.label }}</strong>\n\t\t\t\t\t\t\t\t@if (log.date) {\n\t\t\t\t\t\t\t\t\t<span>{{ log.date }}</span>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<p>{{ log.message }}</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\n\t\t\t\t<label class="aicoder-workbench__reply">\n\t\t\t\t\t<span>Reply to AI</span>\n\t\t\t\t\t<textarea class="aicoder-workbench__textarea" rows="4" [(ngModel)]="chatMessage" [placeholder]="selectedJobIsQuestionOnly ? \'Ask a follow-up question.\' : \'Add more details, ask for another change, or answer a question from AI.\'"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendChatMessage()" [disabled]="!selectedJobId || !chatMessage.trim() || isSubmitting">\n\t\t\t\t\tSend Reply\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t@else {\n\t\t\t\t<div class="aicoder-workbench__blank">\n\t\t\t\t\t<h3>Select an AI chat</h3>\n\t\t\t\t\t<p>Open a chat from the list or start a new one.</p>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</section>\n\t</div>\n\n\t@if (activeDeploys.length || recentDeploys.length) {\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--updates">\n\t\t\t<div class="aicoder-workbench__panel-head">\n\t\t\t\t<h3>Website Updates</h3>\n\t\t\t\t<span>{{ activeDeploys.length ? \'Running now\' : \'Recent\' }}</span>\n\t\t\t</div>\n\t\t\t<div class="aicoder-workbench__updates">\n\t\t\t\t@for (op of activeDeploys; track op.id || op.jobId) {\n\t\t\t\t\t<div class="aicoder-workbench__update">\n\t\t\t\t\t\t<strong>{{ friendlyDeployStatus(op) }}</strong>\n\t\t\t\t\t\t<span>{{ op.backend_deploy_step || op.aws_build_current_phase || op.message || \'Working on the update\' }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\t@for (op of recentDeploys; track op.id || op.jobId) {\n\t\t\t\t\t<div class="aicoder-workbench__update">\n\t\t\t\t\t\t<strong>{{ friendlyDeployStatus(op) }}</strong>\n\t\t\t\t\t\t<span>{{ op.message || op.backend_deploy_step || \'Update finished\' }}</span>\n\t\t\t\t\t\t<small>{{ formatDate(op.updatedAt || op.createdAt) }}</small>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</section>\n\t}\n</section>\n',styles:['.aicoder-workbench{--aicoder-accent: #2f7d73;--aicoder-accent-dark: #155e57;--aicoder-border: #d8e2ec;--aicoder-ink: #1f2933;--aicoder-muted: #64748b;--aicoder-soft: #f5f8fb;display:flex;flex-direction:column;gap:1rem;color:var(--aicoder-ink)}.aicoder-workbench__header,.aicoder-workbench__intro,.aicoder-workbench__panel-head,.aicoder-workbench__detail-head,.aicoder-workbench__actions{display:flex;align-items:center;justify-content:space-between;gap:1rem}.aicoder-workbench__header p,.aicoder-workbench__eyebrow{margin:0 0 .25rem;color:var(--aicoder-accent-dark);font-size:.75rem;font-weight:800;letter-spacing:0;text-transform:uppercase}.aicoder-workbench__header h2,.aicoder-workbench__intro h3,.aicoder-workbench__panel h3{margin:0;font-weight:800;line-height:1.2}.aicoder-workbench__header h2{font-size:1.35rem}.aicoder-workbench__intro{border:1px solid var(--aicoder-border);border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__intro h3{font-size:1.25rem}.aicoder-workbench__intro p:not(.aicoder-workbench__eyebrow),.aicoder-workbench__blank p,.aicoder-workbench__empty{margin:.35rem 0 0;color:var(--aicoder-muted);line-height:1.45}.aicoder-workbench__layout{display:grid;grid-template-columns:minmax(260px,.75fr) minmax(320px,1.35fr);grid-template-areas:"compose detail" "list detail";gap:1rem;align-items:start}.aicoder-workbench__panel{min-width:0;border:1px solid var(--aicoder-border);border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__panel--compose{grid-area:compose}.aicoder-workbench__panel--list{grid-area:list}.aicoder-workbench__panel--detail{grid-area:detail}.aicoder-workbench__panel--updates{display:grid;gap:.75rem}.aicoder-workbench__panel-head span,.aicoder-workbench__status{display:inline-flex;align-items:center;border-radius:999px;background:#e7f4f0;color:var(--aicoder-accent-dark);font-size:.78rem;font-weight:800;padding:.25rem .6rem}.aicoder-workbench__next-step,.aicoder-workbench__thinking{display:flex;align-items:center;gap:.65rem;border:1px solid #dbe7ef;border-radius:8px;background:#f7fafc;color:var(--aicoder-muted);margin-top:1rem;padding:.85rem}.aicoder-workbench__next-step p,.aicoder-workbench__thinking p{margin:0;line-height:1.4}.aicoder-workbench__spinner{display:inline-block;width:1rem;height:1rem;border:2px solid #bfd2df;border-top-color:var(--aicoder-accent);border-radius:999px;flex:0 0 auto;animation:aicoder-workbench-spin .85s linear infinite}.aicoder-workbench__input,.aicoder-workbench__textarea{width:100%;border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:var(--aicoder-ink);padding:.7rem .8rem}.aicoder-workbench__textarea{resize:vertical}.aicoder-workbench__input,.aicoder-workbench__textarea{margin:.75rem 0}.aicoder-workbench__ghost,.aicoder-workbench__primary,.aicoder-workbench__danger,.aicoder-workbench__actions button{border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:var(--aicoder-ink);cursor:pointer;font:inherit;font-weight:800;min-height:40px;padding:.6rem .85rem;text-decoration:none}.aicoder-workbench__primary{border-color:var(--aicoder-accent);background:var(--aicoder-accent);color:#fff}.aicoder-workbench__danger{border-color:#fecaca;color:#991b1b}.aicoder-workbench button:disabled{cursor:not-allowed;opacity:.55}.aicoder-workbench__chat-list,.aicoder-workbench__history,.aicoder-workbench__updates{display:flex;flex-direction:column;gap:.5rem}.aicoder-workbench__chat-list,.aicoder-workbench__history{max-height:520px;overflow:auto}.aicoder-workbench__chat,.aicoder-workbench__message,.aicoder-workbench__update{display:flex;flex-direction:column;align-items:flex-start;gap:.25rem;width:100%;border:1px solid #e2e8f0;border-radius:7px;background:var(--aicoder-soft);padding:.8rem;text-align:left}.aicoder-workbench__message{background:#fff}.aicoder-workbench__message--user{border-color:#c9d8e6;background:#f8fafc}.aicoder-workbench__message--assistant{border-color:#b7ded3;background:#f4fbf9}.aicoder-workbench__message--system{background:#fbfdff}.aicoder-workbench__message.is-active{border-color:var(--aicoder-accent)}.aicoder-workbench__message-meta{display:flex;align-items:center;justify-content:space-between;gap:.75rem;width:100%;color:var(--aicoder-muted);font-size:.8rem;line-height:1.35}.aicoder-workbench__message-meta strong{color:var(--aicoder-ink);font-size:.82rem}.aicoder-workbench__chat{cursor:pointer}.aicoder-workbench__chat.is-active{border-color:var(--aicoder-accent);background:#ecfdf5}.aicoder-workbench__chat span,.aicoder-workbench__chat small,.aicoder-workbench__message span,.aicoder-workbench__update span,.aicoder-workbench__update small{color:var(--aicoder-muted);font-size:.82rem;line-height:1.35}.aicoder-workbench__message p{margin:0;white-space:pre-wrap;overflow-wrap:anywhere}.aicoder-workbench__history{min-height:260px;margin:.75rem 0}.aicoder-workbench__actions{flex-wrap:wrap;justify-content:flex-start;margin-top:1rem}.aicoder-workbench__reply{display:block;margin-top:1rem}.aicoder-workbench__reply span{display:block;color:var(--aicoder-muted);font-size:.78rem;font-weight:800;text-transform:uppercase}.aicoder-workbench__blank{display:grid;min-height:280px;place-content:center;text-align:center}.aicoder-workbench__notice{border-radius:6px;padding:.75rem .9rem;font-weight:800}.aicoder-workbench__notice--error{border:1px solid #fecaca;background:#fff1f2;color:#991b1b}.aicoder-workbench__notice--success{border:1px solid #bbf7d0;background:#f0fdf4;color:#166534}@keyframes aicoder-workbench-spin{to{transform:rotate(360deg)}}.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:minmax(240px,.7fr) minmax(320px,1.3fr)}@media(max-width:980px){.aicoder-workbench__layout,.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:1fr;grid-template-areas:"compose" "list" "detail"}.aicoder-workbench__header,.aicoder-workbench__intro,.aicoder-workbench__detail-head{align-items:stretch;flex-direction:column}.aicoder-workbench__actions button,.aicoder-workbench__primary,.aicoder-workbench__ghost,.aicoder-workbench__danger{width:100%}}\n']}]}],ctorParameters:()=>[{type:AICoderWorkbenchApiService}],propDecorators:{appId:[{type:Input}],appName:[{type:Input}],apiBase:[{type:Input}],appToken:[{type:Input}],defaultTab:[{type:Input}],compact:[{type:Input}],showHeader:[{type:Input}],autoRefresh:[{type:Input}]}});class AICoderDashboardModule{static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,deps:[],target:i0.ɵɵFactoryTarget.NgModule});static"ɵmod"=i0.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,declarations:[AICoderDashboardComponent,AICoderWorkbenchComponent],imports:[CommonModule,FormsModule,HttpClientModule],exports:[AICoderDashboardComponent,AICoderWorkbenchComponent]});static"ɵinj"=i0.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,imports:[CommonModule,FormsModule,HttpClientModule]})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,decorators:[{type:NgModule,args:[{imports:[CommonModule,FormsModule,HttpClientModule],declarations:[AICoderDashboardComponent,AICoderWorkbenchComponent],exports:[AICoderDashboardComponent,AICoderWorkbenchComponent]}]}]});export{AICoderDashboardComponent,AICoderDashboardModule,AICoderDashboardService,AICoderWorkbenchApiService,AICoderWorkbenchComponent};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@resolveio/client-lib-aicoder-dashboard",
3
- "version": "21.0.4",
3
+ "version": "21.0.6",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^21.0.0",
6
6
  "@angular/core": "^21.0.0",
@@ -9,7 +9,7 @@ import * as i4 from '@angular/forms';
9
9
 
10
10
  type AICoderDashboardTab = 'overview' | 'database' | 'infrastructure' | 'code_changes' | 'subscription';
11
11
  type AICoderTier = 'small' | 'medium' | 'large';
12
- type AICoderWorkbenchTab = 'cases' | 'chat' | 'qa' | 'deploy' | 'git';
12
+ type AICoderWorkbenchTab = 'chats' | 'cases' | 'chat' | 'qa' | 'deploy' | 'git';
13
13
  interface AICoderTierOption {
14
14
  id: AICoderTier;
15
15
  label: string;
@@ -58,6 +58,7 @@ interface AICoderDashboardSummary {
58
58
  interface AICoderWorkbenchJob {
59
59
  _id: string;
60
60
  title?: string;
61
+ description?: string;
61
62
  status?: string;
62
63
  stage?: string;
63
64
  phase?: string;
@@ -66,13 +67,34 @@ interface AICoderWorkbenchJob {
66
67
  updatedAt?: string;
67
68
  pr_url?: string;
68
69
  pull_request_url?: string;
70
+ pullRequestStatus?: string;
69
71
  branch?: string;
70
72
  github_branch?: string;
71
73
  summary?: string;
74
+ responseSummary?: string;
72
75
  last_message?: string;
76
+ conversation?: AICoderWorkbenchConversationEntry[];
77
+ runPolicy?: {
78
+ mode?: string;
79
+ questionOnly?: boolean;
80
+ [key: string]: any;
81
+ };
82
+ artifacts?: {
83
+ modifiedFiles?: Record<string, string>;
84
+ diffs?: Record<string, string>;
85
+ agentNotes?: Record<string, string>;
86
+ };
87
+ }
88
+ interface AICoderWorkbenchConversationEntry {
89
+ role?: 'user' | 'assistant' | string;
90
+ _id?: string;
91
+ message?: string;
92
+ timestamp?: string;
93
+ createdAt?: string;
73
94
  }
74
95
  interface AICoderWorkbenchLog {
75
96
  _id?: string;
97
+ entry?: string;
76
98
  message?: string;
77
99
  level?: string;
78
100
  stage?: string;
@@ -153,6 +175,7 @@ declare class AICoderWorkbenchApiService {
153
175
  get<T>(apiBase: string, path: string, appToken?: string): Observable<T>;
154
176
  post<T>(apiBase: string, path: string, body?: any, appToken?: string): Observable<T>;
155
177
  patch<T>(apiBase: string, path: string, body?: any, appToken?: string): Observable<T>;
178
+ delete<T>(apiBase: string, path: string, appToken?: string): Observable<T>;
156
179
  resolveDefaultApiBase(): string;
157
180
  private authHeaders;
158
181
  private ensureSessionToken;
@@ -163,6 +186,14 @@ declare class AICoderWorkbenchApiService {
163
186
  static ɵprov: i0.ɵɵInjectableDeclaration<AICoderWorkbenchApiService>;
164
187
  }
165
188
 
189
+ interface AICoderWorkbenchTimelineItem {
190
+ id: string;
191
+ role: 'user' | 'assistant' | 'system';
192
+ label: string;
193
+ message: string;
194
+ date?: string;
195
+ active?: boolean;
196
+ }
166
197
  declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy {
167
198
  private readonly api;
168
199
  appId: string;
@@ -176,7 +207,7 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
176
207
  activeTab: AICoderWorkbenchTab;
177
208
  jobs: AICoderWorkbenchJob[];
178
209
  selectedJob: AICoderWorkbenchJob | null;
179
- jobLogs: AICoderWorkbenchLog[];
210
+ jobLogs: AICoderWorkbenchTimelineItem[];
180
211
  deployOperations: AICoderWorkbenchDeployOperation[];
181
212
  deployJobs: AICoderWorkbenchDeployJob[];
182
213
  gitStatus: AICoderWorkbenchGitStatus | null;
@@ -195,10 +226,6 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
195
226
  qaFocus: string;
196
227
  riskNotes: string;
197
228
  private refreshSub?;
198
- readonly tabs: Array<{
199
- id: AICoderWorkbenchTab;
200
- label: string;
201
- }>;
202
229
  constructor(api: AICoderWorkbenchApiService);
203
230
  ngOnInit(): void;
204
231
  ngOnChanges(changes: SimpleChanges): void;
@@ -211,6 +238,9 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
211
238
  approveSelectedJob(): void;
212
239
  createPullRequest(): void;
213
240
  publishSelectedJob(): void;
241
+ sendToTestSite(): void;
242
+ approveAndDeployLive(): void;
243
+ deleteSelectedChat(): void;
214
244
  queueDeploy(target: 'full' | 'client' | 'backend'): void;
215
245
  openPr(job?: AICoderWorkbenchJob | null): void;
216
246
  loadJobs(): void;
@@ -222,12 +252,41 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
222
252
  get normalizedAppToken(): string;
223
253
  get activeDeploys(): AICoderWorkbenchDeployOperation[];
224
254
  get recentDeploys(): AICoderWorkbenchDeployOperation[];
225
- isActiveJob(job: AICoderWorkbenchJob): boolean;
255
+ get visibleJobs(): AICoderWorkbenchJob[];
256
+ get hasSelectedJob(): boolean;
257
+ get selectedJobIsWorking(): boolean;
258
+ get selectedJobIsQuestionOnly(): boolean;
259
+ get selectedJobHasChanges(): boolean;
260
+ get selectedJobHasReview(): boolean;
261
+ get canSendSelectedJobToTestSite(): boolean;
262
+ get canApproveSelectedJobLive(): boolean;
263
+ get selectedJobNextStepMessage(): string;
264
+ isActiveJob(job: AICoderWorkbenchJob | null): boolean;
226
265
  resolvePrUrl(job: AICoderWorkbenchJob | null): string;
266
+ friendlyJobStatus(job: AICoderWorkbenchJob | null): string;
267
+ friendlyDeployStatus(op: AICoderWorkbenchDeployOperation): string;
227
268
  formatDate(value: any): string;
269
+ private buildTimeline;
270
+ private extractUserRequest;
271
+ private sanitizeUserFacingMessage;
272
+ private cleanUserFacingLine;
273
+ private isInternalOnlyMessage;
274
+ private isInternalLine;
275
+ private isInternalText;
276
+ private computeVisibleJobs;
277
+ private jobDisplayKey;
278
+ private hasJobChanges;
279
+ private hasJobChangesWithoutQuestionGuard;
280
+ private isQuestionOnlyJob;
281
+ private isLikelyQuestionChat;
282
+ private extractQuestionAnswerFromSummary;
283
+ private isReadOnlyQuestion;
284
+ private isFailedJob;
228
285
  private runJobAction;
229
286
  private mergeJob;
230
287
  private normalizeJob;
288
+ private normalizeTab;
289
+ private toTitle;
231
290
  private startAutoRefresh;
232
291
  private clearMessages;
233
292
  private handleError;
@@ -242,4 +301,4 @@ declare class AICoderDashboardModule {
242
301
  }
243
302
 
244
303
  export { AICoderDashboardComponent, AICoderDashboardModule, AICoderDashboardService, AICoderWorkbenchApiService, AICoderWorkbenchComponent };
245
- export type { AICoderDashboardSummary, AICoderDashboardTab, AICoderTier, AICoderTierOption, AICoderWorkbenchDeployJob, AICoderWorkbenchDeployOperation, AICoderWorkbenchGitStatus, AICoderWorkbenchJob, AICoderWorkbenchLog, AICoderWorkbenchTab };
304
+ export type { AICoderDashboardSummary, AICoderDashboardTab, AICoderTier, AICoderTierOption, AICoderWorkbenchConversationEntry, AICoderWorkbenchDeployJob, AICoderWorkbenchDeployOperation, AICoderWorkbenchGitStatus, AICoderWorkbenchJob, AICoderWorkbenchLog, AICoderWorkbenchTab };