git list refactor
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
// [DEF:api_module:Module]
|
||||
// @TIER: STANDARD
|
||||
// @SEMANTICS: api, client, fetch, rest
|
||||
// @PURPOSE: Handles all communication with the backend API.
|
||||
// @LAYER: Infra-API
|
||||
|
||||
import { addToast } from './toasts.js';
|
||||
import { PUBLIC_WS_URL } from '$env/static/public';
|
||||
|
||||
// [DEF:api_module:Module]
|
||||
// @TIER: STANDARD
|
||||
// @SEMANTICS: api, client, fetch, rest
|
||||
// @PURPOSE: Handles all communication with the backend API.
|
||||
// @LAYER: Infra-API
|
||||
|
||||
import { addToast } from './toasts.js';
|
||||
import { PUBLIC_WS_URL } from '$env/static/public';
|
||||
|
||||
const API_BASE_URL = '/api';
|
||||
|
||||
// [DEF:buildApiError:Function]
|
||||
@@ -40,52 +40,69 @@ function notifyApiError(error) {
|
||||
addToast(error.message, 'error');
|
||||
}
|
||||
// [/DEF:notifyApiError:Function]
|
||||
|
||||
// [DEF:getWsUrl:Function]
|
||||
// @PURPOSE: Returns the WebSocket URL for a specific task, with fallback logic.
|
||||
// @PRE: taskId is provided.
|
||||
// @POST: Returns valid WebSocket URL string.
|
||||
// @PARAM: taskId (string) - The ID of the task.
|
||||
// @RETURN: string - The WebSocket URL.
|
||||
export const getWsUrl = (taskId) => {
|
||||
let baseUrl = PUBLIC_WS_URL;
|
||||
if (!baseUrl) {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
// Use the current host and port to allow Vite proxy to handle the connection
|
||||
baseUrl = `${protocol}//${window.location.host}`;
|
||||
}
|
||||
return `${baseUrl}/ws/logs/${taskId}`;
|
||||
};
|
||||
// [/DEF:getWsUrl:Function]
|
||||
|
||||
// [DEF:getAuthHeaders:Function]
|
||||
// @PURPOSE: Returns headers with Authorization if token exists.
|
||||
function getAuthHeaders() {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (typeof window !== 'undefined') {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
// [/DEF:getAuthHeaders:Function]
|
||||
|
||||
// [DEF:fetchApi:Function]
|
||||
// @PURPOSE: Generic GET request wrapper.
|
||||
// @PRE: endpoint string is provided.
|
||||
// @POST: Returns Promise resolving to JSON data or throws on error.
|
||||
// @PARAM: endpoint (string) - API endpoint.
|
||||
// @RETURN: Promise<any> - JSON response.
|
||||
|
||||
// [DEF:shouldSuppressApiErrorToast:Function]
|
||||
// @PURPOSE: Avoid noisy toasts for expected non-critical API failures.
|
||||
// @PRE: endpoint can be empty; error can be null.
|
||||
// @POST: Returns true only for explicitly allowed suppressed scenarios.
|
||||
function shouldSuppressApiErrorToast(endpoint, error) {
|
||||
const isGitStatusEndpoint =
|
||||
typeof endpoint === 'string' &&
|
||||
endpoint.startsWith('/git/repositories/') &&
|
||||
endpoint.endsWith('/status');
|
||||
const isNoRepoError =
|
||||
(error?.status === 400 || error?.status === 404) &&
|
||||
/Repository for dashboard .* not found/i.test(String(error?.message || ''));
|
||||
|
||||
return isGitStatusEndpoint && isNoRepoError;
|
||||
}
|
||||
// [/DEF:shouldSuppressApiErrorToast:Function]
|
||||
|
||||
// [DEF:getWsUrl:Function]
|
||||
// @PURPOSE: Returns the WebSocket URL for a specific task, with fallback logic.
|
||||
// @PRE: taskId is provided.
|
||||
// @POST: Returns valid WebSocket URL string.
|
||||
// @PARAM: taskId (string) - The ID of the task.
|
||||
// @RETURN: string - The WebSocket URL.
|
||||
export const getWsUrl = (taskId) => {
|
||||
let baseUrl = PUBLIC_WS_URL;
|
||||
if (!baseUrl) {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
// Use the current host and port to allow Vite proxy to handle the connection
|
||||
baseUrl = `${protocol}//${window.location.host}`;
|
||||
}
|
||||
return `${baseUrl}/ws/logs/${taskId}`;
|
||||
};
|
||||
// [/DEF:getWsUrl:Function]
|
||||
|
||||
// [DEF:getAuthHeaders:Function]
|
||||
// @PURPOSE: Returns headers with Authorization if token exists.
|
||||
function getAuthHeaders() {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (typeof window !== 'undefined') {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
// [/DEF:getAuthHeaders:Function]
|
||||
|
||||
// [DEF:fetchApi:Function]
|
||||
// @PURPOSE: Generic GET request wrapper.
|
||||
// @PRE: endpoint string is provided.
|
||||
// @POST: Returns Promise resolving to JSON data or throws on error.
|
||||
// @PARAM: endpoint (string) - API endpoint.
|
||||
// @RETURN: Promise<any> - JSON response.
|
||||
async function fetchApi(endpoint) {
|
||||
try {
|
||||
console.log(`[api.fetchApi][Action] Fetching from context={{'endpoint': '${endpoint}'}}`);
|
||||
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
try {
|
||||
console.log(`[api.fetchApi][Action] Fetching from context={{'endpoint': '${endpoint}'}}`);
|
||||
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
console.log(`[api.fetchApi][Action] Received response context={{'status': ${response.status}, 'ok': ${response.ok}}}`);
|
||||
if (!response.ok) {
|
||||
throw await buildApiError(response);
|
||||
@@ -129,22 +146,22 @@ async function fetchApiBlob(endpoint, options = {}) {
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchApiBlob:Function]
|
||||
|
||||
// [DEF:postApi:Function]
|
||||
// @PURPOSE: Generic POST request wrapper.
|
||||
// @PRE: endpoint and body are provided.
|
||||
// @POST: Returns Promise resolving to JSON data or throws on error.
|
||||
// @PARAM: endpoint (string) - API endpoint.
|
||||
// @PARAM: body (object) - Request payload.
|
||||
// @RETURN: Promise<any> - JSON response.
|
||||
async function postApi(endpoint, body) {
|
||||
try {
|
||||
console.log(`[api.postApi][Action] Posting to context={{'endpoint': '${endpoint}'}}`);
|
||||
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
// [DEF:postApi:Function]
|
||||
// @PURPOSE: Generic POST request wrapper.
|
||||
// @PRE: endpoint and body are provided.
|
||||
// @POST: Returns Promise resolving to JSON data or throws on error.
|
||||
// @PARAM: endpoint (string) - API endpoint.
|
||||
// @PARAM: body (object) - Request payload.
|
||||
// @RETURN: Promise<any> - JSON response.
|
||||
async function postApi(endpoint, body) {
|
||||
try {
|
||||
console.log(`[api.postApi][Action] Posting to context={{'endpoint': '${endpoint}'}}`);
|
||||
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
console.log(`[api.postApi][Action] Received response context={{'status': ${response.status}, 'ok': ${response.ok}}}`);
|
||||
if (!response.ok) {
|
||||
throw await buildApiError(response);
|
||||
@@ -157,22 +174,22 @@ async function postApi(endpoint, body) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// [/DEF:postApi:Function]
|
||||
|
||||
// [DEF:requestApi:Function]
|
||||
// @PURPOSE: Generic request wrapper.
|
||||
// @PRE: endpoint and method are provided.
|
||||
// @POST: Returns Promise resolving to JSON data or throws on error.
|
||||
async function requestApi(endpoint, method = 'GET', body = null) {
|
||||
try {
|
||||
console.log(`[api.requestApi][Action] ${method} to context={{'endpoint': '${endpoint}'}}`);
|
||||
const options = {
|
||||
method,
|
||||
headers: getAuthHeaders(),
|
||||
};
|
||||
if (body) {
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
// [/DEF:postApi:Function]
|
||||
|
||||
// [DEF:requestApi:Function]
|
||||
// @PURPOSE: Generic request wrapper.
|
||||
// @PRE: endpoint and method are provided.
|
||||
// @POST: Returns Promise resolving to JSON data or throws on error.
|
||||
async function requestApi(endpoint, method = 'GET', body = null) {
|
||||
try {
|
||||
console.log(`[api.requestApi][Action] ${method} to context={{'endpoint': '${endpoint}'}}`);
|
||||
const options = {
|
||||
method,
|
||||
headers: getAuthHeaders(),
|
||||
};
|
||||
if (body) {
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
const response = await fetch(`${API_BASE_URL}${endpoint}`, options);
|
||||
console.log(`[api.requestApi][Action] Received response context={{'status': ${response.status}, 'ok': ${response.ok}}}`);
|
||||
if (!response.ok) {
|
||||
@@ -187,126 +204,129 @@ async function requestApi(endpoint, method = 'GET', body = null) {
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error(`[api.requestApi][Coherence:Failed] Error ${method} to ${endpoint}:`, error);
|
||||
notifyApiError(error);
|
||||
if (!shouldSuppressApiErrorToast(endpoint, error)) {
|
||||
notifyApiError(error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// [/DEF:requestApi:Function]
|
||||
|
||||
// [DEF:api:Data]
|
||||
// @PURPOSE: API client object with specific methods.
|
||||
export const api = {
|
||||
fetchApi,
|
||||
postApi,
|
||||
requestApi,
|
||||
getPlugins: () => fetchApi('/plugins'),
|
||||
// [/DEF:requestApi:Function]
|
||||
|
||||
// [DEF:api:Data]
|
||||
// @PURPOSE: API client object with specific methods.
|
||||
export const api = {
|
||||
fetchApi,
|
||||
postApi,
|
||||
requestApi,
|
||||
getPlugins: () => fetchApi('/plugins'),
|
||||
getTasks: (options = {}) => {
|
||||
const params = new URLSearchParams();
|
||||
if (options.limit != null) params.append('limit', String(options.limit));
|
||||
if (options.offset != null) params.append('offset', String(options.offset));
|
||||
if (options.status) params.append('status', options.status);
|
||||
if (options.task_type) params.append('task_type', options.task_type);
|
||||
if (options.completed_only != null) params.append('completed_only', String(Boolean(options.completed_only)));
|
||||
if (Array.isArray(options.plugin_id)) {
|
||||
options.plugin_id.forEach((pluginId) => params.append('plugin_id', pluginId));
|
||||
}
|
||||
const query = params.toString();
|
||||
return fetchApi(`/tasks${query ? `?${query}` : ''}`);
|
||||
const params = new URLSearchParams();
|
||||
if (options.limit != null) params.append('limit', String(options.limit));
|
||||
if (options.offset != null) params.append('offset', String(options.offset));
|
||||
if (options.status) params.append('status', options.status);
|
||||
if (options.task_type) params.append('task_type', options.task_type);
|
||||
if (options.completed_only != null) params.append('completed_only', String(Boolean(options.completed_only)));
|
||||
if (Array.isArray(options.plugin_id)) {
|
||||
options.plugin_id.forEach((pluginId) => params.append('plugin_id', pluginId));
|
||||
}
|
||||
const query = params.toString();
|
||||
return fetchApi(`/tasks${query ? `?${query}` : ''}`);
|
||||
},
|
||||
getTask: (taskId) => fetchApi(`/tasks/${taskId}`),
|
||||
getTaskLogs: (taskId, options = {}) => {
|
||||
const params = new URLSearchParams();
|
||||
if (options.level) params.append('level', options.level);
|
||||
if (options.source) params.append('source', options.source);
|
||||
if (options.search) params.append('search', options.search);
|
||||
if (options.offset != null) params.append('offset', String(options.offset));
|
||||
if (options.limit != null) params.append('limit', String(options.limit));
|
||||
const query = params.toString();
|
||||
return fetchApi(`/tasks/${taskId}/logs${query ? `?${query}` : ''}`);
|
||||
const params = new URLSearchParams();
|
||||
if (options.level) params.append('level', options.level);
|
||||
if (options.source) params.append('source', options.source);
|
||||
if (options.search) params.append('search', options.search);
|
||||
if (options.offset != null) params.append('offset', String(options.offset));
|
||||
if (options.limit != null) params.append('limit', String(options.limit));
|
||||
const query = params.toString();
|
||||
return fetchApi(`/tasks/${taskId}/logs${query ? `?${query}` : ''}`);
|
||||
},
|
||||
createTask: (pluginId, params) => postApi('/tasks', { plugin_id: pluginId, params }),
|
||||
|
||||
|
||||
// Settings
|
||||
getSettings: () => fetchApi('/settings'),
|
||||
updateGlobalSettings: (settings) => requestApi('/settings/global', 'PATCH', settings),
|
||||
getEnvironments: () => fetchApi('/settings/environments'),
|
||||
addEnvironment: (env) => postApi('/settings/environments', env),
|
||||
updateEnvironment: (id, env) => requestApi(`/settings/environments/${id}`, 'PUT', env),
|
||||
deleteEnvironment: (id) => requestApi(`/settings/environments/${id}`, 'DELETE'),
|
||||
testEnvironmentConnection: (id) => postApi(`/settings/environments/${id}/test`, {}),
|
||||
updateEnvironmentSchedule: (id, schedule) => requestApi(`/environments/${id}/schedule`, 'PUT', schedule),
|
||||
getStorageSettings: () => fetchApi('/settings/storage'),
|
||||
updateStorageSettings: (storage) => requestApi('/settings/storage', 'PUT', storage),
|
||||
getSettings: () => fetchApi('/settings'),
|
||||
updateGlobalSettings: (settings) => requestApi('/settings/global', 'PATCH', settings),
|
||||
getEnvironments: () => fetchApi('/settings/environments'),
|
||||
addEnvironment: (env) => postApi('/settings/environments', env),
|
||||
updateEnvironment: (id, env) => requestApi(`/settings/environments/${id}`, 'PUT', env),
|
||||
deleteEnvironment: (id) => requestApi(`/settings/environments/${id}`, 'DELETE'),
|
||||
testEnvironmentConnection: (id) => postApi(`/settings/environments/${id}/test`, {}),
|
||||
updateEnvironmentSchedule: (id, schedule) => requestApi(`/environments/${id}/schedule`, 'PUT', schedule),
|
||||
getStorageSettings: () => fetchApi('/settings/storage'),
|
||||
updateStorageSettings: (storage) => requestApi('/settings/storage', 'PUT', storage),
|
||||
getEnvironmentsList: () => fetchApi('/environments'),
|
||||
getLlmStatus: () => fetchApi('/llm/status'),
|
||||
getEnvironmentDatabases: (id) => fetchApi(`/environments/${id}/databases`),
|
||||
getStorageFileBlob: (path) =>
|
||||
fetchApiBlob(`/storage/file?path=${encodeURIComponent(path)}`),
|
||||
|
||||
fetchApiBlob(`/storage/file?path=${encodeURIComponent(path)}`),
|
||||
|
||||
// Dashboards
|
||||
getDashboards: (envId, options = {}) => {
|
||||
const params = new URLSearchParams({ env_id: envId });
|
||||
if (options.search) params.append('search', options.search);
|
||||
if (options.page) params.append('page', options.page);
|
||||
if (options.page_size) params.append('page_size', options.page_size);
|
||||
return fetchApi(`/dashboards?${params.toString()}`);
|
||||
const params = new URLSearchParams({ env_id: envId });
|
||||
if (options.search) params.append('search', options.search);
|
||||
if (options.page) params.append('page', options.page);
|
||||
if (options.page_size) params.append('page_size', options.page_size);
|
||||
return fetchApi(`/dashboards?${params.toString()}`);
|
||||
},
|
||||
getDashboardDetail: (envId, dashboardId) => fetchApi(`/dashboards/${dashboardId}?env_id=${envId}`),
|
||||
getDashboardTaskHistory: (envId, dashboardId, options = {}) => {
|
||||
const params = new URLSearchParams();
|
||||
if (envId) params.append('env_id', envId);
|
||||
if (options.limit) params.append('limit', options.limit);
|
||||
return fetchApi(`/dashboards/${dashboardId}/tasks?${params.toString()}`);
|
||||
const params = new URLSearchParams();
|
||||
if (envId) params.append('env_id', envId);
|
||||
if (options.limit) params.append('limit', options.limit);
|
||||
return fetchApi(`/dashboards/${dashboardId}/tasks?${params.toString()}`);
|
||||
},
|
||||
getDashboardThumbnail: (envId, dashboardId, options = {}) => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('env_id', envId);
|
||||
if (options.force != null) params.append('force', String(Boolean(options.force)));
|
||||
return fetchApiBlob(`/dashboards/${dashboardId}/thumbnail?${params.toString()}`, { notifyError: false });
|
||||
const params = new URLSearchParams();
|
||||
params.append('env_id', envId);
|
||||
if (options.force != null) params.append('force', String(Boolean(options.force)));
|
||||
return fetchApiBlob(`/dashboards/${dashboardId}/thumbnail?${params.toString()}`, { notifyError: false });
|
||||
},
|
||||
getDatabaseMappings: (sourceEnvId, targetEnvId) => fetchApi(`/dashboards/db-mappings?source_env_id=${sourceEnvId}&target_env_id=${targetEnvId}`),
|
||||
|
||||
// Datasets
|
||||
getDatasets: (envId, options = {}) => {
|
||||
const params = new URLSearchParams({ env_id: envId });
|
||||
if (options.search) params.append('search', options.search);
|
||||
if (options.page) params.append('page', options.page);
|
||||
if (options.page_size) params.append('page_size', options.page_size);
|
||||
return fetchApi(`/datasets?${params.toString()}`);
|
||||
},
|
||||
getDatasetIds: (envId, options = {}) => {
|
||||
const params = new URLSearchParams({ env_id: envId });
|
||||
if (options.search) params.append('search', options.search);
|
||||
return fetchApi(`/datasets/ids?${params.toString()}`);
|
||||
},
|
||||
getDatasetDetail: (envId, datasetId) => fetchApi(`/datasets/${datasetId}?env_id=${envId}`),
|
||||
|
||||
// Settings
|
||||
getConsolidatedSettings: () => fetchApi('/settings/consolidated'),
|
||||
updateConsolidatedSettings: (settings) => requestApi('/settings/consolidated', 'PATCH', settings),
|
||||
};
|
||||
// [/DEF:api:Data]
|
||||
|
||||
// [/DEF:api_module:Module]
|
||||
|
||||
// Export individual functions for easier use in components
|
||||
export { requestApi };
|
||||
export const getPlugins = api.getPlugins;
|
||||
export const getTasks = api.getTasks;
|
||||
export const getTask = api.getTask;
|
||||
export const createTask = api.createTask;
|
||||
export const getSettings = api.getSettings;
|
||||
export const updateGlobalSettings = api.updateGlobalSettings;
|
||||
export const getEnvironments = api.getEnvironments;
|
||||
export const addEnvironment = api.addEnvironment;
|
||||
export const updateEnvironment = api.updateEnvironment;
|
||||
export const deleteEnvironment = api.deleteEnvironment;
|
||||
export const testEnvironmentConnection = api.testEnvironmentConnection;
|
||||
export const updateEnvironmentSchedule = api.updateEnvironmentSchedule;
|
||||
export const getEnvironmentsList = api.getEnvironmentsList;
|
||||
export const getStorageSettings = api.getStorageSettings;
|
||||
export const updateStorageSettings = api.updateStorageSettings;
|
||||
export const getDashboards = api.getDashboards;
|
||||
export const getDatasets = api.getDatasets;
|
||||
export const getConsolidatedSettings = api.getConsolidatedSettings;
|
||||
export const updateConsolidatedSettings = api.updateConsolidatedSettings;
|
||||
calculateMigrationDryRun: (payload) => postApi('/migration/dry-run', payload),
|
||||
|
||||
// Datasets
|
||||
getDatasets: (envId, options = {}) => {
|
||||
const params = new URLSearchParams({ env_id: envId });
|
||||
if (options.search) params.append('search', options.search);
|
||||
if (options.page) params.append('page', options.page);
|
||||
if (options.page_size) params.append('page_size', options.page_size);
|
||||
return fetchApi(`/datasets?${params.toString()}`);
|
||||
},
|
||||
getDatasetIds: (envId, options = {}) => {
|
||||
const params = new URLSearchParams({ env_id: envId });
|
||||
if (options.search) params.append('search', options.search);
|
||||
return fetchApi(`/datasets/ids?${params.toString()}`);
|
||||
},
|
||||
getDatasetDetail: (envId, datasetId) => fetchApi(`/datasets/${datasetId}?env_id=${envId}`),
|
||||
|
||||
// Settings
|
||||
getConsolidatedSettings: () => fetchApi('/settings/consolidated'),
|
||||
updateConsolidatedSettings: (settings) => requestApi('/settings/consolidated', 'PATCH', settings),
|
||||
};
|
||||
// [/DEF:api:Data]
|
||||
|
||||
// [/DEF:api_module:Module]
|
||||
|
||||
// Export individual functions for easier use in components
|
||||
export { requestApi };
|
||||
export const getPlugins = api.getPlugins;
|
||||
export const getTasks = api.getTasks;
|
||||
export const getTask = api.getTask;
|
||||
export const createTask = api.createTask;
|
||||
export const getSettings = api.getSettings;
|
||||
export const updateGlobalSettings = api.updateGlobalSettings;
|
||||
export const getEnvironments = api.getEnvironments;
|
||||
export const addEnvironment = api.addEnvironment;
|
||||
export const updateEnvironment = api.updateEnvironment;
|
||||
export const deleteEnvironment = api.deleteEnvironment;
|
||||
export const testEnvironmentConnection = api.testEnvironmentConnection;
|
||||
export const updateEnvironmentSchedule = api.updateEnvironmentSchedule;
|
||||
export const getEnvironmentsList = api.getEnvironmentsList;
|
||||
export const getStorageSettings = api.getStorageSettings;
|
||||
export const updateStorageSettings = api.updateStorageSettings;
|
||||
export const getDashboards = api.getDashboards;
|
||||
export const getDatasets = api.getDatasets;
|
||||
export const getConsolidatedSettings = api.getConsolidatedSettings;
|
||||
export const updateConsolidatedSettings = api.updateConsolidatedSettings;
|
||||
|
||||
@@ -75,4 +75,14 @@ export function getAssistantConversations(
|
||||
return requestApi(`/assistant/conversations?${params.toString()}`, 'GET');
|
||||
}
|
||||
// [/DEF:getAssistantConversations:Function]
|
||||
|
||||
// [DEF:deleteAssistantConversation:Function]
|
||||
// @PURPOSE: Soft-delete or hard-delete a conversation.
|
||||
// @PRE: conversationId string is provided.
|
||||
// @POST: Returns success status.
|
||||
export function deleteAssistantConversation(conversationId) {
|
||||
return requestApi(`/assistant/conversations/${conversationId}`, 'DELETE');
|
||||
}
|
||||
// [/DEF:deleteAssistantConversation:Function]
|
||||
|
||||
// [/DEF:frontend.src.lib.api.assistant:Module]
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
cancelAssistantOperation,
|
||||
getAssistantHistory,
|
||||
getAssistantConversations,
|
||||
deleteAssistantConversation,
|
||||
} from "$lib/api/assistant.js";
|
||||
import { api } from "$lib/api.js";
|
||||
import { gitService } from "../../../services/gitService.js";
|
||||
@@ -175,6 +176,32 @@
|
||||
}
|
||||
// [/DEF:loadConversations:Function]
|
||||
|
||||
// [DEF:removeConversation:Function]
|
||||
// @PURPOSE: Removes a conversation from the list and deletes it from the backend.
|
||||
// @PRE: conversationId string is provided.
|
||||
// @POST: It is soft-deleted from the API and removed from local UI. If active, reset state.
|
||||
async function removeConversation(e, conversationIdTemp) {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
try {
|
||||
await deleteAssistantConversation(conversationIdTemp);
|
||||
conversations = conversations.filter(
|
||||
(c) => c.conversation_id !== conversationIdTemp,
|
||||
);
|
||||
if (conversationId === conversationIdTemp) {
|
||||
$assistantChatStore.conversationId = null;
|
||||
$assistantChatStore.messages = [];
|
||||
$assistantChatStore.state = "idle";
|
||||
}
|
||||
addToast("Conversation deleted", "success");
|
||||
} catch (err) {
|
||||
addToast("Failed to delete conversation: " + err.message, "error");
|
||||
}
|
||||
}
|
||||
// [/DEF:removeConversation:Function]
|
||||
|
||||
// [DEF:loadOlderMessages:Function]
|
||||
/**
|
||||
* @PURPOSE: Lazy-load older messages for active conversation when user scrolls to top.
|
||||
@@ -622,21 +649,30 @@
|
||||
</div>
|
||||
<div class="flex gap-2 overflow-x-auto pb-1">
|
||||
{#each conversations as convo (convo.conversation_id)}
|
||||
<button
|
||||
class="min-w-[140px] max-w-[220px] rounded-lg border px-2.5 py-1.5 text-left text-xs transition {convo.conversation_id ===
|
||||
conversationId
|
||||
? 'border-sky-300 bg-sky-50 text-sky-900'
|
||||
: 'border-slate-200 bg-white text-slate-700 hover:bg-slate-50'}"
|
||||
on:click={() => selectConversation(convo)}
|
||||
title={formatConversationTime(convo.updated_at)}
|
||||
>
|
||||
<div class="truncate font-semibold">
|
||||
{buildConversationTitle(convo)}
|
||||
</div>
|
||||
<div class="truncate text-[10px] text-slate-500">
|
||||
{convo.last_message || ""}
|
||||
</div>
|
||||
</button>
|
||||
<div class="relative group min-w-[140px] max-w-[220px]">
|
||||
<button
|
||||
class="w-full rounded-lg border px-2.5 py-1.5 text-left text-xs transition {convo.conversation_id ===
|
||||
conversationId
|
||||
? 'border-sky-300 bg-sky-50 text-sky-900'
|
||||
: 'border-slate-200 bg-white text-slate-700 hover:bg-slate-50'}"
|
||||
on:click={() => selectConversation(convo)}
|
||||
title={formatConversationTime(convo.updated_at)}
|
||||
>
|
||||
<div class="truncate font-semibold pr-4">
|
||||
{buildConversationTitle(convo)}
|
||||
</div>
|
||||
<div class="truncate text-[10px] text-slate-500 pr-4">
|
||||
{convo.last_message || ""}
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
class="absolute right-1.5 top-1.5 hidden group-hover:block p-1 text-slate-400 hover:text-red-500 rounded bg-white/80 hover:bg-red-50"
|
||||
on:click={(e) => removeConversation(e, convo.conversation_id)}
|
||||
title="Удалить диалог"
|
||||
>
|
||||
<Icon name="trash" size={12} />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
{#if loadingConversations}
|
||||
<div
|
||||
|
||||
@@ -238,6 +238,19 @@
|
||||
"server": "Git Server",
|
||||
"not_linked": "This dashboard is not yet linked to a Git repository.",
|
||||
"manage": "Manage Git",
|
||||
"manage_selected": "Manage selected",
|
||||
"select_single_for_manage": "Select exactly one dashboard to manage",
|
||||
"selected_count": "{count} selected",
|
||||
"bulk_sync": "Bulk Sync",
|
||||
"bulk_commit": "Bulk Commit",
|
||||
"bulk_pull": "Bulk Pull",
|
||||
"bulk_push": "Bulk Push",
|
||||
"bulk_action_sync": "Sync",
|
||||
"bulk_action_commit": "Commit",
|
||||
"bulk_action_pull": "Pull",
|
||||
"bulk_action_push": "Push",
|
||||
"bulk_result": "{action}: {success} success, {failed} failed",
|
||||
"no_repositories_selected": "No repositories available for selected dashboards",
|
||||
"generate_message": "Generate",
|
||||
"select_dashboard": "Select Dashboard to Manage"
|
||||
,
|
||||
@@ -269,7 +282,17 @@
|
||||
"load_branches_failed": "Failed to load branches",
|
||||
"switched_to": "Switched to {branch}",
|
||||
"created_branch": "Created branch {branch}",
|
||||
"branch_name_placeholder": "branch-name"
|
||||
"branch_name_placeholder": "branch-name",
|
||||
"repo_status": {
|
||||
"loading": "Loading",
|
||||
"no_repo": "No Repo",
|
||||
"synced": "Synced",
|
||||
"changes": "Changes",
|
||||
"behind_remote": "Behind Remote",
|
||||
"ahead_remote": "Ahead Remote",
|
||||
"diverged": "Diverged",
|
||||
"error": "Error"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"search": "Search dashboards...",
|
||||
|
||||
@@ -238,9 +238,21 @@
|
||||
"server": "Git-сервер",
|
||||
"not_linked": "Этот дашборд еще не привязан к Git-репозиторию.",
|
||||
"manage": "Управление Git",
|
||||
"manage_selected": "Управлять выбранным",
|
||||
"select_single_for_manage": "Выберите ровно один дашборд для управления",
|
||||
"selected_count": "{count} выбрано",
|
||||
"bulk_sync": "Массовый sync",
|
||||
"bulk_commit": "Массовый commit",
|
||||
"bulk_pull": "Массовый pull",
|
||||
"bulk_push": "Массовый push",
|
||||
"bulk_action_sync": "Sync",
|
||||
"bulk_action_commit": "Commit",
|
||||
"bulk_action_pull": "Pull",
|
||||
"bulk_action_push": "Push",
|
||||
"bulk_result": "{action}: успешно {success}, ошибок {failed}",
|
||||
"no_repositories_selected": "Для выбранных дашбордов нет доступных репозиториев",
|
||||
"generate_message": "Сгенерировать",
|
||||
"select_dashboard": "Выберите дашборд для управления"
|
||||
,
|
||||
"select_dashboard": "Выберите дашборд для управления",
|
||||
"commit_message_generated": "Сообщение коммита сгенерировано",
|
||||
"commit_message_failed": "Не удалось сгенерировать сообщение коммита",
|
||||
"load_changes_failed": "Не удалось загрузить изменения",
|
||||
@@ -269,7 +281,17 @@
|
||||
"load_branches_failed": "Не удалось загрузить ветки",
|
||||
"switched_to": "Переключено на {branch}",
|
||||
"created_branch": "Создана ветка {branch}",
|
||||
"branch_name_placeholder": "имя-ветки"
|
||||
"branch_name_placeholder": "имя-ветки",
|
||||
"repo_status": {
|
||||
"loading": "Загрузка",
|
||||
"no_repo": "Нет репозитория",
|
||||
"synced": "Синхронизирован",
|
||||
"changes": "Есть изменения",
|
||||
"behind_remote": "Отстает от remote",
|
||||
"ahead_remote": "Опережает remote",
|
||||
"diverged": "Расхождение",
|
||||
"error": "Ошибка"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"search": "Поиск дашбордов...",
|
||||
@@ -545,7 +567,9 @@
|
||||
"mapping_management": "Управление маппингом БД",
|
||||
"fetch_dbs": "Получить БД и подсказки",
|
||||
"mapping_hint": "Выберите окружения и нажмите «Получить БД и подсказки», чтобы начать маппинг.",
|
||||
"task_placeholder_warn": "Не удалось сразу получить детали задачи, используется временное состояние."
|
||||
"task_placeholder_warn": "Не удалось сразу получить детали задачи, используется временное состояние.",
|
||||
"calculating_dry_run": "Расчет ожидаемых изменений...",
|
||||
"dry_run_summary": "Отчет dry-run: всего будет изменено {total} элементов (дашбордов: {dashboards}, чартов: {charts}, датасетов: {datasets})"
|
||||
},
|
||||
"assistant": {
|
||||
"title": "AI Ассистент",
|
||||
|
||||
@@ -14,13 +14,12 @@
|
||||
"M4 7v10",
|
||||
"M20 7v10",
|
||||
],
|
||||
storage: [
|
||||
"M3 8l9-4 9 4-9 4-9-4z",
|
||||
"M3 13l9 4 9-4",
|
||||
"M3 17l9 4 9-4",
|
||||
],
|
||||
storage: ["M3 8l9-4 9 4-9 4-9-4z", "M3 13l9 4 9-4", "M3 17l9 4 9-4"],
|
||||
reports: ["M5 5h14v14H5z", "M8 9h8", "M8 13h8", "M8 17h5"],
|
||||
admin: ["M12 3l8 4v5c0 5.2-3.4 8.6-8 9.9C7.4 20.6 4 17.2 4 12V7l8-4z", "M9 12l2 2 4-4"],
|
||||
admin: [
|
||||
"M12 3l8 4v5c0 5.2-3.4 8.6-8 9.9C7.4 20.6 4 17.2 4 12V7l8-4z",
|
||||
"M9 12l2 2 4-4",
|
||||
],
|
||||
chevronDown: ["M6 9l6 6 6-6"],
|
||||
chevronLeft: ["M15 6l-6 6 6 6"],
|
||||
chevronRight: ["M9 6l6 6-6 6"],
|
||||
@@ -39,9 +38,30 @@
|
||||
layers: ["M12 4l8 4-8 4-8-4 8-4z", "M4 12l8 4 8-4", "M4 16l8 4 8-4"],
|
||||
back: ["M19 12H5", "M12 5l-7 7 7 7"],
|
||||
close: ["M18 6L6 18", "M6 6l12 12"],
|
||||
list: ["M8 7h12", "M8 12h12", "M8 17h12", "M4 7h.01", "M4 12h.01", "M4 17h.01"],
|
||||
clipboard: ["M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2", "M9 5a2 2 0 002 2h2a2 2 0 002-2", "M9 5a2 2 0 012-2h2a2 2 0 012 2"],
|
||||
settings: ["M12 8.5a3.5 3.5 0 100 7 3.5 3.5 0 000-7z", "M19.4 15a1 1 0 00.2 1.1l.1.1a1 1 0 010 1.4l-1.1 1.1a1 1 0 01-1.4 0l-.1-.1a1 1 0 00-1.1-.2 1 1 0 00-.6.9V20a1 1 0 01-1 1h-1.6a1 1 0 01-1-1v-.2a1 1 0 00-.6-.9 1 1 0 00-1.1.2l-.1.1a1 1 0 01-1.4 0l-1.1-1.1a1 1 0 010-1.4l.1-.1a1 1 0 00.2-1.1 1 1 0 00-.9-.6H4a1 1 0 01-1-1v-1.6a1 1 0 011-1h.2a1 1 0 00.9-.6 1 1 0 00-.2-1.1l-.1-.1a1 1 0 010-1.4l1.1-1.1a1 1 0 011.4 0l.1.1a1 1 0 001.1.2 1 1 0 00.6-.9V4a1 1 0 011-1h1.6a1 1 0 011 1v.2a1 1 0 00.6.9 1 1 0 001.1-.2l.1-.1a1 1 0 011.4 0l1.1 1.1a1 1 0 010 1.4l-.1.1a1 1 0 00-.2 1.1 1 1 0 00.9.6H20a1 1 0 011 1v1.6a1 1 0 01-1 1h-.2a1 1 0 00-.9.6z"],
|
||||
list: [
|
||||
"M8 7h12",
|
||||
"M8 12h12",
|
||||
"M8 17h12",
|
||||
"M4 7h.01",
|
||||
"M4 12h.01",
|
||||
"M4 17h.01",
|
||||
],
|
||||
clipboard: [
|
||||
"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2",
|
||||
"M9 5a2 2 0 002 2h2a2 2 0 002-2",
|
||||
"M9 5a2 2 0 012-2h2a2 2 0 012 2",
|
||||
],
|
||||
settings: [
|
||||
"M12 8.5a3.5 3.5 0 100 7 3.5 3.5 0 000-7z",
|
||||
"M19.4 15a1 1 0 00.2 1.1l.1.1a1 1 0 010 1.4l-1.1 1.1a1 1 0 01-1.4 0l-.1-.1a1 1 0 00-1.1-.2 1 1 0 00-.6.9V20a1 1 0 01-1 1h-1.6a1 1 0 01-1-1v-.2a1 1 0 00-.6-.9 1 1 0 00-1.1.2l-.1.1a1 1 0 01-1.4 0l-1.1-1.1a1 1 0 010-1.4l.1-.1a1 1 0 00.2-1.1 1 1 0 00-.9-.6H4a1 1 0 01-1-1v-1.6a1 1 0 011-1h.2a1 1 0 00.9-.6 1 1 0 00-.2-1.1l-.1-.1a1 1 0 010-1.4l1.1-1.1a1 1 0 011.4 0l.1.1a1 1 0 001.1.2 1 1 0 00.6-.9V4a1 1 0 011-1h1.6a1 1 0 011 1v.2a1 1 0 00.6.9 1 1 0 001.1-.2l.1-.1a1 1 0 011.4 0l1.1 1.1a1 1 0 010 1.4l-.1.1a1 1 0 00-.2 1.1 1 1 0 00.9.6H20a1 1 0 011 1v1.6a1 1 0 01-1 1h-.2a1 1 0 00-.9.6z",
|
||||
],
|
||||
trash: [
|
||||
"M3 6h18",
|
||||
"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",
|
||||
"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",
|
||||
"M10 11v6",
|
||||
"M14 11v6",
|
||||
],
|
||||
};
|
||||
|
||||
$: paths = iconPaths[name] || iconPaths.dashboard;
|
||||
@@ -61,6 +81,6 @@
|
||||
aria-hidden="true"
|
||||
>
|
||||
{#each paths as d}
|
||||
<path d={d} />
|
||||
<path {d} />
|
||||
{/each}
|
||||
</svg>
|
||||
|
||||
Reference in New Issue
Block a user