@resolveio/client-lib-aicoder-dashboard 21.0.3 → 21.0.4
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,a=""){return this.authHeaders(t,a).pipe(switchMap(a=>this.http.get(this.buildUrl(t,e),{headers:a})))}post(t,e,a={},o=""){return this.authHeaders(t,o).pipe(switchMap(o=>this.http.post(this.buildUrl(t,e),a,{headers:o})))}patch(t,e,a={},o=""){return this.authHeaders(t,o).pipe(switchMap(o=>this.http.patch(this.buildUrl(t,e),a,{headers:o})))}resolveDefaultApiBase(){const t=String(window?.location?.hostname||"").trim().toLowerCase();return"aicoder.resolveio.com"===t||t.endsWith(".aicoder.resolveio.com")?"https://backend.aicoder.resolveio.com":""}authHeaders(t,e=""){const a=String(e||"").trim();return a?new Observable(t=>{t.next(new HttpHeaders({[this.appTokenHeaderName]:a})),t.complete()}):this.ensureSessionToken(t).pipe(map(t=>new HttpHeaders({[this.sessionHeaderName]:t})))}ensureSessionToken(t){const e=this.normalizeApiBase(t),a=this.sessionByApiBase.get(e);if(a&&a.token&&a.expiresAtMs>Date.now()+this.sessionRefreshSkewMs)return new Observable(t=>{t.next(a.token),t.complete()});const o=this.exchangeByApiBase.get(e);if(o)return o;const r=String(this.tokenManager.getToken("accessToken")||"").trim();if(!r)return throwError(()=>new Error("Sign in before using AICoder."));const i=new HttpHeaders({Authorization:`Bearer ${r}`}),n=this.http.post(this.buildUrl(e,"/api/ai-coder/auth/session"),{},{headers:i}).pipe(map(t=>{const a=String(t?.token||"").trim();if(!a)throw new Error("AICoder session response did not include a token.");return this.sessionByApiBase.set(e,{token:a,expiresAtMs:this.resolveSessionExpiresAtMs(t)}),a}),tap({error:()=>this.sessionByApiBase.delete(e),complete:()=>this.exchangeByApiBase.delete(e)}),shareReplay(1));return this.exchangeByApiBase.set(e,n),n}resolveSessionExpiresAtMs(t){const e=t?.expires_at?new Date(t.expires_at).getTime():0;if(Number.isFinite(e)&&e>Date.now())return e;const a=1e3*Number(t?.expires_in_seconds||0);return Date.now()+(a>0?a:this.defaultSessionTtlMs)}buildUrl(t,e){const a=this.normalizeApiBase(t),o=String(e||"").trim();return a?`${a}${o.startsWith("/")?o:`/${o}`}`:o}normalizeApiBase(t){return String(t||"").trim().replace(/\/+$/,"")}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,deps:[{token:i1.HttpClient},{token:i2.TokenManagerService}],target:i0.ɵɵFactoryTarget.Injectable});static"ɵprov"=i0.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,providedIn:"root"})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,decorators:[{type:Injectable,args:[{providedIn:"root"}]}],ctorParameters:()=>[{type:i1.HttpClient},{type:i2.TokenManagerService}]});class AICoderWorkbenchComponent{api;appId="";appName="AICoder App";apiBase="";appToken="";defaultTab="cases";compact=!1;showHeader=!0;autoRefresh=!0;activeTab="cases";jobs=[];selectedJob=null;jobLogs=[];deployOperations=[];deployJobs=[];gitStatus=null;isLoading=!1;isSubmitting=!1;errorMessage="";successMessage="";requestTitle="";requestMessage="";chatMessage="";workflowMode="qa_playbook";requestKind="bug_or_feature";dataChangePolicy="review_before_write";deploymentTarget="branch_pr";evidenceNotes="";qaFocus="";riskNotes="";refreshSub;tabs=[{id:"cases",label:"Cases"},{id:"chat",label:"Chat + PR"},{id:"qa",label:"QA Playbook"},{id:"deploy",label:"Deploy"},{id:"git",label:"Git"}];constructor(t){this.api=t}ngOnInit(){this.activeTab=this.defaultTab||"cases",this.apiBase||(this.apiBase=this.api.resolveDefaultApiBase()),this.refresh(),this.startAutoRefresh()}ngOnChanges(t){t.defaultTab&&this.defaultTab&&(this.activeTab=this.defaultTab),(t.appId&&!t.appId.firstChange||t.apiBase&&!t.apiBase.firstChange)&&this.refresh()}ngOnDestroy(){this.refreshSub?.unsubscribe()}setTab(t){this.activeTab=t,"git"!==t||this.gitStatus||this.loadGitStatus(),"deploy"!==t||this.deployOperations.length||this.loadDeployHistory()}refresh(){this.normalizedAppId?(this.loadJobs(),this.loadDeployHistory(),this.loadGitStatus()):this.errorMessage="AICoder app id is missing."}selectJob(t){this.selectedJob=t,this.loadJobLogs(t._id)}submitRequest(){if(!this.normalizedAppId||this.isSubmitting)return;const t=String(this.requestMessage||"").trim();t?(this.isSubmitting=!0,this.clearMessages(),this.api.post(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/requests`,{title:String(this.requestTitle||"").trim(),message:t,workflow_mode:this.workflowMode,request_kind:this.requestKind,data_change_policy:this.dataChangePolicy,deployment_target:this.deploymentTarget,evidence_notes:this.evidenceNotes,qa_focus:this.qaFocus,risk_notes:this.riskNotes},this.normalizedAppToken).subscribe({next:t=>{this.successMessage="AICoder case queued.",this.requestTitle="",this.requestMessage="",t?.job&&(this.jobs=[t.job,...this.jobs.filter(e=>e._id!==t.job._id)],this.selectJob(t.job))},error:t=>this.handleError(t,"Could not queue the AICoder case."),complete:()=>this.isSubmitting=!1})):this.errorMessage="Describe the requested change before queueing it."}sendChatMessage(){const t=this.selectedJobId,e=String(this.chatMessage||"").trim();t&&e&&!this.isSubmitting&&(this.isSubmitting=!0,this.clearMessages(),this.api.post(this.apiBase,`/api/ai-coder/jobs/${t}/prompt`,{message:e},this.normalizedAppToken).subscribe({next:t=>{this.successMessage="Message sent to AICoder.",this.chatMessage="",t?.job&&(this.mergeJob(t.job),this.selectJob(t.job))},error:t=>this.handleError(t,"Could not send the message."),complete:()=>this.isSubmitting=!1}))}approveSelectedJob(){this.runJobAction("approve","Case approved.")}createPullRequest(){this.runJobAction("create-pr","Pull request requested.")}publishSelectedJob(){this.runJobAction("publish","Publish requested.")}queueDeploy(t){this.normalizedAppId&&!this.isSubmitting&&(this.isSubmitting=!0,this.clearMessages(),this.api.post(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/deploy`,{deploy_client:"full"===t||"client"===t,deploy_backend:"full"===t||"backend"===t,clear_destination:!0,invalidate:!0},this.normalizedAppToken).subscribe({next:()=>{this.successMessage="full"===t?"Full deploy queued.":("client"===t?"Client":"Server")+" deploy queued.",this.loadDeployHistory()},error:t=>this.handleError(t,"Could not queue deploy."),complete:()=>this.isSubmitting=!1}))}openPr(t=this.selectedJob){const e=this.resolvePrUrl(t);e&&window.open(e,"_blank","noopener")}loadJobs(){this.normalizedAppId&&(this.isLoading=!0,this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/jobs`,this.normalizedAppToken).subscribe({next:t=>{this.jobs=(t?.jobs||[]).map(t=>this.normalizeJob(t)),!this.selectedJob&&this.jobs.length&&this.selectJob(this.jobs[0])},error:t=>this.handleError(t,"Could not load AICoder cases."),complete:()=>this.isLoading=!1}))}loadJobLogs(t){const e=String(t||"").trim();e?this.api.get(this.apiBase,`/api/ai-coder/jobs/${e}/logs?limit=80`,this.normalizedAppToken).subscribe({next:t=>this.jobLogs=t?.logs||[],error:t=>this.handleError(t,"Could not load case logs.")}):this.jobLogs=[]}loadDeployHistory(){this.normalizedAppId&&this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/deploy/history`,this.normalizedAppToken).subscribe({next:t=>{this.deployOperations=t?.operations||[],this.deployJobs=t?.jobs||t?.deploy_jobs||[]},error:t=>this.handleError(t,"Could not load deploy history.")})}loadGitStatus(){this.normalizedAppId&&this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/git`,this.normalizedAppToken).subscribe({next:t=>this.gitStatus=t?.status||t||null,error:t=>this.handleError(t,"Could not load git status.")})}get normalizedAppId(){return String(this.appId||"").trim()}get selectedJobId(){return String(this.selectedJob?._id||"").trim()}get normalizedAppToken(){return String(this.appToken||"").trim()}get activeDeploys(){return this.deployOperations.filter(t=>"In Progress"===String(t?.status||"").trim())}get recentDeploys(){return this.deployOperations.filter(t=>"In Progress"!==String(t?.status||"").trim()).slice(0,6)}isActiveJob(t){const e=String(t?.status||"").toLowerCase();return"queued"===e||"in progress"===e||"running"===e||"pending"===e}resolvePrUrl(t){return String(t?.pr_url||t?.pull_request_url||"").trim()}formatDate(t){if(!t)return"Not recorded";const e=t instanceof Date?t:new Date(t);return Number.isNaN(e.getTime())?String(t):e.toLocaleString()}runJobAction(t,e){const a=this.selectedJobId;a&&!this.isSubmitting&&(this.isSubmitting=!0,this.clearMessages(),this.api.post(this.apiBase,`/api/ai-coder/jobs/${a}/${t}`,{},this.normalizedAppToken).subscribe({next:t=>{this.successMessage=e,t?.job&&(this.mergeJob(t.job),this.selectJob(t.job))},error:e=>this.handleError(e,`Could not ${t.replace("-"," ")}.`),complete:()=>this.isSubmitting=!1}))}mergeJob(t){const e=this.normalizeJob(t);this.jobs=[e,...this.jobs.filter(t=>t._id!==e._id)],this.selectedJob=e}normalizeJob(t){return{...t,_id:String(t?._id||"").trim(),title:String(t?.title||"AICoder Case").trim(),status:String(t?.status||"").trim()||"Queued"}}startAutoRefresh(){this.refreshSub?.unsubscribe(),this.autoRefresh&&(this.refreshSub=interval(15e3).subscribe(()=>{(this.jobs.some(t=>this.isActiveJob(t))||this.activeDeploys.length)&&this.refresh()}))}clearMessages(){this.errorMessage="",this.successMessage=""}handleError(t,e){this.errorMessage=String(t?.error?.message||t?.message||e),this.successMessage="",this.isSubmitting=!1,this.isLoading=!1}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchComponent,deps:[{token:AICoderWorkbenchApiService}],target:i0.ɵɵFactoryTarget.Component});static"ɵcmp"=i0.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"21.1.2",type:AICoderWorkbenchComponent,isStandalone:!1,selector:"resolveio-client-lib-aicoder-workbench",inputs:{appId:"appId",appName:"appName",apiBase:"apiBase",appToken:"appToken",defaultTab:"defaultTab",compact:"compact",showHeader:"showHeader",autoRefresh:"autoRefresh"},usesOnChanges:!0,ngImport:i0,template:'<section class="aicoder-workbench" [class.aicoder-workbench--compact]="compact">\n\t@if (showHeader) {\n\t\t<header class="aicoder-workbench__header">\n\t\t\t<div>\n\t\t\t\t<p>AICoder</p>\n\t\t\t\t<h2>{{ appName || \'AICoder App\' }}</h2>\n\t\t\t</div>\n\t\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh</button>\n\t\t</header>\n\t}\n\n\t<nav class="aicoder-workbench__tabs" aria-label="AICoder workbench tabs">\n\t\t@for (tab of tabs; track tab.id) {\n\t\t\t<button type="button" [class.is-active]="activeTab === tab.id" (click)="setTab(tab.id)">\n\t\t\t\t{{ tab.label }}\n\t\t\t</button>\n\t\t}\n\t</nav>\n\n\t@if (errorMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--error">{{ errorMessage }}</div>\n\t}\n\t@if (successMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--success">{{ successMessage }}</div>\n\t}\n\n\t@if (activeTab === \'cases\') {\n\t\t<div class="aicoder-workbench__layout">\n\t\t\t<section class="aicoder-workbench__panel">\n\t\t\t\t<h3>New Case</h3>\n\t\t\t\t<input class="aicoder-workbench__input" [(ngModel)]="requestTitle" placeholder="Case title" />\n\t\t\t\t<textarea class="aicoder-workbench__textarea" rows="7" [(ngModel)]="requestMessage" placeholder="Describe what should change, what evidence to inspect, and what done looks like."></textarea>\n\t\t\t\t<div class="aicoder-workbench__grid">\n\t\t\t\t\t<label>\n\t\t\t\t\t\t<span>Workflow</span>\n\t\t\t\t\t\t<select [(ngModel)]="workflowMode">\n\t\t\t\t\t\t\t<option value="qa_playbook">QA playbook</option>\n\t\t\t\t\t\t\t<option value="implementation">Implementation</option>\n\t\t\t\t\t\t\t<option value="data_change">Data change</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t</label>\n\t\t\t\t\t<label>\n\t\t\t\t\t\t<span>Deploy target</span>\n\t\t\t\t\t\t<select [(ngModel)]="deploymentTarget">\n\t\t\t\t\t\t\t<option value="branch_pr">Branch + PR</option>\n\t\t\t\t\t\t\t<option value="test_server">Test server</option>\n\t\t\t\t\t\t\t<option value="live_after_approval">Live after approval</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="submitRequest()" [disabled]="isSubmitting || !requestMessage.trim()">Queue Case</button>\n\t\t\t</section>\n\n\t\t\t<section class="aicoder-workbench__panel">\n\t\t\t\t<h3>Cases</h3>\n\t\t\t\t@if (!jobs.length && !isLoading) {\n\t\t\t\t\t<p class="aicoder-workbench__muted">No cases yet.</p>\n\t\t\t\t}\n\t\t\t\t<div class="aicoder-workbench__case-list">\n\t\t\t\t\t@for (job of jobs; track job._id) {\n\t\t\t\t\t\t<button type="button" class="aicoder-workbench__case" [class.is-active]="selectedJob?._id === job._id" (click)="selectJob(job)">\n\t\t\t\t\t\t\t<strong>{{ job.title || \'AICoder Case\' }}</strong>\n\t\t\t\t\t\t\t<span>{{ job.status || \'Queued\' }}{{ job.stage ? \' - \' + job.stage : \'\' }}</span>\n\t\t\t\t\t\t\t<small>{{ formatDate(job.updatedAt || job.createdAt) }}</small>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t</section>\n\t\t</div>\n\t}\n\n\t@if (activeTab === \'chat\') {\n\t\t<div class="aicoder-workbench__layout">\n\t\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--wide">\n\t\t\t\t<h3>{{ selectedJob?.title || \'Select a case\' }}</h3>\n\t\t\t\t<div class="aicoder-workbench__toolbar">\n\t\t\t\t\t<button type="button" (click)="approveSelectedJob()" [disabled]="!selectedJobId || isSubmitting">Approve</button>\n\t\t\t\t\t<button type="button" (click)="createPullRequest()" [disabled]="!selectedJobId || isSubmitting">Create PR</button>\n\t\t\t\t\t<button type="button" (click)="publishSelectedJob()" [disabled]="!selectedJobId || isSubmitting">Publish</button>\n\t\t\t\t\t<button type="button" (click)="openPr()" [disabled]="!resolvePrUrl(selectedJob)">Open PR</button>\n\t\t\t\t</div>\n\t\t\t\t<div class="aicoder-workbench__log">\n\t\t\t\t\t@if (!jobLogs.length) {\n\t\t\t\t\t\t<p class="aicoder-workbench__muted">No log entries loaded.</p>\n\t\t\t\t\t}\n\t\t\t\t\t@for (log of jobLogs; track log._id || log.createdAt || log.message) {\n\t\t\t\t\t\t<div class="aicoder-workbench__log-row">\n\t\t\t\t\t\t\t<span>{{ formatDate(log.createdAt || log.date_created) }}</span>\n\t\t\t\t\t\t\t<p>{{ log.message || log.stage || log.level || \'Log entry\' }}</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t\t<textarea class="aicoder-workbench__textarea" rows="4" [(ngModel)]="chatMessage" placeholder="Send a follow-up to this case."></textarea>\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendChatMessage()" [disabled]="!selectedJobId || !chatMessage.trim() || isSubmitting">Send</button>\n\t\t\t</section>\n\t\t</div>\n\t}\n\n\t@if (activeTab === \'qa\') {\n\t\t<section class="aicoder-workbench__panel">\n\t\t\t<h3>QA Playbook</h3>\n\t\t\t<div class="aicoder-workbench__grid">\n\t\t\t\t<label>\n\t\t\t\t\t<span>Evidence notes</span>\n\t\t\t\t\t<textarea rows="5" [(ngModel)]="evidenceNotes"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<label>\n\t\t\t\t\t<span>QA focus</span>\n\t\t\t\t\t<textarea rows="5" [(ngModel)]="qaFocus"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<label>\n\t\t\t\t\t<span>Risk notes</span>\n\t\t\t\t\t<textarea rows="5" [(ngModel)]="riskNotes"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<label>\n\t\t\t\t\t<span>Data change policy</span>\n\t\t\t\t\t<select [(ngModel)]="dataChangePolicy">\n\t\t\t\t\t\t<option value="review_before_write">Review before write</option>\n\t\t\t\t\t\t<option value="safe_seed_only">Safe seed only</option>\n\t\t\t\t\t\t<option value="no_data_changes">No data changes</option>\n\t\t\t\t\t</select>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t</section>\n\t}\n\n\t@if (activeTab === \'deploy\') {\n\t\t<section class="aicoder-workbench__panel">\n\t\t\t<h3>Deploy</h3>\n\t\t\t<div class="aicoder-workbench__toolbar">\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="queueDeploy(\'full\')" [disabled]="isSubmitting">Full Deploy</button>\n\t\t\t\t<button type="button" (click)="queueDeploy(\'client\')" [disabled]="isSubmitting">Client</button>\n\t\t\t\t<button type="button" (click)="queueDeploy(\'backend\')" [disabled]="isSubmitting">Server</button>\n\t\t\t</div>\n\t\t\t<h4>Current</h4>\n\t\t\t@if (!activeDeploys.length) {\n\t\t\t\t<p class="aicoder-workbench__muted">No active deploys.</p>\n\t\t\t}\n\t\t\t@for (op of activeDeploys; track op.id || op.jobId) {\n\t\t\t\t<div class="aicoder-workbench__deploy-row">\n\t\t\t\t\t<strong>{{ op.type || \'Deploy\' }} - {{ op.status }}</strong>\n\t\t\t\t\t<span>{{ op.aws_build_current_phase || op.backend_deploy_step || op.message }}</span>\n\t\t\t\t</div>\n\t\t\t}\n\t\t\t<h4>Recent</h4>\n\t\t\t@for (op of recentDeploys; track op.id || op.jobId) {\n\t\t\t\t<div class="aicoder-workbench__deploy-row">\n\t\t\t\t\t<strong>{{ op.type || \'Deploy\' }} - {{ op.status }}</strong>\n\t\t\t\t\t<span>{{ op.message || op.backend_deploy_step || \'Deploy finished\' }}</span>\n\t\t\t\t\t<small>{{ formatDate(op.updatedAt || op.createdAt) }}</small>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</section>\n\t}\n\n\t@if (activeTab === \'git\') {\n\t\t<section class="aicoder-workbench__panel">\n\t\t\t<h3>Git</h3>\n\t\t\t<div class="aicoder-workbench__grid">\n\t\t\t\t<div><span>Repo</span><strong>{{ gitStatus?.repo || \'Not loaded\' }}</strong></div>\n\t\t\t\t<div><span>Main branch</span><strong>{{ gitStatus?.default_branch || \'main\' }}</strong></div>\n\t\t\t\t<div><span>Pushes</span><strong>{{ gitStatus?.push_count ?? \'N/A\' }}</strong></div>\n\t\t\t\t<div><span>Last push</span><strong>{{ formatDate(gitStatus?.last_push) }}</strong></div>\n\t\t\t</div>\n\t\t\t<div class="aicoder-workbench__toolbar">\n\t\t\t\t@if (gitStatus?.repo_url) {\n\t\t\t\t\t<a [href]="gitStatus?.repo_url" target="_blank" rel="noopener">Open Repo</a>\n\t\t\t\t}\n\t\t\t\t<button type="button" (click)="loadGitStatus()">Refresh Git</button>\n\t\t\t</div>\n\t\t</section>\n\t}\n</section>\n',styles:[".aicoder-workbench{display:flex;flex-direction:column;gap:1rem;color:#1f2933}.aicoder-workbench__header{display:flex;align-items:center;justify-content:space-between;gap:1rem}.aicoder-workbench__header p{margin:0 0 .25rem;color:#64748b;font-size:.75rem;font-weight:800;letter-spacing:0;text-transform:uppercase}.aicoder-workbench__header h2{margin:0;font-size:1.35rem;font-weight:800}.aicoder-workbench__tabs,.aicoder-workbench__toolbar{display:flex;flex-wrap:wrap;gap:.5rem}.aicoder-workbench__tabs button,.aicoder-workbench__toolbar button,.aicoder-workbench__toolbar a,.aicoder-workbench__ghost,.aicoder-workbench__primary{border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:#1f2933;font-weight:700;padding:.55rem .8rem;text-decoration:none}.aicoder-workbench__tabs button.is-active,.aicoder-workbench__primary{border-color:#2f7d73;background:#2f7d73;color:#fff}.aicoder-workbench__tabs button:disabled,.aicoder-workbench__toolbar button:disabled,.aicoder-workbench__primary:disabled{cursor:not-allowed;opacity:.55}.aicoder-workbench__layout{display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,.7fr);gap:1rem}.aicoder-workbench__panel{border:1px solid #dbe3ec;border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__panel--wide{grid-column:1/-1}.aicoder-workbench__panel h3,.aicoder-workbench__panel h4{margin:0 0 .75rem;font-size:1rem;font-weight:800}.aicoder-workbench__panel h4{margin-top:1rem;color:#64748b;font-size:.8rem;text-transform:uppercase}.aicoder-workbench__input,.aicoder-workbench__textarea,.aicoder-workbench select,.aicoder-workbench textarea{width:100%;border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:#1f2933;padding:.65rem .75rem}.aicoder-workbench__textarea,.aicoder-workbench textarea{resize:vertical}.aicoder-workbench__input,.aicoder-workbench__textarea{margin-bottom:.75rem}.aicoder-workbench__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:.75rem;margin-bottom:.75rem}.aicoder-workbench__grid span,.aicoder-workbench__grid label span{display:block;margin-bottom:.3rem;color:#64748b;font-size:.75rem;font-weight:800;text-transform:uppercase}.aicoder-workbench__case-list,.aicoder-workbench__log{display:flex;flex-direction:column;gap:.5rem;max-height:460px;overflow:auto}.aicoder-workbench__case,.aicoder-workbench__deploy-row,.aicoder-workbench__log-row{display:flex;flex-direction:column;align-items:flex-start;gap:.2rem;width:100%;border:1px solid #e2e8f0;border-radius:7px;background:#f8fafc;padding:.75rem;text-align:left}.aicoder-workbench__case.is-active{border-color:#2f7d73;background:#ecfdf5}.aicoder-workbench__case span,.aicoder-workbench__case small,.aicoder-workbench__deploy-row span,.aicoder-workbench__deploy-row small,.aicoder-workbench__log-row span{color:#64748b;font-size:.82rem}.aicoder-workbench__log{min-height:220px;margin-bottom:.75rem}.aicoder-workbench__log-row p{margin:0}.aicoder-workbench__notice{border-radius:6px;padding:.75rem .9rem;font-weight:700}.aicoder-workbench__notice--error{border:1px solid #fecaca;background:#fff1f2;color:#991b1b}.aicoder-workbench__notice--success{border:1px solid #bbf7d0;background:#f0fdf4;color:#166534}.aicoder-workbench__muted{margin:0;color:#64748b}.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:1fr}@media(max-width:900px){.aicoder-workbench__layout{grid-template-columns:1fr}}\n"],dependencies:[{kind:"directive",type:i2$1.NgSelectOption,selector:"option",inputs:["ngValue","value"]},{kind:"directive",type:i2$1.ɵNgSelectMultipleOption,selector:"option",inputs:["ngValue","value"]},{kind:"directive",type:i2$1.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:i2$1.SelectControlValueAccessor,selector:"select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]",inputs:["compareWith"]},{kind:"directive",type:i2$1.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:i2$1.NgModel,selector:"[ngModel]:not([formControlName]):not([formControl])",inputs:["name","disabled","ngModel","ngModelOptions"],outputs:["ngModelChange"],exportAs:["ngModel"]}]})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchComponent,decorators:[{type:Component,args:[{selector:"resolveio-client-lib-aicoder-workbench",standalone:!1,template:'<section class="aicoder-workbench" [class.aicoder-workbench--compact]="compact">\n\t@if (showHeader) {\n\t\t<header class="aicoder-workbench__header">\n\t\t\t<div>\n\t\t\t\t<p>AICoder</p>\n\t\t\t\t<h2>{{ appName || \'AICoder App\' }}</h2>\n\t\t\t</div>\n\t\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh</button>\n\t\t</header>\n\t}\n\n\t<nav class="aicoder-workbench__tabs" aria-label="AICoder workbench tabs">\n\t\t@for (tab of tabs; track tab.id) {\n\t\t\t<button type="button" [class.is-active]="activeTab === tab.id" (click)="setTab(tab.id)">\n\t\t\t\t{{ tab.label }}\n\t\t\t</button>\n\t\t}\n\t</nav>\n\n\t@if (errorMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--error">{{ errorMessage }}</div>\n\t}\n\t@if (successMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--success">{{ successMessage }}</div>\n\t}\n\n\t@if (activeTab === \'cases\') {\n\t\t<div class="aicoder-workbench__layout">\n\t\t\t<section class="aicoder-workbench__panel">\n\t\t\t\t<h3>New Case</h3>\n\t\t\t\t<input class="aicoder-workbench__input" [(ngModel)]="requestTitle" placeholder="Case title" />\n\t\t\t\t<textarea class="aicoder-workbench__textarea" rows="7" [(ngModel)]="requestMessage" placeholder="Describe what should change, what evidence to inspect, and what done looks like."></textarea>\n\t\t\t\t<div class="aicoder-workbench__grid">\n\t\t\t\t\t<label>\n\t\t\t\t\t\t<span>Workflow</span>\n\t\t\t\t\t\t<select [(ngModel)]="workflowMode">\n\t\t\t\t\t\t\t<option value="qa_playbook">QA playbook</option>\n\t\t\t\t\t\t\t<option value="implementation">Implementation</option>\n\t\t\t\t\t\t\t<option value="data_change">Data change</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t</label>\n\t\t\t\t\t<label>\n\t\t\t\t\t\t<span>Deploy target</span>\n\t\t\t\t\t\t<select [(ngModel)]="deploymentTarget">\n\t\t\t\t\t\t\t<option value="branch_pr">Branch + PR</option>\n\t\t\t\t\t\t\t<option value="test_server">Test server</option>\n\t\t\t\t\t\t\t<option value="live_after_approval">Live after approval</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="submitRequest()" [disabled]="isSubmitting || !requestMessage.trim()">Queue Case</button>\n\t\t\t</section>\n\n\t\t\t<section class="aicoder-workbench__panel">\n\t\t\t\t<h3>Cases</h3>\n\t\t\t\t@if (!jobs.length && !isLoading) {\n\t\t\t\t\t<p class="aicoder-workbench__muted">No cases yet.</p>\n\t\t\t\t}\n\t\t\t\t<div class="aicoder-workbench__case-list">\n\t\t\t\t\t@for (job of jobs; track job._id) {\n\t\t\t\t\t\t<button type="button" class="aicoder-workbench__case" [class.is-active]="selectedJob?._id === job._id" (click)="selectJob(job)">\n\t\t\t\t\t\t\t<strong>{{ job.title || \'AICoder Case\' }}</strong>\n\t\t\t\t\t\t\t<span>{{ job.status || \'Queued\' }}{{ job.stage ? \' - \' + job.stage : \'\' }}</span>\n\t\t\t\t\t\t\t<small>{{ formatDate(job.updatedAt || job.createdAt) }}</small>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t</section>\n\t\t</div>\n\t}\n\n\t@if (activeTab === \'chat\') {\n\t\t<div class="aicoder-workbench__layout">\n\t\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--wide">\n\t\t\t\t<h3>{{ selectedJob?.title || \'Select a case\' }}</h3>\n\t\t\t\t<div class="aicoder-workbench__toolbar">\n\t\t\t\t\t<button type="button" (click)="approveSelectedJob()" [disabled]="!selectedJobId || isSubmitting">Approve</button>\n\t\t\t\t\t<button type="button" (click)="createPullRequest()" [disabled]="!selectedJobId || isSubmitting">Create PR</button>\n\t\t\t\t\t<button type="button" (click)="publishSelectedJob()" [disabled]="!selectedJobId || isSubmitting">Publish</button>\n\t\t\t\t\t<button type="button" (click)="openPr()" [disabled]="!resolvePrUrl(selectedJob)">Open PR</button>\n\t\t\t\t</div>\n\t\t\t\t<div class="aicoder-workbench__log">\n\t\t\t\t\t@if (!jobLogs.length) {\n\t\t\t\t\t\t<p class="aicoder-workbench__muted">No log entries loaded.</p>\n\t\t\t\t\t}\n\t\t\t\t\t@for (log of jobLogs; track log._id || log.createdAt || log.message) {\n\t\t\t\t\t\t<div class="aicoder-workbench__log-row">\n\t\t\t\t\t\t\t<span>{{ formatDate(log.createdAt || log.date_created) }}</span>\n\t\t\t\t\t\t\t<p>{{ log.message || log.stage || log.level || \'Log entry\' }}</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t\t<textarea class="aicoder-workbench__textarea" rows="4" [(ngModel)]="chatMessage" placeholder="Send a follow-up to this case."></textarea>\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendChatMessage()" [disabled]="!selectedJobId || !chatMessage.trim() || isSubmitting">Send</button>\n\t\t\t</section>\n\t\t</div>\n\t}\n\n\t@if (activeTab === \'qa\') {\n\t\t<section class="aicoder-workbench__panel">\n\t\t\t<h3>QA Playbook</h3>\n\t\t\t<div class="aicoder-workbench__grid">\n\t\t\t\t<label>\n\t\t\t\t\t<span>Evidence notes</span>\n\t\t\t\t\t<textarea rows="5" [(ngModel)]="evidenceNotes"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<label>\n\t\t\t\t\t<span>QA focus</span>\n\t\t\t\t\t<textarea rows="5" [(ngModel)]="qaFocus"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<label>\n\t\t\t\t\t<span>Risk notes</span>\n\t\t\t\t\t<textarea rows="5" [(ngModel)]="riskNotes"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<label>\n\t\t\t\t\t<span>Data change policy</span>\n\t\t\t\t\t<select [(ngModel)]="dataChangePolicy">\n\t\t\t\t\t\t<option value="review_before_write">Review before write</option>\n\t\t\t\t\t\t<option value="safe_seed_only">Safe seed only</option>\n\t\t\t\t\t\t<option value="no_data_changes">No data changes</option>\n\t\t\t\t\t</select>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t</section>\n\t}\n\n\t@if (activeTab === \'deploy\') {\n\t\t<section class="aicoder-workbench__panel">\n\t\t\t<h3>Deploy</h3>\n\t\t\t<div class="aicoder-workbench__toolbar">\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="queueDeploy(\'full\')" [disabled]="isSubmitting">Full Deploy</button>\n\t\t\t\t<button type="button" (click)="queueDeploy(\'client\')" [disabled]="isSubmitting">Client</button>\n\t\t\t\t<button type="button" (click)="queueDeploy(\'backend\')" [disabled]="isSubmitting">Server</button>\n\t\t\t</div>\n\t\t\t<h4>Current</h4>\n\t\t\t@if (!activeDeploys.length) {\n\t\t\t\t<p class="aicoder-workbench__muted">No active deploys.</p>\n\t\t\t}\n\t\t\t@for (op of activeDeploys; track op.id || op.jobId) {\n\t\t\t\t<div class="aicoder-workbench__deploy-row">\n\t\t\t\t\t<strong>{{ op.type || \'Deploy\' }} - {{ op.status }}</strong>\n\t\t\t\t\t<span>{{ op.aws_build_current_phase || op.backend_deploy_step || op.message }}</span>\n\t\t\t\t</div>\n\t\t\t}\n\t\t\t<h4>Recent</h4>\n\t\t\t@for (op of recentDeploys; track op.id || op.jobId) {\n\t\t\t\t<div class="aicoder-workbench__deploy-row">\n\t\t\t\t\t<strong>{{ op.type || \'Deploy\' }} - {{ op.status }}</strong>\n\t\t\t\t\t<span>{{ op.message || op.backend_deploy_step || \'Deploy finished\' }}</span>\n\t\t\t\t\t<small>{{ formatDate(op.updatedAt || op.createdAt) }}</small>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</section>\n\t}\n\n\t@if (activeTab === \'git\') {\n\t\t<section class="aicoder-workbench__panel">\n\t\t\t<h3>Git</h3>\n\t\t\t<div class="aicoder-workbench__grid">\n\t\t\t\t<div><span>Repo</span><strong>{{ gitStatus?.repo || \'Not loaded\' }}</strong></div>\n\t\t\t\t<div><span>Main branch</span><strong>{{ gitStatus?.default_branch || \'main\' }}</strong></div>\n\t\t\t\t<div><span>Pushes</span><strong>{{ gitStatus?.push_count ?? \'N/A\' }}</strong></div>\n\t\t\t\t<div><span>Last push</span><strong>{{ formatDate(gitStatus?.last_push) }}</strong></div>\n\t\t\t</div>\n\t\t\t<div class="aicoder-workbench__toolbar">\n\t\t\t\t@if (gitStatus?.repo_url) {\n\t\t\t\t\t<a [href]="gitStatus?.repo_url" target="_blank" rel="noopener">Open Repo</a>\n\t\t\t\t}\n\t\t\t\t<button type="button" (click)="loadGitStatus()">Refresh Git</button>\n\t\t\t</div>\n\t\t</section>\n\t}\n</section>\n',styles:[".aicoder-workbench{display:flex;flex-direction:column;gap:1rem;color:#1f2933}.aicoder-workbench__header{display:flex;align-items:center;justify-content:space-between;gap:1rem}.aicoder-workbench__header p{margin:0 0 .25rem;color:#64748b;font-size:.75rem;font-weight:800;letter-spacing:0;text-transform:uppercase}.aicoder-workbench__header h2{margin:0;font-size:1.35rem;font-weight:800}.aicoder-workbench__tabs,.aicoder-workbench__toolbar{display:flex;flex-wrap:wrap;gap:.5rem}.aicoder-workbench__tabs button,.aicoder-workbench__toolbar button,.aicoder-workbench__toolbar a,.aicoder-workbench__ghost,.aicoder-workbench__primary{border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:#1f2933;font-weight:700;padding:.55rem .8rem;text-decoration:none}.aicoder-workbench__tabs button.is-active,.aicoder-workbench__primary{border-color:#2f7d73;background:#2f7d73;color:#fff}.aicoder-workbench__tabs button:disabled,.aicoder-workbench__toolbar button:disabled,.aicoder-workbench__primary:disabled{cursor:not-allowed;opacity:.55}.aicoder-workbench__layout{display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,.7fr);gap:1rem}.aicoder-workbench__panel{border:1px solid #dbe3ec;border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__panel--wide{grid-column:1/-1}.aicoder-workbench__panel h3,.aicoder-workbench__panel h4{margin:0 0 .75rem;font-size:1rem;font-weight:800}.aicoder-workbench__panel h4{margin-top:1rem;color:#64748b;font-size:.8rem;text-transform:uppercase}.aicoder-workbench__input,.aicoder-workbench__textarea,.aicoder-workbench select,.aicoder-workbench textarea{width:100%;border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:#1f2933;padding:.65rem .75rem}.aicoder-workbench__textarea,.aicoder-workbench textarea{resize:vertical}.aicoder-workbench__input,.aicoder-workbench__textarea{margin-bottom:.75rem}.aicoder-workbench__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:.75rem;margin-bottom:.75rem}.aicoder-workbench__grid span,.aicoder-workbench__grid label span{display:block;margin-bottom:.3rem;color:#64748b;font-size:.75rem;font-weight:800;text-transform:uppercase}.aicoder-workbench__case-list,.aicoder-workbench__log{display:flex;flex-direction:column;gap:.5rem;max-height:460px;overflow:auto}.aicoder-workbench__case,.aicoder-workbench__deploy-row,.aicoder-workbench__log-row{display:flex;flex-direction:column;align-items:flex-start;gap:.2rem;width:100%;border:1px solid #e2e8f0;border-radius:7px;background:#f8fafc;padding:.75rem;text-align:left}.aicoder-workbench__case.is-active{border-color:#2f7d73;background:#ecfdf5}.aicoder-workbench__case span,.aicoder-workbench__case small,.aicoder-workbench__deploy-row span,.aicoder-workbench__deploy-row small,.aicoder-workbench__log-row span{color:#64748b;font-size:.82rem}.aicoder-workbench__log{min-height:220px;margin-bottom:.75rem}.aicoder-workbench__log-row p{margin:0}.aicoder-workbench__notice{border-radius:6px;padding:.75rem .9rem;font-weight:700}.aicoder-workbench__notice--error{border:1px solid #fecaca;background:#fff1f2;color:#991b1b}.aicoder-workbench__notice--success{border:1px solid #bbf7d0;background:#f0fdf4;color:#166534}.aicoder-workbench__muted{margin:0;color:#64748b}.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:1fr}@media(max-width:900px){.aicoder-workbench__layout{grid-template-columns:1fr}}\n"]}]}],ctorParameters:()=>[{type:AICoderWorkbenchApiService}],propDecorators:{appId:[{type:Input}],appName:[{type:Input}],apiBase:[{type:Input}],appToken:[{type:Input}],defaultTab:[{type:Input}],compact:[{type:Input}],showHeader:[{type:Input}],autoRefresh:[{type:Input}]}});class AICoderDashboardModule{static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,deps:[],target:i0.ɵɵFactoryTarget.NgModule});static"ɵmod"=i0.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,declarations:[AICoderDashboardComponent,AICoderWorkbenchComponent],imports:[CommonModule,FormsModule,HttpClientModule],exports:[AICoderDashboardComponent,AICoderWorkbenchComponent]});static"ɵinj"=i0.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,imports:[CommonModule,FormsModule,HttpClientModule]})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,decorators:[{type:NgModule,args:[{imports:[CommonModule,FormsModule,HttpClientModule],declarations:[AICoderDashboardComponent,AICoderWorkbenchComponent],exports:[AICoderDashboardComponent,AICoderWorkbenchComponent]}]}]});export{AICoderDashboardComponent,AICoderDashboardModule,AICoderDashboardService,AICoderWorkbenchApiService,AICoderWorkbenchComponent};
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -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?: {
|
|
@@ -140,14 +144,15 @@ declare class AICoderWorkbenchApiService {
|
|
|
140
144
|
private readonly http;
|
|
141
145
|
private readonly tokenManager;
|
|
142
146
|
private readonly sessionHeaderName;
|
|
147
|
+
private readonly appTokenHeaderName;
|
|
143
148
|
private readonly sessionRefreshSkewMs;
|
|
144
149
|
private readonly defaultSessionTtlMs;
|
|
145
150
|
private sessionByApiBase;
|
|
146
151
|
private exchangeByApiBase;
|
|
147
152
|
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>;
|
|
153
|
+
get<T>(apiBase: string, path: string, appToken?: string): Observable<T>;
|
|
154
|
+
post<T>(apiBase: string, path: string, body?: any, appToken?: string): Observable<T>;
|
|
155
|
+
patch<T>(apiBase: string, path: string, body?: any, appToken?: string): Observable<T>;
|
|
151
156
|
resolveDefaultApiBase(): string;
|
|
152
157
|
private authHeaders;
|
|
153
158
|
private ensureSessionToken;
|
|
@@ -163,6 +168,7 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
|
|
|
163
168
|
appId: string;
|
|
164
169
|
appName: string;
|
|
165
170
|
apiBase: string;
|
|
171
|
+
appToken: string;
|
|
166
172
|
defaultTab: AICoderWorkbenchTab;
|
|
167
173
|
compact: boolean;
|
|
168
174
|
showHeader: boolean;
|
|
@@ -213,6 +219,7 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
|
|
|
213
219
|
loadGitStatus(): void;
|
|
214
220
|
get normalizedAppId(): string;
|
|
215
221
|
get selectedJobId(): string;
|
|
222
|
+
get normalizedAppToken(): string;
|
|
216
223
|
get activeDeploys(): AICoderWorkbenchDeployOperation[];
|
|
217
224
|
get recentDeploys(): AICoderWorkbenchDeployOperation[];
|
|
218
225
|
isActiveJob(job: AICoderWorkbenchJob): boolean;
|
|
@@ -225,7 +232,7 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
|
|
|
225
232
|
private clearMessages;
|
|
226
233
|
private handleError;
|
|
227
234
|
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>;
|
|
235
|
+
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
236
|
}
|
|
230
237
|
|
|
231
238
|
declare class AICoderDashboardModule {
|
|
Binary file
|