Skip to content

Commit 4cc4475

Browse files
xuyushun441-sysos-zhuangclaude
authored
fix(app-shell): send current-page object to the AI assistant context (#1698)
The floating console assistant only forwarded appName + the full objects list, never the object the user is actually viewing. So asking it to "analyse this object" (especially in a non-English prompt) gave the agent nothing to anchor on and it replied that it couldn't find the object. Derive the current object/record from the route (mirroring useTrackRouteAsRecent's URL layout, tolerant of a `_console` shell prefix) and pass them as context.objectName / context.recordId. The backend then injects that object's schema into the system prompt and scopes data queries to it. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ed27b0a commit 4cc4475

1 file changed

Lines changed: 61 additions & 1 deletion

File tree

packages/app-shell/src/layout/ConsoleFloatingChatbot.tsx

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
import { Share2, SquarePen } from 'lucide-react';
3535
import { useObjectTranslation } from '@object-ui/i18n';
3636
import { toast } from 'sonner';
37-
import { useNavigate } from 'react-router-dom';
37+
import { useNavigate, useLocation } from 'react-router-dom';
3838
import {
3939
sanitizeChatMessagesForCache,
4040
useChatConversation,
@@ -250,6 +250,52 @@ function buildEditorSuggestions(
250250
: [`Add fields to ${subject}`, `Suggest validations for ${subject}`, 'Add a status picklist field'];
251251
}
252252

253+
/** Segments after `:appName` that are route prefixes, not object names. */
254+
const NON_OBJECT_ROUTE_SEGMENTS = new Set([
255+
'view', 'record', 'page', 'dashboard', 'design', 'report', 'metadata',
256+
]);
257+
258+
/** Decode a URL segment, falling back to the raw value on malformed input. */
259+
function safeDecodeSegment(value: string): string {
260+
try {
261+
return decodeURIComponent(value);
262+
} catch {
263+
return value;
264+
}
265+
}
266+
267+
/**
268+
* Derive the object (and record) the user is currently viewing from the
269+
* console URL, so the agent can act on "this object" without the user
270+
* restating it. Mirrors the URL layout parsed by `useTrackRouteAsRecent`:
271+
*
272+
* /apps/:appName/:objectName
273+
* /apps/:appName/:objectName/:recordId
274+
* /apps/:appName/:objectName/new
275+
*
276+
* Tolerates an optional shell prefix (e.g. `/_console`) by locating the
277+
* `apps` segment dynamically. Returns an empty object when the path isn't an
278+
* object route (dashboard/page/report/metadata) or the segment doesn't match
279+
* a known object in the current app.
280+
*/
281+
function resolveCurrentRouteObject(
282+
pathname: string,
283+
objects: ConsoleObject[],
284+
): { objectName?: string; recordId?: string } {
285+
const parts = pathname.split('/').filter(Boolean);
286+
const appsIdx = parts.indexOf('apps');
287+
// Need at least [apps, appName, objectName].
288+
if (appsIdx === -1 || parts.length < appsIdx + 3) return {};
289+
290+
const objectSeg = safeDecodeSegment(parts[appsIdx + 2]);
291+
if (NON_OBJECT_ROUTE_SEGMENTS.has(objectSeg)) return {};
292+
if (!objects.some((o) => o.name === objectSeg)) return {};
293+
294+
const recordSeg = parts[appsIdx + 3] ? safeDecodeSegment(parts[appsIdx + 3]) : undefined;
295+
const recordId = recordSeg && recordSeg !== 'new' ? recordSeg : undefined;
296+
return { objectName: objectSeg, ...(recordId ? { recordId } : {}) };
297+
}
298+
253299
interface ChatbotInnerProps {
254300
appLabel: string;
255301
appName?: string;
@@ -301,6 +347,15 @@ function ChatbotInner({
301347
}: ChatbotInnerProps) {
302348
const { language } = useObjectTranslation();
303349
const navigate = useNavigate();
350+
const location = useLocation();
351+
352+
// The object/record the user is currently viewing in the runtime console,
353+
// derived from the route. Lets the agent answer "analyse this object" and
354+
// scope data queries to the open page without the user naming it.
355+
const currentRouteObject = React.useMemo(
356+
() => resolveCurrentRouteObject(location.pathname, objects),
357+
[location.pathname, objects],
358+
);
304359

305360
// What the user is currently editing in a designer (if any). Merged into
306361
// the agent context so "add a priority field" acts on the open object,
@@ -350,6 +405,11 @@ function ChatbotInner({
350405
// Publish posture, so the agent's narration matches reality (an
351406
// auto-published build is live, not "to publish").
352407
autoPublishAiBuilds: getRuntimeConfig().features.autoPublishAiBuilds,
408+
// The object/record currently open in the runtime console — the
409+
// backend injects its schema and scopes data queries to it, so
410+
// "analyse this object" works without the user naming it.
411+
...(currentRouteObject.objectName ? { objectName: currentRouteObject.objectName } : {}),
412+
...(currentRouteObject.recordId ? { recordId: currentRouteObject.recordId } : {}),
353413
// The metadata item currently open in a designer, so the agent
354414
// can act on "this object/view/…" without the user restating it.
355415
...(editor ? { editing: editor } : {}),

0 commit comments

Comments
 (0)