This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch feat/ai-history-and-enhancement
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git

commit 2bed95114530100ee2ef88d5447455008ab9ccf0
Author: Wu Sheng <[email protected]>
AuthorDate: Wed Jul 8 19:48:49 2026 +0800

    refactor(ai): drop show_service_list — no live-tab handoff, no drift
    
    The last capture-replay gap the scan found: show_service_list mounted a card
    that window.open'd the live layer page, so what the operator saw would drift
    from what the agent read. Remove it and its now-dead subpage machinery
    entirely (it was the only sub-page tool): the SUBPAGES tool, SubPageSpec /
    SubPageKind, the 'subpage' SSE + block, emitSubPage (figure-buffer/context/
    chat), the transcript dispatch + history summary arm, ChatSubPageBlock.vue, 
and
    the yaml + skills/system prompt mentions.
    
    The agent already renders a layer's services with their metrics from 
captured
    tools — list_services (the roster) + show_widget / show_line (frozen 
figures) —
    so nothing is lost and there is no live-tab drift. Every AI-rendered answer 
is
    now a captured, replay-badged snapshot, or an honest text read-out when 
there
    is no data to capture.
---
 apps/bff/src/ai/context.ts                         |  5 --
 apps/bff/src/ai/figure-buffer.ts                   | 10 ---
 apps/bff/src/ai/http/chat.ts                       |  2 -
 apps/bff/src/ai/resources/prompts/skills.md        |  3 -
 apps/bff/src/ai/resources/prompts/system.md        |  4 +-
 apps/bff/src/ai/resources/tools/visualization.yaml | 11 ---
 apps/bff/src/ai/skill/visualization/tools.ts       | 36 +--------
 apps/bff/src/ai/types.ts                           | 15 ----
 apps/ui/src/ai/ChatSubPageBlock.vue                | 93 ----------------------
 apps/ui/src/ai/ChatTranscript.vue                  |  2 -
 apps/ui/src/ai/types.ts                            | 16 ----
 apps/ui/src/ai/useAiConversations.ts               |  3 -
 12 files changed, 3 insertions(+), 197 deletions(-)

diff --git a/apps/bff/src/ai/context.ts b/apps/bff/src/ai/context.ts
index 88f776b..2516bb2 100644
--- a/apps/bff/src/ai/context.ts
+++ b/apps/bff/src/ai/context.ts
@@ -42,7 +42,6 @@ import type {
   ProposalSpec,
   BrowserErrorsSpec,
   LogsSpec,
-  SubPageSpec,
   TopologySpec,
   TracesSpec,
   ZipkinTracesSpec,
@@ -70,10 +69,6 @@ export interface AiRequestContext {
    *  tab-based" UX); without a group, any pending group is flushed first and
    *  this figure emits on its own. The running figure number is owned here. */
   emitFigure(fig: { title?: string; figures: ChatFigure[]; group?: string }): 
void;
-  /** Mount an embeddable feature view (topology/deployment/service-list) 
inline
-   *  in the chat. Params only — the UI view fetches its own data over the 
chat's
-   *  range; verb-gated in the tool like every other read. */
-  emitSubPage(spec: SubPageSpec): void;
   /** Propose a mutating action (profiling) as a decision card — the agent 
never
    *  fires it; the user approves/dismisses in the UI, which then calls the
    *  existing verb-gated create route. */
diff --git a/apps/bff/src/ai/figure-buffer.ts b/apps/bff/src/ai/figure-buffer.ts
index af5a278..7fa7e44 100644
--- a/apps/bff/src/ai/figure-buffer.ts
+++ b/apps/bff/src/ai/figure-buffer.ts
@@ -36,7 +36,6 @@ import type {
   SseEvent,
   BrowserErrorsSpec,
   LogsSpec,
-  SubPageSpec,
   TopologySpec,
   TracesSpec,
   ZipkinTracesSpec,
@@ -44,9 +43,6 @@ import type {
 
 export interface FigureBuffer {
   emitFigure(fig: { title?: string; figures: ChatFigure[]; group?: string }): 
void;
-  /** Emit a sub-page block (topology/deployment/…). Shares the figure number
-   *  sequence; closes any pending figure group first so ordering is 
preserved. */
-  emitSubPage(spec: SubPageSpec): void;
   /** Emit a proposed-action decision card. Same numbering; flushes first. */
   emitProposal(spec: ProposalSpec): void;
   /** Emit a captured profiling-result (flame) block. Same numbering; flushes 
first. */
@@ -99,11 +95,6 @@ export function createFigureBuffer(send: (ev: SseEvent) => 
void): FigureBuffer {
     }
   };
 
-  const emitSubPage = (spec: SubPageSpec): void => {
-    flushFigures();
-    send({ type: 'subpage', n: ++figureN, spec });
-  };
-
   const emitProposal = (spec: ProposalSpec): void => {
     flushFigures();
     send({ type: 'proposal', n: ++figureN, spec });
@@ -171,7 +162,6 @@ export function createFigureBuffer(send: (ev: SseEvent) => 
void): FigureBuffer {
 
   return {
     emitFigure,
-    emitSubPage,
     emitProposal,
     emitProfiling,
     emitProcessTopology,
diff --git a/apps/bff/src/ai/http/chat.ts b/apps/bff/src/ai/http/chat.ts
index 49e91f1..517faaa 100644
--- a/apps/bff/src/ai/http/chat.ts
+++ b/apps/bff/src/ai/http/chat.ts
@@ -162,7 +162,6 @@ export function registerAiRoutes(app: FastifyInstance, 
deps: AiChatRouteDeps): v
 
     const {
       emitFigure,
-      emitSubPage,
       emitProposal,
       emitProfiling,
       emitProcessTopology,
@@ -188,7 +187,6 @@ export function registerAiRoutes(app: FastifyInstance, 
deps: AiChatRouteDeps): v
       bulkSize: cfg.performance.bulk.dashboard.bulkSize,
       hasVerb: (verb) => sessionHasVerb(cfg, roles, verb),
       emitFigure,
-      emitSubPage,
       emitProposal,
       emitProfiling,
       emitProcessTopology,
diff --git a/apps/bff/src/ai/resources/prompts/skills.md 
b/apps/bff/src/ai/resources/prompts/skills.md
index 576ee12..bbebee5 100644
--- a/apps/bff/src/ai/resources/prompts/skills.md
+++ b/apps/bff/src/ai/resources/prompts/skills.md
@@ -66,9 +66,6 @@ INLINE VIEW TOOLS — mount a REAL feature view inline 
(read-only, focused on th
 - show_instance_topology(layer, sourceService, destService) — the per-PAIR 
INSTANCE map inline: the instances of a SOURCE (client) service and a DEST 
(server) service as two columns with the instance-to-instance calls BETWEEN 
them (the topology tab's edge drill-down). Needs BOTH services and they must 
have a call relationship (source calls dest) — resolve two service names. Use 
it for "how do service A's instances talk to service B's"; contrast 
show_deployment (one service's own instance [...]
 - show_endpoint_dependency(layer, service) — the per-ENDPOINT API-dependency 
chain inline: the service's PRIMARY endpoint (auto-picked) and its upstream 
callers + downstream callees across services, the real API-dependency-tab view 
(expand any node). This is API-DEPENDENCY detection (endpoint→endpoint). It 
reads EndpointRelation, which is SERVER-SIDE ONLY (no client edge) and comes 
from NATIVE SkyWalking tracing — so it only has data on a native-trace layer; 
on a Zipkin/mesh layer (check [...]
 
-SUB-PAGE TOOLS — mount an interactive feature view as a card (opens the full 
page in a new tab):
-- show_service_list(layer) — the services in a layer with their key metrics.
-
 TRIGGERS skill — gated profiling (propose_profiling) + analysis 
(analyze_profiling)
 - You are READ-ONLY except for propose_profiling. When metrics + pod logs 
cannot localise a cause and a PROFILE would confirm a specific hypothesis, call 
propose_profiling — it presents a decision card (your analysed cause, why 
profiling, what you expect); the USER approves it in a popout; it runs only 
after approval; you analyse the result in a LATER turn. Never assume it ran, 
and never fabricate its output.
 - Pick the profilingType by READING, not assuming: 
kb_layer_capabilities(layer).components says which of 
trace/async/pprof/ebpf/network the layer supports; async-profiler is JVM-only 
and pprof is Go-only (read the instance language via kb_resolve_scope_drill to 
choose), trace + eBPF are language-agnostic, network profiling yields a 
process-conversation graph. The tool re-validates the type against the layer 
and resolves target instances.
diff --git a/apps/bff/src/ai/resources/prompts/system.md 
b/apps/bff/src/ai/resources/prompts/system.md
index 0752e96..8a0b11d 100644
--- a/apps/bff/src/ai/resources/prompts/system.md
+++ b/apps/bff/src/ai/resources/prompts/system.md
@@ -27,7 +27,7 @@ INVESTIGATION LOOP — for a TROUBLESHOOTING / diagnostic 
question (a direct req
 0. Health triage — for "what is unhealthy / wrong?", start with list_alarms 
(TELEMETRY): active alarms name the anomaly entity; then explain its metrics.
 1. Orient — find the layer + service with list_layers / list_services 
(CONTEXT); search by name with NO layer when the layer is unknown. For a layer 
you'll work in, kb_layer_capabilities(layer) gives its vocabulary, components, 
trace source, and relation metric legends up front — read it in the layer's own 
terms.
 2. Find metrics — BEFORE rendering any metric figure, kb_browse_catalog the 
EXACT layer+scope you are about to render, and use ONLY an entry from THAT 
response — its catalog title AND its MQE, both VERBATIM (METRIC-CATALOG). 
Re-browse for every layer/scope; never reuse a name or MQE you saw in another 
layer or recall from memory (e.g. do NOT render "HTTP Response Time" in MESH — 
that title lives in K8S_SERVICE; MESH's is "Avg Response Time"). A made-up, 
renamed, or wrong-layer/scope metr [...]
-3. Render — display a metric with the right show_* widget (chosen by MQE 
shape) or a whole-view sub-page (VISUALIZATION — see the RENDER GUIDE for the 
widgets, scopes and limits).
+3. Render — display a metric with the right show_* widget (chosen by MQE 
shape) or a whole feature view inline (VISUALIZATION — see the RENDER GUIDE for 
the widgets, scopes and limits).
 4. Drill — no cross-scope rollup: kb_resolve_scope_drill to the child ids, 
then render at that finer scope (METRIC-CATALOG).
 5. Read pod logs — for a Kubernetes workload, fetch_pod_logs is the error 
stack (KUBERNETES).
 
@@ -35,7 +35,7 @@ ROOT-CAUSE METHOD
 For any "why is X wrong / what is the root cause" question, call 
list_playbooks and get_playbook the best-matching one, then FOLLOW its steps. 
The method in brief: find the ROOT SERVICE (walk upstream with show_topology; 
fix a sick upstream first; a Virtual_* remote gives only a client-side edge 
metric; use kb_resolve_hierarchy to cross a layer boundary into an 
infra/database layer, where memory / disk / connection causes live) → then the 
CALLING CHAIN (kb_resolve_scope_drill to the outl [...]
 
 INTERACTION MODES — tools resolve in three different ways; handle each 
correctly:
-- AUTO-QUERY — most tools (list_*, kb_*, the show_* metric figures, 
show_topology / deployment / service_list, list_alarms): they run and return 
data immediately. Read the result and narrate.
+- AUTO-QUERY — most tools (list_*, kb_*, the show_* metric figures, 
show_topology / deployment, list_alarms): they run and return data immediately. 
Read the result and narrate.
 - WAIT-FOR-CONFIRMATION — propose_profiling: you PROPOSE a task as a decision 
card; it runs ONLY after the user approves it in a popout; you analyse the 
result in a LATER turn. Never assume it ran, and never fabricate its output.
 - WAIT-FOR-COLLECTION (fixed-time) — an approved profile is gathered OVER 
TIME: it runs for its duration and you analyse the result in a LATER turn 
(never assume it's done). On-demand pod logs are NOT this — fetch_pod_logs 
returns the current window's lines IMMEDIATELY, so analyse what came back right 
away; it shows those lines to the operator inline as a read-only result (not a 
live tail). If that first snapshot is empty, WIDEN the window and retry before 
concluding the pod is silent —  [...]
 
diff --git a/apps/bff/src/ai/resources/tools/visualization.yaml 
b/apps/bff/src/ai/resources/tools/visualization.yaml
index 375c62a..3f6b41a 100644
--- a/apps/bff/src/ai/resources/tools/visualization.yaml
+++ b/apps/bff/src/ai/resources/tools/visualization.yaml
@@ -89,17 +89,6 @@ show_widget:
     group: >-
       cluster related figures into one tabbed block
 
-show_service_list:
-  description: >-
-    Render the services in a layer with their key metrics inline in the chat 
as an interactive view (the same component the dashboards use). Provide the 
layer (and optionally a service to focus).
-  params:
-    layer: >-
-      OAP layer key, e.g. GENERAL
-    service: >-
-      optional service NAME to focus on
-    title: >-
-      optional heading for the view
-
 show_hierarchy:
   description: >-
     Render a service's CROSS-LAYER hierarchy inline (the topology page's 
Smartscape overlay): the focused service plus the same logical service 
projected into its upper layers (e.g. a GENERAL service's MESH / K8S_SERVICE 
mirrors) and lower layers (backing infrastructure). Use this to show how one 
service maps across layers — NOT for same-layer dependencies (use show_topology 
for those). Provide the layer and service name.
diff --git a/apps/bff/src/ai/skill/visualization/tools.ts 
b/apps/bff/src/ai/skill/visualization/tools.ts
index 10eee0d..6209990 100644
--- a/apps/bff/src/ai/skill/visualization/tools.ts
+++ b/apps/bff/src/ai/skill/visualization/tools.ts
@@ -38,7 +38,7 @@ import type {
   ZipkinTraceListResponse,
 } from '@skywalking-horizon-ui/api-client';
 import type { AiRequestContext } from '../../context.js';
-import type { HierarchyGroup, SubPageKind, TopoPeer } from '../../types.js';
+import type { HierarchyGroup, TopoPeer } from '../../types.js';
 import { runWidgets } from '../../../logic/dashboard/run.js';
 import { resolveEffectiveLayer } from '../../../logic/layers/effective.js';
 import {
@@ -387,39 +387,6 @@ export function visualizationTools(ctx: AiRequestContext): 
StructuredToolInterfa
     },
   );
 
-  // Sub-page tools mount an embeddable feature VIEW inline (not a widget). 
They
-  // carry only params — the UI view fetches its own data over the chat's 
range.
-  const SUBPAGES: Array<{
-    kind: SubPageKind;
-    verb: string;
-    needsService: boolean;
-  }> = [
-    { kind: 'service-list', verb: 'metrics:read', needsService: false },
-  ];
-  const subPageTools = SUBPAGES.map(({ kind, verb, needsService }) => {
-    const t = toolPrompt('visualization', `show_${kind.replace(/-/g, '_')}`);
-    return tool(
-      async ({ layer, service, title }: { layer: string; service?: string; 
title?: string }): Promise<string> => {
-        if (!ctx.hasVerb(verb)) return `Permission denied: the current user 
lacks ${verb}.`;
-        if (needsService && !service) return `${kind} needs a service name.`;
-        const heading = title || `${kind} — ${service ?? layer}`;
-        ctx.emitSubPage({ kind, title: heading, layer: layer.toUpperCase(), 
service, range: ctx.range });
-        return `Mounted the ${kind} view for ${service ?? layer}. It renders 
interactively in the chat.`;
-      },
-      {
-        name: `show_${kind.replace(/-/g, '_')}`,
-        description: t.description,
-        schema: z.object({
-          layer: z.string().describe(t.p('layer')),
-          service: needsService
-            ? z.string().describe(t.p('service'))
-            : z.string().optional().describe(t.p('service')),
-          title: z.string().optional().describe(t.p('title')),
-        }),
-      },
-    );
-  });
-
   // show_hierarchy renders the topology page's cross-layer Smartscape overlay
   // inline: the focus service + the same logical service projected into upper
   // (K8S_SERVICE ← MESH ← GENERAL) and lower (→ infra) layers. Params only — 
the
@@ -958,7 +925,6 @@ export function visualizationTools(ctx: AiRequestContext): 
StructuredToolInterfa
     make('table'),
     make('record'),
     widgetTool,
-    ...subPageTools,
     hierarchyTool,
     topologyTool,
     deploymentTool,
diff --git a/apps/bff/src/ai/types.ts b/apps/bff/src/ai/types.ts
index 2f7362e..037dabd 100644
--- a/apps/bff/src/ai/types.ts
+++ b/apps/bff/src/ai/types.ts
@@ -55,20 +55,6 @@ export interface ChatFigure {
   xaxis?: FigureXAxis;
 }
 
-/** A "sub-page" figure: a feature view surfaced as a card that opens the real
- *  full page in a new tab. Graph/triage views (topology, deployment, traces,
- *  logs, browser errors) now embed inline via their own specs; this is the
- *  remaining link-out (the layer service list). */
-export type SubPageKind = 'service-list';
-
-export interface SubPageSpec {
-  kind: SubPageKind;
-  title: string;
-  layer: string;
-  service?: string;
-  range: FigureXAxis;
-}
-
 /** A PROPOSED mutating action (profiling / live-debug). The agent never fires
  *  it — it presents a decision card (what it found, why this action, what it
  *  expects) and the user approves or dismisses in a popout. On approve the UI
@@ -337,7 +323,6 @@ export type SseEvent =
   | { type: 'thinking'; text: string }
   | { type: 'tool'; name: string; status: 'running' | 'done' | 'denied' }
   | { type: 'figure'; n: number; title?: string; layout: FigureLayout; 
figures: ChatFigure[] }
-  | { type: 'subpage'; n: number; spec: SubPageSpec }
   | { type: 'proposal'; n: number; spec: ProposalSpec }
   | { type: 'profiling'; n: number; spec: ProfilingResultSpec }
   | { type: 'process-topology'; n: number; spec: ProcessTopologySpec }
diff --git a/apps/ui/src/ai/ChatSubPageBlock.vue 
b/apps/ui/src/ai/ChatSubPageBlock.vue
deleted file mode 100644
index 905fbb9..0000000
--- a/apps/ui/src/ai/ChatSubPageBlock.vue
+++ /dev/null
@@ -1,93 +0,0 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
--->
-<!-- Sub-page figure block. Surfaces a feature view (the layer service list) 
the
-     assistant chose, and opens the REAL view (with the resolved route) in a 
new
-     tab. Graph/triage views embed inline via their own blocks; this is the
-     remaining link-out card. -->
-<script setup lang="ts">
-import { computed } from 'vue';
-import { useRouter } from 'vue-router';
-import { useI18n } from 'vue-i18n';
-import Icon from '@/components/icons/Icon.vue';
-import type { SubPageSpec } from './types';
-
-const props = defineProps<{ n: number; spec: SubPageSpec }>();
-const router = useRouter();
-const { t } = useI18n({ useScope: 'global' });
-
-const ROUTE_SUFFIX: Record<SubPageSpec['kind'], string> = {
-  'service-list': 'service',
-};
-
-const href = computed(
-  () => router.resolve({ path: 
`/layer/${props.spec.layer.toLowerCase()}/${ROUTE_SUFFIX[props.spec.kind]}` 
}).href,
-);
-
-function open(): void {
-  window.open(href.value, '_blank', 'noopener');
-}
-</script>
-
-<template>
-  <div class="csp">
-    <div class="csp__cap">{{ t('Figure {n}', { n }) }} · {{ spec.title }}</div>
-    <button type="button" class="csp__open" :title="t('Open in a new tab')" 
@click="open">
-      <Icon name="external" :size="13" />
-      <span>{{ t('Open in a new tab') }}</span>
-    </button>
-  </div>
-</template>
-
-<style scoped>
-.csp {
-  border: 1px solid var(--sw-line-2);
-  border-radius: 8px;
-  background: var(--sw-bg-1);
-  padding: 10px 12px;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  gap: 12px;
-}
-.csp__cap {
-  font-size: var(--sw-fs-sm);
-  color: var(--sw-fg-1);
-  min-width: 0;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
-.csp__open {
-  flex: 0 0 auto;
-  display: inline-flex;
-  align-items: center;
-  gap: 6px;
-  height: 28px;
-  padding: 0 10px;
-  border: 1px solid var(--sw-line-2);
-  border-radius: 6px;
-  background: var(--sw-bg-2);
-  color: var(--sw-fg-1);
-  font: inherit;
-  font-size: var(--sw-fs-sm);
-  cursor: pointer;
-}
-.csp__open:hover {
-  border-color: var(--sw-accent);
-  color: var(--sw-fg-0);
-}
-</style>
diff --git a/apps/ui/src/ai/ChatTranscript.vue 
b/apps/ui/src/ai/ChatTranscript.vue
index 19e6b2e..001eedd 100644
--- a/apps/ui/src/ai/ChatTranscript.vue
+++ b/apps/ui/src/ai/ChatTranscript.vue
@@ -20,7 +20,6 @@
 import { computed } from 'vue';
 import { useI18n } from 'vue-i18n';
 import ChatFigureBlock from './ChatFigureBlock.vue';
-import ChatSubPageBlock from './ChatSubPageBlock.vue';
 import ChatProposalBlock from './ChatProposalBlock.vue';
 import ChatProfilingBlock from './ChatProfilingBlock.vue';
 import ChatProcessTopologyBlock from './ChatProcessTopologyBlock.vue';
@@ -94,7 +93,6 @@ function fmtTime(at: number): string {
           <!-- eslint-disable-next-line vue/no-v-html -- renderMarkdown 
escapes HTML before formatting (see markdown.ts), so LLM output cannot inject 
-->
           <div v-if="b.kind === 'text'" class="tx__prose" 
v-html="renderMarkdown(b.text)"></div>
           <ChatFigureBlock v-else-if="b.kind === 'figure'" :block="b" />
-          <ChatSubPageBlock v-else-if="b.kind === 'subpage'" :n="b.n" 
:spec="b.spec" />
           <ChatProposalBlock v-else-if="b.kind === 'proposal'" :block="b" />
           <ChatProfilingBlock v-else-if="b.kind === 'profiling'" :n="b.n" 
:spec="b.spec" :captured-at="b.capturedAt" />
           <ChatProcessTopologyBlock v-else-if="b.kind === 'process-topology'" 
:n="b.n" :spec="b.spec" :captured-at="b.capturedAt" />
diff --git a/apps/ui/src/ai/types.ts b/apps/ui/src/ai/types.ts
index c2b97a0..332d4a4 100644
--- a/apps/ui/src/ai/types.ts
+++ b/apps/ui/src/ai/types.ts
@@ -48,20 +48,6 @@ export interface ChatFigure {
   xaxis?: FigureXAxis;
 }
 
-// A sub-page figure: a feature view surfaced as a card that opens the real 
full
-// page in a new tab. Graph/triage views (topology, deployment, traces, logs,
-// browser errors) embed inline via their own specs; this is the remaining
-// link-out (the layer service list).
-export type SubPageKind = 'service-list';
-
-export interface SubPageSpec {
-  kind: SubPageKind;
-  title: string;
-  layer: string;
-  service?: string;
-  range: FigureXAxis;
-}
-
 // A proposed mutating action (profiling) — a decision card the user approves 
in
 // a popout; the agent never fires it. Carries the reasoning it must justify 
plus
 // the type-specific params the approve handler fires with. Mirror of BFF 
ProposalSpec.
@@ -302,7 +288,6 @@ export type SseEvent =
   | { type: 'thinking'; text: string }
   | { type: 'tool'; name: string; status: 'running' | 'done' | 'denied' }
   | { type: 'figure'; n: number; title?: string; layout: FigureLayout; 
figures: ChatFigure[] }
-  | { type: 'subpage'; n: number; spec: SubPageSpec }
   | { type: 'proposal'; n: number; spec: ProposalSpec }
   | { type: 'profiling'; n: number; spec: ProfilingResultSpec }
   | { type: 'process-topology'; n: number; spec: ProcessTopologySpec }
@@ -324,7 +309,6 @@ export type ProposalStatus = 'pending' | 'approved' | 
'dismissed' | 'failed';
 export type Block =
   | { kind: 'text'; text: string }
   | { kind: 'figure'; n: number; title?: string; layout: FigureLayout; 
figures: ChatFigure[]; capturedAt?: number }
-  | { kind: 'subpage'; n: number; spec: SubPageSpec }
   | { kind: 'proposal'; n: number; spec: ProposalSpec; status: ProposalStatus; 
taskId?: string; error?: string }
   | { kind: 'profiling'; n: number; spec: ProfilingResultSpec; capturedAt?: 
number }
   | { kind: 'process-topology'; n: number; spec: ProcessTopologySpec; 
capturedAt?: number }
diff --git a/apps/ui/src/ai/useAiConversations.ts 
b/apps/ui/src/ai/useAiConversations.ts
index c095e4f..d974839 100644
--- a/apps/ui/src/ai/useAiConversations.ts
+++ b/apps/ui/src/ai/useAiConversations.ts
@@ -57,8 +57,6 @@ function textOf(blocks: Block[]): string {
       parts.push(`(logs: ${b.spec.service})`);
     } else if (b.kind === 'browser-errors') {
       parts.push(`(browser errors: ${b.spec.service})`);
-    } else if (b.kind === 'subpage') {
-      parts.push(`(sub-page: ${b.spec.title})`);
     } else if (b.kind === 'proposal') {
       parts.push(`(suggested action: ${b.spec.kind} for ${b.spec.service})`);
     }
@@ -239,7 +237,6 @@ async function send(text: string): Promise<void> {
       // Stamp the capture time so a reloaded (frozen) figure documents WHEN 
its
       // point-in-time data is from, not read as current.
       else if (ev.type === 'figure') assistant.blocks.push({ kind: 'figure', 
n: ev.n, title: ev.title, layout: ev.layout, figures: ev.figures, capturedAt: 
Date.now() });
-      else if (ev.type === 'subpage') assistant.blocks.push({ kind: 'subpage', 
n: ev.n, spec: ev.spec });
       else if (ev.type === 'proposal') assistant.blocks.push({ kind: 
'proposal', n: ev.n, spec: ev.spec, status: 'pending' });
       else if (ev.type === 'profiling') assistant.blocks.push({ kind: 
'profiling', n: ev.n, spec: ev.spec, capturedAt: Date.now() });
       else if (ev.type === 'process-topology') assistant.blocks.push({ kind: 
'process-topology', n: ev.n, spec: ev.spec, capturedAt: Date.now() });

Reply via email to