@resolveio/client-lib-aicoder-dashboard 21.0.3 → 21.0.5

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";sessionRefreshSkewMs=6e4;defaultSessionTtlMs=144e5;sessionByApiBase=new Map;exchangeByApiBase=new Map;constructor(t,e){this.http=t,this.tokenManager=e}get(t,e){return this.authHeaders(t).pipe(switchMap(a=>this.http.get(this.buildUrl(t,e),{headers:a})))}post(t,e,a={}){return this.authHeaders(t).pipe(switchMap(o=>this.http.post(this.buildUrl(t,e),a,{headers:o})))}patch(t,e,a={}){return this.authHeaders(t).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){return 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}`}),s=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,s),s}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="";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}).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}).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}).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`).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`).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`).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`).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 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}`,{}).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",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}],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 o=new HttpHeaders({Authorization:`Bearer ${i}`}),s=this.http.post(this.buildUrl(e,"/api/ai-coder/auth/session"),{},{headers:o}).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,s),s}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.jobs.length&&this.selectJob(this.jobs[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 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?"AI is working on this now. The next action will appear when there is a finished change to review.":this.selectedJobHasReview?"A review is ready. Open it first, then approve it when the test site looks right.":this.selectedJobHasChanges?"AI has a finished change. Send it to the test site when you are ready.":this.isFailedJob(this.selectedJob)?"AI needs attention before this can continue. Add a reply with what you want it to try next.":"AI will keep this chat updated as it works.":""}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 o="assistant"===String(e?.role||"").toLowerCase()?"assistant":"user",s="user"===o?this.extractUserRequest(e?.message||""):String(e?.message||"");i({role:o,label:"user"===o?"You":"AI Answer",message:s,date:this.formatDate(e?.timestamp||e?.createdAt),active:!1}),0===r&&"user"===o&&this.isActiveJob(t)&&i({role:"assistant",label:a?"AI Answer":"AI",message:a?"I am checking this and will answer without changing anything.":"I am working on this now. I will inspect the app, make the change if needed, test it, and tell you when it is ready.",date:"",active:!0})});const o=this.extractQuestionAnswerFromSummary(t);if(o&&i({role:"assistant",label:"AI Answer",message:o,date:this.formatDate(t?.updatedAt),active:!1}),!a){this.buildFriendlyProgressMessages(e).forEach(t=>i(t))}return!r.length&&this.isActiveJob(t)&&i({role:"assistant",label:a?"AI Answer":"AI",message:a?"I am reading this and will answer shortly.":"I am getting started and will post progress here.",date:"",active:!0}),r}buildFriendlyProgressMessages(t){const e=[],r=new Set;return(t||[]).forEach(t=>{const a=this.parseLogEntry(t),i=this.toFriendlyProgressMessage(a.message);i&&!r.has(i)&&(r.add(i),e.push({role:"system",label:"Progress",message:i,date:this.formatDate(a.date),active:!1}))}),e.slice(-6)}parseLogEntry(t){const e="string"==typeof t?t:String(t?.entry||t?.message||t?.stage||t?.level||""),r="string"==typeof t?"":String(t?.createdAt||t?.date_created||""),a=e.match(/^(\d{4}-\d{2}-\d{2}T[^ ]+)\s+-\s+([\s\S]*)$/);return a?{date:a[1],message:a[2]}:{date:r,message:e}}toFriendlyProgressMessage(t){const e=String(t||"").trim(),r=e.toLowerCase();return e?r.includes("user prompt:")?"":r.includes("job created")||r.includes("runner start")||r.includes("claimed execution affinity")?"I started the work.":r.includes("[workspace]")||r.includes("workspace prep")||r.includes("cloning repo")||r.includes("updating reference repo")||r.includes("staged intake")?"I am setting up a safe workspace for this app.":r.includes("npm install")||r.includes("dependency")||r.includes("cache")?"I am preparing the app so I can inspect and test it.":r.includes("discovery")?"I am figuring out what needs to change.":r.includes("planning")?"I am planning the update.":r.includes("execution")||r.includes("implement")?"I am making the update.":r.includes("lint")||r.includes("build")||r.includes("test")?"I am checking that the app still works.":r.includes("pull request")||r.includes("review")?"I am preparing the review for this change.":r.includes("complete")||r.includes("done")?"The work is ready for review.":"":""}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){return 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(/\n{3,}/g,"\n\n").trim()}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>{{ jobs.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 jobs; 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>{{ jobs.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 jobs; 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.3",
3
+ "version": "21.0.5",
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;
@@ -36,6 +36,10 @@ interface AICoderDashboardSummary {
36
36
  code_changes: {
37
37
  release_notes: string;
38
38
  last_deployed_at: string;
39
+ app_id?: string;
40
+ aicoder_url?: string;
41
+ api_base_url?: string;
42
+ dashboard_token?: string;
39
43
  recent_updates: string[];
40
44
  };
41
45
  subscription?: {
@@ -54,6 +58,7 @@ interface AICoderDashboardSummary {
54
58
  interface AICoderWorkbenchJob {
55
59
  _id: string;
56
60
  title?: string;
61
+ description?: string;
57
62
  status?: string;
58
63
  stage?: string;
59
64
  phase?: string;
@@ -62,13 +67,34 @@ interface AICoderWorkbenchJob {
62
67
  updatedAt?: string;
63
68
  pr_url?: string;
64
69
  pull_request_url?: string;
70
+ pullRequestStatus?: string;
65
71
  branch?: string;
66
72
  github_branch?: string;
67
73
  summary?: string;
74
+ responseSummary?: string;
68
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;
69
94
  }
70
95
  interface AICoderWorkbenchLog {
71
96
  _id?: string;
97
+ entry?: string;
72
98
  message?: string;
73
99
  level?: string;
74
100
  stage?: string;
@@ -140,14 +166,16 @@ declare class AICoderWorkbenchApiService {
140
166
  private readonly http;
141
167
  private readonly tokenManager;
142
168
  private readonly sessionHeaderName;
169
+ private readonly appTokenHeaderName;
143
170
  private readonly sessionRefreshSkewMs;
144
171
  private readonly defaultSessionTtlMs;
145
172
  private sessionByApiBase;
146
173
  private exchangeByApiBase;
147
174
  constructor(http: HttpClient, tokenManager: TokenManagerService);
148
- get<T>(apiBase: string, path: string): Observable<T>;
149
- post<T>(apiBase: string, path: string, body?: any): Observable<T>;
150
- patch<T>(apiBase: string, path: string, body?: any): Observable<T>;
175
+ get<T>(apiBase: string, path: string, appToken?: string): Observable<T>;
176
+ post<T>(apiBase: string, path: string, body?: any, appToken?: string): Observable<T>;
177
+ patch<T>(apiBase: string, path: string, body?: any, appToken?: string): Observable<T>;
178
+ delete<T>(apiBase: string, path: string, appToken?: string): Observable<T>;
151
179
  resolveDefaultApiBase(): string;
152
180
  private authHeaders;
153
181
  private ensureSessionToken;
@@ -158,11 +186,20 @@ declare class AICoderWorkbenchApiService {
158
186
  static ɵprov: i0.ɵɵInjectableDeclaration<AICoderWorkbenchApiService>;
159
187
  }
160
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
+ }
161
197
  declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy {
162
198
  private readonly api;
163
199
  appId: string;
164
200
  appName: string;
165
201
  apiBase: string;
202
+ appToken: string;
166
203
  defaultTab: AICoderWorkbenchTab;
167
204
  compact: boolean;
168
205
  showHeader: boolean;
@@ -170,7 +207,7 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
170
207
  activeTab: AICoderWorkbenchTab;
171
208
  jobs: AICoderWorkbenchJob[];
172
209
  selectedJob: AICoderWorkbenchJob | null;
173
- jobLogs: AICoderWorkbenchLog[];
210
+ jobLogs: AICoderWorkbenchTimelineItem[];
174
211
  deployOperations: AICoderWorkbenchDeployOperation[];
175
212
  deployJobs: AICoderWorkbenchDeployJob[];
176
213
  gitStatus: AICoderWorkbenchGitStatus | null;
@@ -189,10 +226,6 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
189
226
  qaFocus: string;
190
227
  riskNotes: string;
191
228
  private refreshSub?;
192
- readonly tabs: Array<{
193
- id: AICoderWorkbenchTab;
194
- label: string;
195
- }>;
196
229
  constructor(api: AICoderWorkbenchApiService);
197
230
  ngOnInit(): void;
198
231
  ngOnChanges(changes: SimpleChanges): void;
@@ -205,6 +238,9 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
205
238
  approveSelectedJob(): void;
206
239
  createPullRequest(): void;
207
240
  publishSelectedJob(): void;
241
+ sendToTestSite(): void;
242
+ approveAndDeployLive(): void;
243
+ deleteSelectedChat(): void;
208
244
  queueDeploy(target: 'full' | 'client' | 'backend'): void;
209
245
  openPr(job?: AICoderWorkbenchJob | null): void;
210
246
  loadJobs(): void;
@@ -213,19 +249,45 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
213
249
  loadGitStatus(): void;
214
250
  get normalizedAppId(): string;
215
251
  get selectedJobId(): string;
252
+ get normalizedAppToken(): string;
216
253
  get activeDeploys(): AICoderWorkbenchDeployOperation[];
217
254
  get recentDeploys(): AICoderWorkbenchDeployOperation[];
218
- isActiveJob(job: AICoderWorkbenchJob): boolean;
255
+ get hasSelectedJob(): boolean;
256
+ get selectedJobIsWorking(): boolean;
257
+ get selectedJobIsQuestionOnly(): boolean;
258
+ get selectedJobHasChanges(): boolean;
259
+ get selectedJobHasReview(): boolean;
260
+ get canSendSelectedJobToTestSite(): boolean;
261
+ get canApproveSelectedJobLive(): boolean;
262
+ get selectedJobNextStepMessage(): string;
263
+ isActiveJob(job: AICoderWorkbenchJob | null): boolean;
219
264
  resolvePrUrl(job: AICoderWorkbenchJob | null): string;
265
+ friendlyJobStatus(job: AICoderWorkbenchJob | null): string;
266
+ friendlyDeployStatus(op: AICoderWorkbenchDeployOperation): string;
220
267
  formatDate(value: any): string;
268
+ private buildTimeline;
269
+ private buildFriendlyProgressMessages;
270
+ private parseLogEntry;
271
+ private toFriendlyProgressMessage;
272
+ private extractUserRequest;
273
+ private sanitizeUserFacingMessage;
274
+ private hasJobChanges;
275
+ private hasJobChangesWithoutQuestionGuard;
276
+ private isQuestionOnlyJob;
277
+ private isLikelyQuestionChat;
278
+ private extractQuestionAnswerFromSummary;
279
+ private isReadOnlyQuestion;
280
+ private isFailedJob;
221
281
  private runJobAction;
222
282
  private mergeJob;
223
283
  private normalizeJob;
284
+ private normalizeTab;
285
+ private toTitle;
224
286
  private startAutoRefresh;
225
287
  private clearMessages;
226
288
  private handleError;
227
289
  static ɵfac: i0.ɵɵFactoryDeclaration<AICoderWorkbenchComponent, never>;
228
- static ɵcmp: i0.ɵɵComponentDeclaration<AICoderWorkbenchComponent, "resolveio-client-lib-aicoder-workbench", never, { "appId": { "alias": "appId"; "required": false; }; "appName": { "alias": "appName"; "required": false; }; "apiBase": { "alias": "apiBase"; "required": false; }; "defaultTab": { "alias": "defaultTab"; "required": false; }; "compact": { "alias": "compact"; "required": false; }; "showHeader": { "alias": "showHeader"; "required": false; }; "autoRefresh": { "alias": "autoRefresh"; "required": false; }; }, {}, never, never, false, never>;
290
+ static ɵcmp: i0.ɵɵComponentDeclaration<AICoderWorkbenchComponent, "resolveio-client-lib-aicoder-workbench", never, { "appId": { "alias": "appId"; "required": false; }; "appName": { "alias": "appName"; "required": false; }; "apiBase": { "alias": "apiBase"; "required": false; }; "appToken": { "alias": "appToken"; "required": false; }; "defaultTab": { "alias": "defaultTab"; "required": false; }; "compact": { "alias": "compact"; "required": false; }; "showHeader": { "alias": "showHeader"; "required": false; }; "autoRefresh": { "alias": "autoRefresh"; "required": false; }; }, {}, never, never, false, never>;
229
291
  }
230
292
 
231
293
  declare class AICoderDashboardModule {
@@ -235,4 +297,4 @@ declare class AICoderDashboardModule {
235
297
  }
236
298
 
237
299
  export { AICoderDashboardComponent, AICoderDashboardModule, AICoderDashboardService, AICoderWorkbenchApiService, AICoderWorkbenchComponent };
238
- export type { AICoderDashboardSummary, AICoderDashboardTab, AICoderTier, AICoderTierOption, AICoderWorkbenchDeployJob, AICoderWorkbenchDeployOperation, AICoderWorkbenchGitStatus, AICoderWorkbenchJob, AICoderWorkbenchLog, AICoderWorkbenchTab };
300
+ export type { AICoderDashboardSummary, AICoderDashboardTab, AICoderTier, AICoderTierOption, AICoderWorkbenchConversationEntry, AICoderWorkbenchDeployJob, AICoderWorkbenchDeployOperation, AICoderWorkbenchGitStatus, AICoderWorkbenchJob, AICoderWorkbenchLog, AICoderWorkbenchTab };