mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-07 17:18:16 +00:00
Rename AI chat mention labels
This commit is contained in:
parent
f5fba35368
commit
fa99046c64
5 changed files with 29 additions and 26 deletions
|
|
@ -141,6 +141,9 @@ Structured mention resolution also uses the shared AI tools discovery
|
|||
canonicalization helpers now, so chat prefetch and discovery responses agree
|
||||
on resource-type and target-ID formatting instead of maintaining chat-local
|
||||
copies.
|
||||
The chat mention picker now also carries the canonical preferred resource
|
||||
label as `label` through the structured mention payload, so mention search,
|
||||
selection, and submission do not depend on a raw `displayName` field fork.
|
||||
The same governed-context rule also applies to the main unified AI resource
|
||||
overview: infrastructure, workload, alert-label, and top-consumer summaries
|
||||
must not leak raw resource names, cluster labels, IP addresses, or unresolved
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { getSimpleStatusIndicator } from '@/utils/status';
|
|||
|
||||
export interface MentionResource {
|
||||
id: string;
|
||||
displayName: string;
|
||||
label: string;
|
||||
type: 'vm' | 'system-container' | 'app-container' | 'agent';
|
||||
status?: string;
|
||||
node?: string;
|
||||
|
|
@ -28,7 +28,7 @@ export function MentionAutocomplete(props: MentionAutocompleteProps) {
|
|||
if (!q) return props.resources.slice(0, 10); // Show first 10 if no query
|
||||
|
||||
return props.resources
|
||||
.filter((r) => r.displayName.toLowerCase().includes(q))
|
||||
.filter((r) => r.label.toLowerCase().includes(q))
|
||||
.slice(0, 10); // Limit to 10 results
|
||||
};
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ export function MentionAutocomplete(props: MentionAutocompleteProps) {
|
|||
<span class="text-muted">{getTypeIcon(resource.type)}</span>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-base-content truncate">{resource.displayName}</span>
|
||||
<span class="font-medium text-base-content truncate">{resource.label}</span>
|
||||
<Show when={resource.status}>
|
||||
<StatusDot
|
||||
variant={getSimpleStatusIndicator(resource.status).variant}
|
||||
|
|
|
|||
|
|
@ -127,15 +127,15 @@ vi.mock('../MentionAutocomplete', () => ({
|
|||
MentionAutocomplete: (props: {
|
||||
visible: boolean;
|
||||
query: string;
|
||||
resources: Array<{ id: string; displayName: string }>;
|
||||
onSelect: (resource: { id: string; displayName: string }) => void;
|
||||
resources: Array<{ id: string; label: string }>;
|
||||
onSelect: (resource: { id: string; label: string }) => void;
|
||||
}) => (
|
||||
<div
|
||||
data-testid="mention-autocomplete"
|
||||
data-visible={String(props.visible)}
|
||||
data-query={props.query}
|
||||
data-resource-count={String(props.resources.length)}
|
||||
data-resource-labels={props.resources.map((resource) => resource.displayName).join('|')}
|
||||
data-resource-labels={props.resources.map((resource) => resource.label).join('|')}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -528,7 +528,7 @@ describe('AIChat', () => {
|
|||
{
|
||||
id: 'agent-1',
|
||||
name: 'secret-node-1',
|
||||
displayName: 'redacted by policy',
|
||||
label: 'redacted by policy',
|
||||
type: 'agent' as const,
|
||||
status: 'online',
|
||||
policy: governedPolicy,
|
||||
|
|
@ -537,7 +537,7 @@ describe('AIChat', () => {
|
|||
{
|
||||
id: 'agent-2',
|
||||
name: 'secret-node-2',
|
||||
displayName: 'redacted by policy',
|
||||
label: 'redacted by policy',
|
||||
type: 'agent' as const,
|
||||
status: 'online',
|
||||
policy: governedPolicy,
|
||||
|
|
@ -571,7 +571,7 @@ describe('AIChat', () => {
|
|||
{
|
||||
id: 'agent-1',
|
||||
name: 'secret-node-1',
|
||||
displayName: 'redacted by policy',
|
||||
label: 'redacted by policy',
|
||||
type: 'agent' as const,
|
||||
status: 'online',
|
||||
policy: governedPolicy,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ afterEach(cleanup);
|
|||
function makeResource(overrides?: Partial<MentionResource>): MentionResource {
|
||||
return {
|
||||
id: 'vm-100',
|
||||
displayName: 'web-server',
|
||||
label: 'web-server',
|
||||
type: 'vm',
|
||||
status: 'running',
|
||||
node: 'pve1',
|
||||
|
|
@ -19,29 +19,29 @@ function makeResource(overrides?: Partial<MentionResource>): MentionResource {
|
|||
const defaultResources: MentionResource[] = [
|
||||
makeResource({
|
||||
id: 'vm-100',
|
||||
displayName: 'web-server',
|
||||
label: 'web-server',
|
||||
type: 'vm',
|
||||
status: 'running',
|
||||
node: 'pve1',
|
||||
}),
|
||||
makeResource({
|
||||
id: 'ct-200',
|
||||
displayName: 'db-container',
|
||||
label: 'db-container',
|
||||
type: 'system-container',
|
||||
status: 'running',
|
||||
node: 'pve2',
|
||||
}),
|
||||
makeResource({ id: 'node-1', displayName: 'pve1', type: 'agent', status: 'running' }),
|
||||
makeResource({ id: 'node-1', label: 'pve1', type: 'agent', status: 'running' }),
|
||||
makeResource({
|
||||
id: 'docker-1',
|
||||
displayName: 'nginx-proxy',
|
||||
label: 'nginx-proxy',
|
||||
type: 'app-container',
|
||||
status: 'running',
|
||||
node: 'docker-host',
|
||||
}),
|
||||
makeResource({
|
||||
id: 'host-1',
|
||||
displayName: 'bare-metal-01',
|
||||
label: 'bare-metal-01',
|
||||
type: 'agent',
|
||||
status: 'stopped',
|
||||
node: undefined,
|
||||
|
|
@ -85,7 +85,7 @@ describe('MentionAutocomplete', () => {
|
|||
it('shows all resources (up to 10) when query is empty', () => {
|
||||
renderAutocomplete({ query: '' });
|
||||
for (const r of defaultResources) {
|
||||
expect(screen.getByText(r.displayName)).toBeInTheDocument();
|
||||
expect(screen.getByText(r.label)).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ describe('MentionAutocomplete', () => {
|
|||
|
||||
it('limits results to 10 items', () => {
|
||||
const manyResources = Array.from({ length: 15 }, (_, i) =>
|
||||
makeResource({ id: `vm-${i}`, displayName: `server-${i}` }),
|
||||
makeResource({ id: `vm-${i}`, label: `server-${i}` }),
|
||||
);
|
||||
renderAutocomplete({ resources: manyResources, query: '' });
|
||||
|
||||
|
|
|
|||
|
|
@ -438,7 +438,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
|||
const vmid = parseLegacyVmid(vm, platformData);
|
||||
mentionCandidates.push({
|
||||
id: `vm:${node}:${vmid}`,
|
||||
displayName: getPreferredResourceDisplayName(vm),
|
||||
label: getPreferredResourceDisplayName(vm),
|
||||
type: 'vm',
|
||||
status: vm.status === 'running' ? 'running' : 'stopped',
|
||||
node,
|
||||
|
|
@ -453,7 +453,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
|||
const vmid = parseLegacyVmid(container, platformData);
|
||||
mentionCandidates.push({
|
||||
id: `system-container:${node}:${vmid}`,
|
||||
displayName: getPreferredResourceDisplayName(container),
|
||||
label: getPreferredResourceDisplayName(container),
|
||||
type: 'system-container',
|
||||
status: container.status === 'running' ? 'running' : 'stopped',
|
||||
node,
|
||||
|
|
@ -482,7 +482,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
|||
: runtime.status || 'online';
|
||||
mentionCandidates.push({
|
||||
id: `agent:${dockerActionId}`,
|
||||
displayName,
|
||||
label: displayName,
|
||||
type: 'agent',
|
||||
status: runtimeStatus,
|
||||
});
|
||||
|
|
@ -494,7 +494,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
|||
: container.id;
|
||||
mentionCandidates.push({
|
||||
id: `docker:${dockerActionId}:${originalContainerId}`,
|
||||
displayName: getPreferredResourceDisplayName(container),
|
||||
label: getPreferredResourceDisplayName(container),
|
||||
type: 'app-container',
|
||||
status: container.status === 'running' ? 'running' : 'exited',
|
||||
node: hostnameOrId,
|
||||
|
|
@ -506,7 +506,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
|||
for (const node of nodes) {
|
||||
mentionCandidates.push({
|
||||
id: `node:${node.platformId || ''}:${node.name}`,
|
||||
displayName: getPreferredResourceDisplayName(node),
|
||||
label: getPreferredResourceDisplayName(node),
|
||||
type: 'agent',
|
||||
status: node.status,
|
||||
});
|
||||
|
|
@ -522,7 +522,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
|||
: agentResource.status;
|
||||
mentionCandidates.push({
|
||||
id: `agent:${agentActionId}`,
|
||||
displayName: name,
|
||||
label: name,
|
||||
type: 'agent',
|
||||
status: agentStatus,
|
||||
});
|
||||
|
|
@ -541,7 +541,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
|||
mentions.length > 0
|
||||
? mentions.map((mention) => ({
|
||||
id: mention.id,
|
||||
name: mention.displayName,
|
||||
name: mention.label,
|
||||
type: mention.type,
|
||||
node: mention.node,
|
||||
}))
|
||||
|
|
@ -597,7 +597,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
|||
// Replace @query with the resource name
|
||||
const before = currentInput.slice(0, startIndex);
|
||||
const after = currentInput.slice(cursorPos);
|
||||
const newValue = `${before}@${resource.displayName} ${after}`;
|
||||
const newValue = `${before}@${resource.label} ${after}`;
|
||||
|
||||
setInput(newValue);
|
||||
setMentionActive(false);
|
||||
|
|
@ -613,7 +613,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
|||
setTimeout(() => {
|
||||
if (textareaRef) {
|
||||
textareaRef.focus();
|
||||
const newCursorPos = startIndex + resource.displayName.length + 2; // +2 for @ and space
|
||||
const newCursorPos = startIndex + resource.label.length + 2; // +2 for @ and space
|
||||
textareaRef.setSelectionRange(newCursorPos, newCursorPos);
|
||||
}
|
||||
}, 0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue