551 lines
18 KiB
JavaScript
551 lines
18 KiB
JavaScript
function agentUiStatus(status) {
|
||
if (status === 'published') return 'published';
|
||
if (status === 'archived') return 'archived';
|
||
return status || 'draft';
|
||
}
|
||
|
||
function mapAgent(agent) {
|
||
var mapped = {
|
||
id: agent.id,
|
||
slug: agent.slug,
|
||
display_name: agent.display_name,
|
||
description: agent.description || '',
|
||
status: agentUiStatus(agent.status),
|
||
raw_status: agent.status,
|
||
operation_count: agent.operation_count || 0,
|
||
operation_ids: agent.operation_ids || [],
|
||
key_count: agent.key_count || 0,
|
||
calls_today: agent.calls_today || 0,
|
||
created_at: agent.created_at,
|
||
current_draft_version: agent.current_draft_version || 1,
|
||
latest_published_version: agent.latest_published_version,
|
||
mcp_endpoint: agent.mcp_endpoint || '',
|
||
};
|
||
|
||
return window.localizeDemoAgent ? window.localizeDemoAgent(mapped) : mapped;
|
||
}
|
||
|
||
function mapOperation(operation) {
|
||
var mapped = {
|
||
id: operation.id,
|
||
name: operation.name,
|
||
display_name: operation.display_name,
|
||
protocol: operation.protocol,
|
||
status: operation.status,
|
||
current_draft_version: operation.current_draft_version || 1,
|
||
latest_published_version: operation.latest_published_version,
|
||
};
|
||
|
||
return window.localizeDemoOperation ? window.localizeDemoOperation(mapped) : mapped;
|
||
}
|
||
|
||
function currentLocale() {
|
||
return localStorage.getItem('crank_lang') === 'ru' ? 'ru-RU' : 'en-US';
|
||
}
|
||
|
||
document.addEventListener('alpine:init', function() {
|
||
Alpine.data('agents', function() {
|
||
return {
|
||
agents: [],
|
||
operations: [],
|
||
capabilities: null,
|
||
loading: true,
|
||
loadError: '',
|
||
workspaceId: null,
|
||
|
||
agentSearch: '',
|
||
openDropdown: null,
|
||
drawerOpen: false,
|
||
drawerMode: 'create',
|
||
editingId: null,
|
||
saving: false,
|
||
|
||
form: {
|
||
display_name: '',
|
||
slug: '',
|
||
description: '',
|
||
status: 'published',
|
||
selectedOps: [],
|
||
},
|
||
|
||
opSearch: '',
|
||
slugManuallyEdited: false,
|
||
|
||
async init() {
|
||
var self = this;
|
||
|
||
await (window.whenWorkspacesReady ? window.whenWorkspacesReady() : Promise.resolve());
|
||
var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null;
|
||
this.workspaceId = workspace ? workspace.id : null;
|
||
await this.loadCapabilities();
|
||
await this.reload();
|
||
|
||
document.addEventListener('keydown', function(event) {
|
||
if (event.key === 'Escape' && self.drawerOpen) {
|
||
self.closeDrawer();
|
||
}
|
||
});
|
||
|
||
document.addEventListener('click', function() {
|
||
self.openDropdown = null;
|
||
});
|
||
|
||
window.addEventListener('crank:workspacechange', async function(event) {
|
||
self.workspaceId = event.detail ? event.detail.id : null;
|
||
await self.loadCapabilities();
|
||
await self.reload();
|
||
});
|
||
},
|
||
|
||
async loadCapabilities() {
|
||
if (!window.CrankApi || typeof window.CrankApi.getCapabilities !== 'function') {
|
||
this.capabilities = null;
|
||
return;
|
||
}
|
||
try {
|
||
this.capabilities = await window.CrankApi.getCapabilities();
|
||
} catch (_error) {
|
||
this.capabilities = null;
|
||
}
|
||
},
|
||
|
||
async reload() {
|
||
this.loading = true;
|
||
this.loadError = '';
|
||
|
||
if (!this.workspaceId || !window.CrankApi) {
|
||
this.agents = [];
|
||
this.operations = [];
|
||
this.loading = false;
|
||
this.loadError = this.tKey('agents.error.api');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
var responses = await Promise.all([
|
||
window.CrankApi.listAgents(this.workspaceId),
|
||
window.CrankApi.listOperations(this.workspaceId),
|
||
]);
|
||
this.agents = ((responses[0] && responses[0].items) || []).map(mapAgent);
|
||
this.operations = ((responses[1] && responses[1].items) || []).map(mapOperation);
|
||
} catch (error) {
|
||
this.agents = [];
|
||
this.operations = [];
|
||
this.loadError = error.message || this.tKey('agents.error.load');
|
||
}
|
||
|
||
this.loading = false;
|
||
},
|
||
|
||
get filteredAgents() {
|
||
var query = this.agentSearch.toLowerCase().trim();
|
||
if (!query) return this.agents;
|
||
return this.agents.filter(function(agent) {
|
||
return agent.display_name.toLowerCase().includes(query)
|
||
|| agent.slug.toLowerCase().includes(query)
|
||
|| (agent.description || '').toLowerCase().includes(query);
|
||
});
|
||
},
|
||
|
||
get totalCalls() {
|
||
return this.agents.reduce(function(sum, agent) {
|
||
return sum + (agent.calls_today || 0);
|
||
}, 0);
|
||
},
|
||
|
||
get activeCount() {
|
||
return this.agents.filter(function(agent) {
|
||
return agent.status === 'published';
|
||
}).length;
|
||
},
|
||
|
||
get totalOpsExposed() {
|
||
return this.agents.reduce(function(sum, agent) {
|
||
return sum + (agent.operation_count || 0);
|
||
}, 0);
|
||
},
|
||
|
||
get isCommunityBuild() {
|
||
return !!(this.capabilities && this.capabilities.edition === 'community');
|
||
},
|
||
|
||
agentCalloutText() {
|
||
if (this.isCommunityBuild) {
|
||
return this.tfKey('agents.callout.community', { count: this.operations.length });
|
||
}
|
||
return this.tfKey('agents.callout.default', { count: this.operations.length });
|
||
},
|
||
|
||
subtitleText() {
|
||
return this.tKey(this.isCommunityBuild ? 'agents.subtitle.community' : 'agents.subtitle');
|
||
},
|
||
|
||
emptyStateText() {
|
||
return this.tKey(this.isCommunityBuild ? 'agents.empty.initial.sub_community' : 'agents.empty.initial.sub');
|
||
},
|
||
|
||
drawerSubText() {
|
||
if (this.drawerMode === 'edit') {
|
||
return this.tKey('agents.drawer.edit_sub');
|
||
}
|
||
return this.tKey(this.isCommunityBuild ? 'agents.drawer.new_sub_community' : 'agents.drawer.new_sub');
|
||
},
|
||
|
||
operationsSubText() {
|
||
return this.tKey(this.isCommunityBuild ? 'agents.drawer.operations_sub_community' : 'agents.drawer.operations_sub');
|
||
},
|
||
|
||
get selectedOperations() {
|
||
var selected = this.form.selectedOps;
|
||
return this.operations.filter(function(operation) {
|
||
return selected.includes(operation.id);
|
||
});
|
||
},
|
||
|
||
get agentToolFindings() {
|
||
var findings = [];
|
||
var selected = this.selectedOperations;
|
||
if (selected.length > 8) {
|
||
findings.push(this.tKey('agents.drawer.finding.too_many_tools'));
|
||
}
|
||
|
||
for (var leftIndex = 0; leftIndex < selected.length; leftIndex += 1) {
|
||
for (var rightIndex = leftIndex + 1; rightIndex < selected.length; rightIndex += 1) {
|
||
if (this.operationsLookSimilar(selected[leftIndex], selected[rightIndex])) {
|
||
findings.push(this.tfKey('agents.drawer.finding.similar_tools', {
|
||
left: selected[leftIndex].display_name || selected[leftIndex].name,
|
||
right: selected[rightIndex].display_name || selected[rightIndex].name,
|
||
}));
|
||
return findings;
|
||
}
|
||
}
|
||
}
|
||
|
||
return findings;
|
||
},
|
||
|
||
get filteredOps() {
|
||
var query = this.opSearch.toLowerCase().trim();
|
||
if (!query) return this.operations;
|
||
return this.operations.filter(function(operation) {
|
||
return operation.name.toLowerCase().includes(query)
|
||
|| operation.display_name.toLowerCase().includes(query);
|
||
});
|
||
},
|
||
|
||
openCreate() {
|
||
this.drawerMode = 'create';
|
||
this.editingId = null;
|
||
this.form = {
|
||
display_name: '',
|
||
slug: '',
|
||
description: '',
|
||
status: 'published',
|
||
selectedOps: [],
|
||
};
|
||
this.opSearch = '';
|
||
this.slugManuallyEdited = false;
|
||
this.drawerOpen = true;
|
||
},
|
||
|
||
openEdit(agent) {
|
||
this.drawerMode = 'edit';
|
||
this.editingId = agent.id;
|
||
this.form = {
|
||
display_name: agent.display_name,
|
||
slug: agent.slug,
|
||
description: agent.description,
|
||
status: agent.raw_status || agent.status || 'draft',
|
||
selectedOps: [].concat(agent.operation_ids || []),
|
||
};
|
||
this.opSearch = '';
|
||
this.slugManuallyEdited = true;
|
||
this.drawerOpen = true;
|
||
},
|
||
|
||
closeDrawer() {
|
||
this.drawerOpen = false;
|
||
this.saving = false;
|
||
},
|
||
|
||
onNameInput(value) {
|
||
this.form.display_name = value;
|
||
if (!this.slugManuallyEdited) {
|
||
this.form.slug = value
|
||
.toLowerCase()
|
||
.replace(/\s+/g, '-')
|
||
.replace(/[^a-z0-9-]/g, '');
|
||
}
|
||
},
|
||
|
||
onSlugInput(value) {
|
||
this.form.slug = value.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
||
this.slugManuallyEdited = true;
|
||
},
|
||
|
||
toggleOp(operationId) {
|
||
var index = this.form.selectedOps.indexOf(operationId);
|
||
if (index === -1) {
|
||
this.form.selectedOps.push(operationId);
|
||
} else {
|
||
this.form.selectedOps.splice(index, 1);
|
||
}
|
||
},
|
||
|
||
isOpSelected(operationId) {
|
||
return this.form.selectedOps.includes(operationId);
|
||
},
|
||
|
||
operationsLookSimilar(left, right) {
|
||
var leftTokens = this.operationTokens(left);
|
||
var rightTokens = this.operationTokens(right);
|
||
if (!leftTokens.length || !rightTokens.length) return false;
|
||
var shared = leftTokens.filter(function(token) { return rightTokens.includes(token); }).length;
|
||
var smaller = Math.min(leftTokens.length, rightTokens.length);
|
||
return shared >= 2 && (shared / smaller) >= 0.6;
|
||
},
|
||
|
||
operationTokens(operation) {
|
||
var text = [
|
||
operation.name || '',
|
||
operation.display_name || '',
|
||
].join(' ').toLowerCase();
|
||
var tokens = [];
|
||
text.split(/[^a-z0-9а-яё]+/i).forEach(function(token) {
|
||
if (token.length < 3 || tokens.includes(token)) return;
|
||
tokens.push(token);
|
||
});
|
||
return tokens;
|
||
},
|
||
|
||
async saveAgent() {
|
||
var self = this;
|
||
if (
|
||
this.saving
|
||
|| !this.workspaceId
|
||
|| !this.form.display_name.trim()
|
||
|| !this.form.slug.trim()
|
||
) {
|
||
return;
|
||
}
|
||
|
||
this.saving = true;
|
||
|
||
try {
|
||
var agentId = this.editingId;
|
||
var currentVersion = 1;
|
||
var existingAgent = this.drawerMode === 'edit'
|
||
? this.agents.find(function(item) { return item.id === agentId; })
|
||
: null;
|
||
var previousStatus = existingAgent ? (existingAgent.raw_status || 'draft') : 'draft';
|
||
|
||
if (this.drawerMode === 'create') {
|
||
var created = await window.CrankApi.createAgent(this.workspaceId, {
|
||
slug: this.form.slug,
|
||
display_name: this.form.display_name,
|
||
description: this.form.description,
|
||
instructions: {},
|
||
tool_selection_policy: {},
|
||
});
|
||
agentId = created.agent_id;
|
||
currentVersion = created.version || 1;
|
||
} else {
|
||
await window.CrankApi.updateAgent(this.workspaceId, this.editingId, {
|
||
slug: this.form.slug,
|
||
display_name: this.form.display_name,
|
||
description: this.form.description,
|
||
});
|
||
var agent = await window.CrankApi.getAgent(this.workspaceId, this.editingId);
|
||
currentVersion = agent.current_draft_version || 1;
|
||
}
|
||
|
||
await window.CrankApi.saveAgentBindings(
|
||
this.workspaceId,
|
||
agentId,
|
||
this.form.selectedOps.map(function(operationId) {
|
||
var operation = self.operations.find(function(item) {
|
||
return item.id === operationId;
|
||
});
|
||
return {
|
||
operation_id: operationId,
|
||
operation_version: operation && operation.latest_published_version
|
||
? operation.latest_published_version
|
||
: operation && operation.current_draft_version
|
||
? operation.current_draft_version
|
||
: 1,
|
||
tool_name: operation ? operation.name : operationId,
|
||
tool_title: operation ? (operation.display_name || operation.name) : operationId,
|
||
tool_description_override: null,
|
||
enabled: true,
|
||
};
|
||
}),
|
||
);
|
||
|
||
if (this.form.status === 'published') {
|
||
await window.CrankApi.publishAgent(this.workspaceId, agentId, {
|
||
version: currentVersion,
|
||
});
|
||
} else if (this.form.status === 'archived') {
|
||
await window.CrankApi.archiveAgent(this.workspaceId, agentId);
|
||
} else if (this.drawerMode === 'edit' && previousStatus !== 'draft') {
|
||
await window.CrankApi.unpublishAgent(this.workspaceId, agentId);
|
||
}
|
||
|
||
await this.reload();
|
||
if (window.CrankUi) {
|
||
window.CrankUi.success(
|
||
this.tfKey('agents.toast.saved_message', {
|
||
name: this.form.display_name,
|
||
count: this.form.selectedOps.length
|
||
}),
|
||
this.drawerMode === 'create'
|
||
? this.tKey('agents.toast.saved_title_create')
|
||
: this.tKey('agents.toast.saved_title_update')
|
||
);
|
||
}
|
||
this.closeDrawer();
|
||
} catch (error) {
|
||
if (window.CrankUi) {
|
||
window.CrankUi.error(
|
||
error.message || this.tKey('agents.toast.save_error_message'),
|
||
this.tKey('agents.toast.save_error_title')
|
||
);
|
||
}
|
||
this.saving = false;
|
||
}
|
||
},
|
||
|
||
async deleteAgent(id) {
|
||
if (!confirm(this.tKey('agents.toast.delete_confirm'))) return;
|
||
|
||
try {
|
||
var agent = this.agents.find(function(item) { return item.id === id; });
|
||
await window.CrankApi.deleteAgent(this.workspaceId, id);
|
||
await this.reload();
|
||
if (window.CrankUi) {
|
||
window.CrankUi.success(
|
||
this.tfKey('agents.toast.delete_message', { name: agent ? agent.display_name : '' }),
|
||
this.tKey('agents.toast.delete_title')
|
||
);
|
||
}
|
||
} catch (error) {
|
||
if (window.CrankUi) {
|
||
window.CrankUi.error(
|
||
error.message || this.tKey('agents.toast.delete_error_message'),
|
||
this.tKey('agents.toast.delete_error_title')
|
||
);
|
||
}
|
||
}
|
||
},
|
||
|
||
async applyLifecycle(agent, action) {
|
||
if (!this.workspaceId || !window.CrankApi) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
if (action === 'publish') {
|
||
await window.CrankApi.publishAgent(this.workspaceId, agent.id, {
|
||
version: agent.current_draft_version || 1,
|
||
});
|
||
} else if (action === 'unpublish') {
|
||
await window.CrankApi.unpublishAgent(this.workspaceId, agent.id);
|
||
} else if (action === 'archive') {
|
||
await window.CrankApi.archiveAgent(this.workspaceId, agent.id);
|
||
}
|
||
await this.reload();
|
||
if (window.CrankUi) {
|
||
window.CrankUi.success(
|
||
action === 'publish'
|
||
? this.tfKey('agents.toast.lifecycle_publish', { name: agent.display_name })
|
||
: action === 'unpublish'
|
||
? this.tfKey('agents.toast.lifecycle_unpublish', { name: agent.display_name })
|
||
: this.tfKey('agents.toast.lifecycle_archive', { name: agent.display_name }),
|
||
this.tKey('agents.toast.lifecycle_title')
|
||
);
|
||
}
|
||
} catch (error) {
|
||
if (window.CrankUi) {
|
||
window.CrankUi.error(
|
||
error.message || this.tKey('agents.toast.lifecycle_error_message'),
|
||
this.tKey('agents.toast.lifecycle_error_title')
|
||
);
|
||
}
|
||
}
|
||
},
|
||
|
||
lifecycleAction(agent) {
|
||
if (agent.raw_status === 'published') {
|
||
return { key: 'unpublish', label: this.tKey('agents.lifecycle.unpublish') };
|
||
}
|
||
if (agent.raw_status === 'archived') {
|
||
return { key: 'unpublish', label: this.tKey('agents.lifecycle.restore_draft') };
|
||
}
|
||
return { key: 'publish', label: this.tKey('agents.lifecycle.publish') };
|
||
},
|
||
|
||
lifecycleLabel(agent) {
|
||
if (agent.raw_status === 'published') return this.tKey('agents.lifecycle.published');
|
||
if (agent.raw_status === 'archived') return this.tKey('agents.lifecycle.archived');
|
||
return this.tKey('agents.lifecycle.draft');
|
||
},
|
||
|
||
mcpEndpoint(agent) {
|
||
if (agent.mcp_endpoint) return agent.mcp_endpoint;
|
||
var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null;
|
||
var workspaceSlug = workspace ? workspace.slug : 'default';
|
||
return '/mcp/v1/' + workspaceSlug + '/' + agent.slug;
|
||
},
|
||
|
||
endpointHelpText(agent) {
|
||
return this.tfKey(
|
||
this.isCommunityBuild ? 'agents.card.endpoint_help_community' : 'agents.card.endpoint_help',
|
||
{ count: agent.key_count || 0 }
|
||
);
|
||
},
|
||
|
||
copyEndpoint(agent) {
|
||
var endpoint = this.mcpEndpoint(agent);
|
||
navigator.clipboard.writeText(endpoint).catch(function() {});
|
||
if (window.CrankUi) {
|
||
window.CrankUi.info(endpoint, this.tKey('agents.toast.endpoint_title'));
|
||
}
|
||
},
|
||
|
||
statusClass(status) {
|
||
return 'agent-status-badge agent-status-' + status;
|
||
},
|
||
|
||
protocolBadge(operation) {
|
||
return 'badge badge-rest';
|
||
},
|
||
|
||
protocolLabel(operation) {
|
||
return 'REST';
|
||
},
|
||
|
||
formatDate(dateStr) {
|
||
if (!dateStr) return '—';
|
||
return new Date(dateStr).toLocaleDateString(currentLocale(), {
|
||
month: 'short',
|
||
day: 'numeric',
|
||
year: 'numeric',
|
||
});
|
||
},
|
||
|
||
formatCalls(value) {
|
||
if (!value) return '0';
|
||
return value >= 1000 ? (value / 1000).toFixed(1) + 'k' : String(value);
|
||
},
|
||
|
||
tKey(key) {
|
||
return window.t ? t(key) : key;
|
||
},
|
||
|
||
tfKey(key, vars) {
|
||
return window.tf ? tf(key, vars) : this.tKey(key);
|
||
},
|
||
};
|
||
});
|
||
});
|