Files
SuperCharged-Claude-Code-Up…/dexto/packages/webui/components/hooks/useResourceContent.ts
admin b52318eeae feat: Add intelligent auto-router and enhanced integrations
- Add intelligent-router.sh hook for automatic agent routing
- Add AUTO-TRIGGER-SUMMARY.md documentation
- Add FINAL-INTEGRATION-SUMMARY.md documentation
- Complete Prometheus integration (6 commands + 4 tools)
- Complete Dexto integration (12 commands + 5 tools)
- Enhanced Ralph with access to all agents
- Fix /clawd command (removed disable-model-invocation)
- Update hooks.json to v5 with intelligent routing
- 291 total skills now available
- All 21 commands with automatic routing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-28 00:27:56 +04:00

204 lines
6.5 KiB
TypeScript

import { useMemo } from 'react';
import { useQueries } from '@tanstack/react-query';
import { client } from '@/lib/client';
type NormalizedResourceItem =
| {
kind: 'text';
text: string;
mimeType?: string;
}
| {
kind: 'image';
src: string;
mimeType: string;
alt?: string;
}
| {
kind: 'audio';
src: string;
mimeType: string;
filename?: string;
}
| {
kind: 'video';
src: string;
mimeType: string;
filename?: string;
}
| {
kind: 'file';
src?: string;
mimeType?: string;
filename?: string;
};
export interface NormalizedResource {
uri: string;
name?: string;
meta?: Record<string, unknown>;
items: NormalizedResourceItem[];
}
export interface ResourceState {
status: 'loading' | 'loaded' | 'error';
data?: NormalizedResource;
error?: string;
}
type ResourceStateMap = Record<string, ResourceState>;
function buildDataUrl(base64: string, mimeType: string): string {
return `data:${mimeType};base64,${base64}`;
}
function normalizeResource(uri: string, payload: any): NormalizedResource {
const contents = Array.isArray(payload?.contents) ? payload.contents : [];
const meta = (payload?._meta ?? {}) as Record<string, unknown>;
const name =
(typeof meta.originalName === 'string' && meta.originalName.trim().length > 0
? meta.originalName
: undefined) || uri;
const items: NormalizedResourceItem[] = [];
for (const item of contents) {
if (!item || typeof item !== 'object') continue;
if (typeof (item as { text?: unknown }).text === 'string') {
items.push({
kind: 'text',
text: (item as { text: string }).text,
mimeType: typeof item.mimeType === 'string' ? item.mimeType : undefined,
});
continue;
}
const blobData = typeof item.blob === 'string' ? item.blob : undefined;
const rawData = typeof item.data === 'string' ? item.data : undefined;
const mimeType = typeof item.mimeType === 'string' ? item.mimeType : undefined;
const filename = typeof item.filename === 'string' ? item.filename : undefined;
if ((blobData || rawData) && mimeType) {
const base64 = blobData ?? rawData!;
const src = buildDataUrl(base64, mimeType);
if (mimeType.startsWith('image/')) {
items.push({
kind: 'image',
src,
mimeType,
alt: filename || name,
});
} else if (mimeType.startsWith('audio/')) {
items.push({
kind: 'audio',
src,
mimeType,
filename: filename || name,
});
} else if (mimeType.startsWith('video/')) {
items.push({
kind: 'video',
src,
mimeType,
filename: filename || name,
});
} else {
items.push({
kind: 'file',
src,
mimeType,
filename: filename || name,
});
}
continue;
}
if (mimeType && mimeType.startsWith('text/') && typeof item.value === 'string') {
items.push({
kind: 'text',
text: item.value,
mimeType,
});
}
}
return {
uri,
name,
meta,
items,
};
}
async function fetchResourceContent(uri: string): Promise<NormalizedResource> {
const response = await client.api.resources[':resourceId'].content.$get({
param: { resourceId: encodeURIComponent(uri) },
});
const body = await response.json();
const contentPayload = body?.content;
if (!contentPayload) {
throw new Error('No content returned for resource');
}
return normalizeResource(uri, contentPayload);
}
export function useResourceContent(resourceUris: string[]): ResourceStateMap {
// Serialize array for stable dependency comparison.
// Arrays are compared by reference in React, so ['a','b'] !== ['a','b'] even though
// values are identical. Serializing to 'a|b' allows value-based comparison to avoid
// unnecessary re-computation when parent passes new array reference with same contents.
const serializedUris = resourceUris.join('|');
const normalizedUris = useMemo(() => {
const seen = new Set<string>();
const ordered: string[] = [];
for (const uri of resourceUris) {
if (!uri || typeof uri !== 'string') continue;
const trimmed = uri.trim();
if (!trimmed || seen.has(trimmed)) continue;
seen.add(trimmed);
ordered.push(trimmed);
}
return ordered;
// We use resourceUris inside but only depend on serializedUris. This is safe because
// serializedUris is derived from resourceUris - when the string changes, the array
// values changed too. This is an intentional optimization to prevent re-runs when
// array reference changes but values remain the same.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [serializedUris]);
const queries = useQueries({
queries: normalizedUris.map((uri) => ({
queryKey: ['resourceContent', uri],
queryFn: () => fetchResourceContent(uri),
enabled: !!uri,
retry: false,
})),
});
const resources: ResourceStateMap = useMemo(() => {
const result: ResourceStateMap = {};
queries.forEach((query, index) => {
const uri = normalizedUris[index];
if (!uri) return;
if (query.isLoading) {
result[uri] = { status: 'loading' };
} else if (query.error) {
result[uri] = {
status: 'error',
error: query.error instanceof Error ? query.error.message : String(query.error),
};
} else if (query.data) {
result[uri] = { status: 'loaded', data: query.data };
}
});
return result;
}, [queries, normalizedUris]);
return resources;
}
export type { NormalizedResourceItem };