diff --git a/application/single_app/config.py b/application/single_app/config.py index ef623f47b..b9da9e810 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -95,7 +95,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.250.109" +VERSION = "0.250.110" IS_DEVELOPMENT = is_development_env_enabled() SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax') diff --git a/application/single_app/functions_settings.py b/application/single_app/functions_settings.py index eb56889dc..c225b6434 100644 --- a/application/single_app/functions_settings.py +++ b/application/single_app/functions_settings.py @@ -86,6 +86,9 @@ ADMIN_SETTINGS_NESTED_SECRET_FIELDS = ( "web_search_agent.other_settings.azure_ai_foundry.client_secret", ) +PUBLIC_WORKSPACE_DISPLAY_NAME_DEFAULT = "Public Workspace" +PUBLIC_WORKSPACE_DISPLAY_NAME_PLURAL_DEFAULT = "Public Workspaces" +PUBLIC_WORKSPACE_DISPLAY_NAME_MAX_LENGTH = 32 def is_admin_settings_redacted_secret(value): @@ -118,6 +121,59 @@ def resolve_admin_settings_secret_value(field_name, submitted_value, existing_se return str(_get_nested_setting_value(existing_settings, field_name) or '').strip() +def normalize_public_workspace_display_name(value): + """Return the end-user Public Workspace display name setting.""" + display_name = " ".join(str(value or "").replace("\r", " ").replace("\n", " ").split()) + return display_name[:PUBLIC_WORKSPACE_DISPLAY_NAME_MAX_LENGTH] + + +def get_public_workspace_label_context(settings=None): + """Return safe labels for end-user Public Workspace UI copy.""" + source_settings = settings if isinstance(settings, dict) else {} + custom_display_name = normalize_public_workspace_display_name( + source_settings.get("public_workspace_display_name") + ) + is_custom = bool(custom_display_name) + singular = custom_display_name or PUBLIC_WORKSPACE_DISPLAY_NAME_DEFAULT + plural = custom_display_name or PUBLIC_WORKSPACE_DISPLAY_NAME_PLURAL_DEFAULT + lower_singular = singular if is_custom else "public workspace" + lower_plural = plural if is_custom else "public workspaces" + return { + "singular": singular, + "plural": plural, + "lower_singular": lower_singular, + "lower_plural": lower_plural, + "short": singular if is_custom else "Public", + "is_custom": is_custom, + "max_length": PUBLIC_WORKSPACE_DISPLAY_NAME_MAX_LENGTH, + } + + +def normalize_public_workspace_display_settings(settings): + """Normalize stored Public Workspace display-name settings in-place.""" + if not isinstance(settings, dict): + return False + + changed = False + if "public_workspace_labels" in settings: + settings.pop("public_workspace_labels", None) + changed = True + + normalized_display_name = normalize_public_workspace_display_name( + settings.get("public_workspace_display_name") + ) + changed = changed or settings.get("public_workspace_display_name", "") != normalized_display_name + settings["public_workspace_display_name"] = normalized_display_name + return changed + + +def attach_public_workspace_label_context(settings): + """Attach derived end-user Public Workspace label values to a settings dict.""" + if isinstance(settings, dict): + settings["public_workspace_labels"] = get_public_workspace_label_context(settings) + return settings + + def normalize_document_access_index_required_settings(settings): """Force DAI operational settings that are required for the default read path.""" if not isinstance(settings, dict): @@ -1024,6 +1080,7 @@ def get_settings(use_cosmos=False, include_source=False): 'require_member_of_create_group': False, 'require_owner_for_group_agent_management': False, 'enable_public_workspaces': False, + 'public_workspace_display_name': '', 'require_member_of_create_public_workspace': False, 'enable_file_sharing': False, 'allow_personal_workspace_file_downloads': False, @@ -1457,6 +1514,7 @@ def _format_result(settings_payload, source): promoted_popular_settings_updated = normalize_agents_page_promoted_popular_settings(merged) document_access_index_settings_updated = normalize_document_access_index_required_settings(merged) inbound_mcp_settings_updated = normalize_inbound_mcp_settings(merged) + public_workspace_display_settings_updated = normalize_public_workspace_display_settings(merged) merged['enable_tabular_processing_plugin'] = is_tabular_processing_enabled(merged) @@ -1469,6 +1527,7 @@ def _format_result(settings_payload, source): or promoted_popular_settings_updated or document_access_index_settings_updated or inbound_mcp_settings_updated + or public_workspace_display_settings_updated ): cosmos_settings_container.upsert_item(merged) _refresh_app_settings_cache_after_write(merged, context="merge_upsert") @@ -1480,10 +1539,10 @@ def _format_result(settings_payload, source): }, level=logging.INFO ) - return _format_result(merged, settings_source) + return _format_result(attach_public_workspace_label_context(merged), settings_source) else: # If merged is unchanged, no new keys needed - return _format_result(merged, settings_source) + return _format_result(attach_public_workspace_label_context(merged), settings_source) except CosmosResourceNotFoundError: cosmos_settings_container.create_item(body=default_settings) @@ -1496,7 +1555,7 @@ def _format_result(settings_payload, source): }, level=logging.WARNING ) - return _format_result(default_settings, "cosmos_default_created") + return _format_result(attach_public_workspace_label_context(default_settings), "cosmos_default_created") except Exception as e: log_event( @@ -1520,6 +1579,7 @@ def update_settings(new_settings): normalize_agents_page_promoted_popular_settings(settings_item) normalize_document_access_index_required_settings(settings_item) normalize_inbound_mcp_settings(settings_item) + normalize_public_workspace_display_settings(settings_item) settings_item['enable_multi_model_endpoints'] = coerce_multi_model_endpoint_enablement( existing_multi_endpoint_enabled, settings_item.get('enable_multi_model_endpoints', False), @@ -2606,6 +2666,8 @@ def sanitize_settings_for_user(full_settings: dict) -> dict: 'enabled': False, } + sanitized['public_workspace_labels'] = get_public_workspace_label_context(full_settings) + return sanitized def sanitize_settings_for_logging(full_settings: dict) -> dict: diff --git a/application/single_app/route_frontend_admin_settings.py b/application/single_app/route_frontend_admin_settings.py index 94bbf1ad6..91018f147 100644 --- a/application/single_app/route_frontend_admin_settings.py +++ b/application/single_app/route_frontend_admin_settings.py @@ -1120,6 +1120,9 @@ def parse_admin_int(raw_value, fallback_value, field_name="unknown", hard_defaul require_member_of_create_group = form_data.get('require_member_of_create_group') == 'on' require_owner_for_group_agent_management = form_data.get('require_owner_for_group_agent_management') == 'on' + public_workspace_display_name = normalize_public_workspace_display_name( + form_data.get('public_workspace_display_name') + ) require_member_of_create_public_workspace = form_data.get('require_member_of_create_public_workspace') == 'on' require_member_of_chat_file_upload_user = form_data.get('require_member_of_chat_file_upload_user') == 'on' require_member_of_workflow_user = form_data.get('require_member_of_workflow_user') == 'on' @@ -2434,6 +2437,7 @@ def is_valid_url(url): # disable_group_creation is inverted: when checked (on), enable_group_creation = False 'enable_group_creation': form_data.get('disable_group_creation') != 'on', 'enable_public_workspaces': form_data.get('enable_public_workspaces') == 'on', + 'public_workspace_display_name': public_workspace_display_name, 'enable_file_sharing': form_data.get('enable_file_sharing') == 'on', 'enable_chat_file_uploads': form_data.get('enable_chat_file_uploads') == 'on', 'enable_conversation_contents_drawer': form_data.get('enable_conversation_contents_drawer') == 'on', diff --git a/application/single_app/static/js/agent_modal_stepper.js b/application/single_app/static/js/agent_modal_stepper.js index aece466da..808d3477c 100644 --- a/application/single_app/static/js/agent_modal_stepper.js +++ b/application/single_app/static/js/agent_modal_stepper.js @@ -6,6 +6,8 @@ import { getModelSupportedLevels } from "./chat/chat-reasoning.js"; const ACTION_CAPABILITIES_KEY = 'action_capabilities'; const ASSIGNED_KNOWLEDGE_KEY = 'assigned_knowledge'; +const publicWorkspaceLowerSingular = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel('lower_singular') : 'public workspace'; +const publicWorkspaceLowerPlural = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel('lower_plural') : 'public workspaces'; const ASSIGNED_KNOWLEDGE_USER_ACTIONS = Object.freeze(['search', 'analyze', 'compare']); const ASSIGNED_KNOWLEDGE_WEB_SOURCE_MODES = Object.freeze(['url_review', 'deep_research']); const EMPTY_ASSIGNED_KNOWLEDGE = Object.freeze({ @@ -3094,7 +3096,9 @@ export class AgentModalStepper { summaryItems.push(`${scopes.group_ids.length} group source${scopes.group_ids.length === 1 ? '' : 's'}`); } if (scopes.public_workspace_ids?.length) { - summaryItems.push(`${scopes.public_workspace_ids.length} public workspace${scopes.public_workspace_ids.length === 1 ? '' : 's'}`); + const publicWorkspaceCount = scopes.public_workspace_ids.length; + const publicWorkspaceLabel = publicWorkspaceCount === 1 ? publicWorkspaceLowerSingular : publicWorkspaceLowerPlural; + summaryItems.push(`${publicWorkspaceCount} ${publicWorkspaceLabel}`); } if (assignedKnowledge.document_ids?.length) { summaryItems.push(`${assignedKnowledge.document_ids.length} specific document${assignedKnowledge.document_ids.length === 1 ? '' : 's'}`); diff --git a/application/single_app/static/js/chat/chat-documents.js b/application/single_app/static/js/chat/chat-documents.js index 7695717eb..62ad623c1 100644 --- a/application/single_app/static/js/chat/chat-documents.js +++ b/application/single_app/static/js/chat/chat-documents.js @@ -8,6 +8,7 @@ const searchDocumentsBtn = document.getElementById("search-documents-btn"); const docSelectEl = document.getElementById("document-select"); // Hidden select element const searchDocumentsContainer = document.getElementById("search-documents-container"); // Container for scope/doc/class const searchDocumentsMobileClose = document.getElementById("search-documents-mobile-close"); +const publicWorkspacePlural = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel("plural") : "Public Workspaces"; // Custom dropdown elements const docDropdown = document.getElementById("document-dropdown"); @@ -1114,7 +1115,7 @@ function buildScopeDropdown() { if (publicWorkspaces.length > 0) { const pubHeader = document.createElement("div"); pubHeader.classList.add("dropdown-header", "small", "text-muted", "px-2", "pt-2", "pb-1"); - pubHeader.textContent = "Public Workspaces"; + pubHeader.textContent = publicWorkspacePlural; scopeDropdownItems.appendChild(pubHeader); publicWorkspaces.forEach(ws => { diff --git a/application/single_app/static/js/chat/chat-onload.js b/application/single_app/static/js/chat/chat-onload.js index 40928a221..ecb057605 100644 --- a/application/single_app/static/js/chat/chat-onload.js +++ b/application/single_app/static/js/chat/chat-onload.js @@ -24,6 +24,8 @@ import { initializeReasoningToggle } from "./chat-reasoning.js"; import { initializeSpeechInput } from "./chat-speech-input.js"; import { initChatTutorial } from "./chat-tutorial.js"; +const publicWorkspaceLowerSingular = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel("lower_singular") : "public workspace"; + function clearFeatureActionParam() { const url = new URL(window.location.href); @@ -228,17 +230,17 @@ window.addEventListener('DOMContentLoaded', async () => { // Trigger change to update UI handleDocumentSelectChange(); - showToast('Public workspace activated for chat', 'success'); + showToast(`${publicWorkspaceLowerSingular} activated for chat`, 'success'); } else { console.error('Failed to set active public workspace:', data.error || data.message); - showToast('Failed to activate public workspace', 'error'); + showToast(`Failed to activate ${publicWorkspaceLowerSingular}`, 'error'); // Fall back to normal document handling populateDocumentSelectScope(); } }) .catch(error => { console.error('Error setting active public workspace:', error); - showToast('Error activating public workspace', 'error'); + showToast(`Error activating ${publicWorkspaceLowerSingular}`, 'error'); // Fall back to normal document handling populateDocumentSelectScope(); }); diff --git a/application/single_app/static/js/chat/chat-tutorial.js b/application/single_app/static/js/chat/chat-tutorial.js index b3cae03c3..1c93b88a0 100644 --- a/application/single_app/static/js/chat/chat-tutorial.js +++ b/application/single_app/static/js/chat/chat-tutorial.js @@ -3,6 +3,7 @@ const STORAGE_KEY = "chatTutorialDismissed"; const EDGE_PADDING = 12; const HIGHLIGHT_PADDING = 10; +const publicWorkspaceLowerPlural = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel("lower_plural") : "public workspaces"; let tutorialSteps = []; let layerEl = null; let highlightEl = null; @@ -197,7 +198,7 @@ function buildSteps() { id: "workspace-search", selector: "#search-documents-btn", title: "Workspace search", - body: "Search personal, group, or public workspaces to ground answers with approved documents.", + body: `Search personal, group, or ${publicWorkspaceLowerPlural} to ground answers with approved documents.`, phase: "chat" }, { diff --git a/application/single_app/static/js/form-voice-input.js b/application/single_app/static/js/form-voice-input.js index 7eec1d958..030586048 100644 --- a/application/single_app/static/js/form-voice-input.js +++ b/application/single_app/static/js/form-voice-input.js @@ -4,6 +4,7 @@ (function () { const MAX_RECORDING_DURATION_MS = 90000; const DEFAULT_TRANSCRIPTION_ENDPOINT = '/api/speech/transcribe-chat'; + const publicWorkspaceLowerSingular = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel('lower_singular') : 'public workspace'; const fieldControls = new WeakMap(); const controls = []; let activeControl = null; @@ -509,10 +510,10 @@ ['groupDescription', { label: 'Dictate group description' }], ['editGroupName', { label: 'Dictate group name', insertMode: 'replace' }], ['editGroupDescription', { label: 'Dictate group description' }], - ['publicWorkspaceName', { label: 'Dictate public workspace name', insertMode: 'replace' }], - ['publicWorkspaceDescription', { label: 'Dictate public workspace description' }], - ['editWorkspaceName', { label: 'Dictate public workspace name', insertMode: 'replace' }], - ['editWorkspaceDescription', { label: 'Dictate public workspace description' }], + ['publicWorkspaceName', { label: `Dictate ${publicWorkspaceLowerSingular} name`, insertMode: 'replace' }], + ['publicWorkspaceDescription', { label: `Dictate ${publicWorkspaceLowerSingular} description` }], + ['editWorkspaceName', { label: `Dictate ${publicWorkspaceLowerSingular} name`, insertMode: 'replace' }], + ['editWorkspaceDescription', { label: `Dictate ${publicWorkspaceLowerSingular} description` }], ['doc-title', { label: 'Dictate title', insertMode: 'replace' }], ['doc-abstract', { label: 'Dictate abstract' }], ['doc-keywords', { label: 'Dictate keywords', mode: 'comma-list' }], diff --git a/application/single_app/static/js/plugin_modal_stepper.js b/application/single_app/static/js/plugin_modal_stepper.js index 53875e9a6..7394fc06c 100644 --- a/application/single_app/static/js/plugin_modal_stepper.js +++ b/application/single_app/static/js/plugin_modal_stepper.js @@ -23,6 +23,7 @@ const SNOWFLAKE_AUTH_METHOD_OAUTH = 'oauth'; const TABLEAU_PLUGIN_TYPE = 'tableau'; const TABLEAU_AUTH_METHOD_PAT = 'personal_access_token'; const TABLEAU_AUTH_METHOD_USERNAME_PASSWORD = 'username_password'; +const publicWorkspacePlural = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel('plural') : 'Public Workspaces'; const MCP_PLUGIN_TYPE = 'mcp'; const MCP_HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/; const MCP_MAX_CUSTOM_HEADER_COUNT = 20; @@ -2284,7 +2285,7 @@ export class PluginModalStepper { all: 'All Accessible Content', personal: 'Personal Workspace', group: 'Group Workspaces', - public: 'Public Workspaces' + public: publicWorkspacePlural }; return scopeMap[scope] || scope || '-'; diff --git a/application/single_app/static/js/profile/profile-tabs.js b/application/single_app/static/js/profile/profile-tabs.js index 234db8877..8de4c556e 100644 --- a/application/single_app/static/js/profile/profile-tabs.js +++ b/application/single_app/static/js/profile/profile-tabs.js @@ -2,6 +2,8 @@ (function () { const pageConfig = window.profilePageConfig || {}; + const publicWorkspaceLowerSingular = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel('lower_singular') : 'public workspace'; + const publicWorkspaceLowerPlural = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel('lower_plural') : 'public workspaces'; const feedbackState = { currentPage: 1, pageSize: 10, @@ -371,8 +373,8 @@ }, publicWorkspaces: { type: 'publicWorkspaces', - label: 'public workspaces', - itemLabel: 'public workspace', + label: publicWorkspaceLowerPlural, + itemLabel: publicWorkspaceLowerSingular, state: publicWorkspaceState, apiEndpoint: '/api/public_workspaces', responseKey: 'workspaces', @@ -409,9 +411,9 @@ discoverStatusId: 'profile-find-public-workspaces-status', discoverTbodyId: 'profile-find-public-workspaces-tbody', storageKey: 'simplechat.profile.publicWorkspaces.viewMode', - emptyMessage: 'No public workspaces found for the current search.', - loadingMessage: 'Loading public workspaces...', - discoverEmptyMessage: 'No public workspaces found for the current search.', + emptyMessage: `No ${publicWorkspaceLowerPlural} found for the current search.`, + loadingMessage: `Loading ${publicWorkspaceLowerPlural}...`, + discoverEmptyMessage: `No ${publicWorkspaceLowerPlural} found for the current search.`, requestLabel: 'Request Access', }, }; diff --git a/application/single_app/static/js/public/manage_public_workspace.js b/application/single_app/static/js/public/manage_public_workspace.js index 1e07fd1c8..078a830a4 100644 --- a/application/single_app/static/js/public/manage_public_workspace.js +++ b/application/single_app/static/js/public/manage_public_workspace.js @@ -9,6 +9,8 @@ let currentStatsWindow = { days: 30, startDate: '', endDate: '' }; let currentStatsData = null; const defaultWorkspaceHeroColor = '#0078d4'; const workspaceHeroColorPattern = /^#[0-9a-fA-F]{6}$/; +const publicWorkspaceSingular = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel('singular') : 'Public Workspace'; +const publicWorkspaceLowerSingular = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel('lower_singular') : 'public workspace'; function normalizeWorkspaceHeroColor(color) { const candidate = String(color || '').trim(); @@ -283,7 +285,7 @@ $(document).ready(function () { `); $("#deleteWorkspaceWarningModal").modal("show"); } else { - if (!confirm("Permanently delete this public workspace?")) return; + if (!confirm(`Permanently delete this ${publicWorkspaceLowerSingular}?`)) return; $.ajax({ url: `/api/public_workspaces/${workspaceId}`, method: "DELETE", @@ -1025,7 +1027,7 @@ async function exportWorkspaceStats() { const windowLabel = stats.window?.label || getStatsWindowLabel(exportWindow); const rows = []; - rows.push('Public Workspace Stats Export'); + rows.push(`${publicWorkspaceSingular} Stats Export`); appendCsvRow(rows, ['Export Date', new Date().toLocaleString()]); appendCsvRow(rows, ['Data Period', windowLabel]); appendCsvSectionBreak(rows); @@ -1077,10 +1079,10 @@ async function exportWorkspaceStats() { if (modal) { modal.hide(); } - showStatsToast('Public workspace stats exported successfully.', 'success'); + showStatsToast(`${publicWorkspaceSingular} stats exported successfully.`, 'success'); } catch (error) { console.error('Failed to export public workspace stats:', error); - showStatsToast('Failed to export public workspace stats.', 'danger'); + showStatsToast(`Failed to export ${publicWorkspaceLowerSingular} stats.`, 'danger'); } finally { if (exportButton) { exportButton.disabled = false; diff --git a/application/single_app/static/js/public/my_public_workspaces.js b/application/single_app/static/js/public/my_public_workspaces.js index f2ab9b088..ae61e08ad 100644 --- a/application/single_app/static/js/public/my_public_workspaces.js +++ b/application/single_app/static/js/public/my_public_workspaces.js @@ -13,12 +13,38 @@ $(document).ready(function () { const clearSearchBtn = $("#clearSearchBtn"); const createModal = window.canCreatePublicWorkspaces ? new bootstrap.Modal(document.getElementById('createPublicWorkspaceModal')) : null; const findModal = new bootstrap.Modal(document.getElementById('findPublicWorkspaceModal')); + const publicWorkspaceSingular = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel('singular') : 'Public Workspace'; + const publicWorkspaceLowerPlural = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel('lower_plural') : 'public workspaces'; // State let currentPage = 1; let pageSize = parseInt(pageSizeSelect.val(), 10); let currentSearchQuery = ""; + function setTableTextMessage(message, columnSpan = 5, className = "text-center p-4 text-muted") { + const row = document.createElement("tr"); + const cell = document.createElement("td"); + cell.colSpan = columnSpan; + cell.className = className; + cell.textContent = message; + row.appendChild(cell); + tableBody.empty().append(row); + } + + function setEmptyPublicWorkspaceMessage() { + const row = document.createElement("tr"); + const cell = document.createElement("td"); + cell.colSpan = 5; + cell.className = "text-center p-4 text-muted"; + cell.append( + document.createTextNode(`You don't have any ${publicWorkspaceLowerPlural} yet.`), + document.createElement("br"), + document.createTextNode(`Use "Create New ${publicWorkspaceSingular}" or "Find ${publicWorkspaceSingular}" above.`) + ); + row.appendChild(cell); + tableBody.empty().append(row); + } + // Fetch and render the list of public workspaces function fetchWorkspaces() { // Show loading placeholder @@ -49,28 +75,15 @@ $(document).ready(function () { if (workspaces.length) { workspaces.forEach(renderWorkspaceRow); } else if (currentSearchQuery) { - tableBody.html(` - - No workspaces found matching "${escapeHtml(currentSearchQuery)}". - - `); + setTableTextMessage(`No workspaces found matching "${currentSearchQuery}".`); } else { - tableBody.html(` - - You don't have any public workspaces yet.
- Use "Create New Public Workspace" or "Find Public Workspace" above. - - `); + setEmptyPublicWorkspaceMessage(); } renderPaginationControls(data.page, data.page_size, data.total_count); }) .fail(function (jqXHR) { const err = jqXHR.responseJSON?.error || jqXHR.statusText; - tableBody.html(` - - Error loading workspaces: ${escapeHtml(err)} - - `); + setTableTextMessage(`Error loading workspaces: ${err}`, 5, "text-center text-danger p-4"); renderPaginationControls(1, pageSize, 0); }); } diff --git a/application/single_app/static/js/public/public_directory.js b/application/single_app/static/js/public/public_directory.js index b73adfc26..d9a549e8b 100644 --- a/application/single_app/static/js/public/public_directory.js +++ b/application/single_app/static/js/public/public_directory.js @@ -11,6 +11,8 @@ $(document).ready(function () { const allVisibleBtn = $("#allVisibleBtn"); const allHiddenBtn = $("#allHiddenBtn"); const viewModal = new bootstrap.Modal(document.getElementById('viewWorkspaceModal')); + const publicWorkspaceSingular = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel('singular') : 'Public Workspace'; + const publicWorkspaceLowerPlural = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel('lower_plural') : 'public workspaces'; // State let currentPage = 1; @@ -19,6 +21,32 @@ $(document).ready(function () { let allWorkspaces = []; let userSettings = {}; + function setDirectoryTableTextMessage(message, className = "text-center p-4 text-muted") { + const row = document.createElement("tr"); + const cell = document.createElement("td"); + cell.colSpan = 4; + cell.className = className; + cell.textContent = message; + row.appendChild(cell); + tableBody.empty().append(row); + } + + function setDirectoryLoadingMessage(message) { + const row = document.createElement("tr"); + row.className = "table-loading-row"; + const cell = document.createElement("td"); + cell.colSpan = 4; + cell.className = "text-center p-4 text-muted"; + + const spinner = document.createElement("div"); + spinner.className = "spinner-border spinner-border-sm me-2"; + spinner.setAttribute("role", "status"); + + cell.append(spinner, document.createTextNode(message)); + row.appendChild(cell); + tableBody.empty().append(row); + } + // --- Curated List Helpers --- let currentLoadedList = null; let curatedListDirty = false; @@ -171,14 +199,7 @@ function updateCuratedListStatus() { // Fetch all public workspaces function fetchWorkspaces() { // Show loading placeholder - tableBody.html(` - - -
- Loading public workspaces... - - - `); + setDirectoryLoadingMessage(`Loading ${publicWorkspaceLowerPlural}...`); paginationContainer.empty(); currentSearchQuery = searchInput.val().trim(); @@ -194,11 +215,7 @@ function updateCuratedListStatus() { }) .fail(function (jqXHR) { const err = jqXHR.responseJSON?.error || jqXHR.statusText; - tableBody.html(` - - Error loading workspaces: ${escapeHtml(err)} - - `); + setDirectoryTableTextMessage(`Error loading workspaces: ${err}`, "text-center text-danger p-4"); renderPaginationControls(1, pageSize, 0); }); } @@ -209,17 +226,9 @@ function updateCuratedListStatus() { if (!allWorkspaces.length) { if (currentSearchQuery) { - tableBody.html(` - - No workspaces found matching "${escapeHtml(currentSearchQuery)}". - - `); + setDirectoryTableTextMessage(`No workspaces found matching "${currentSearchQuery}".`); } else { - tableBody.html(` - - No public workspaces available. - - `); + setDirectoryTableTextMessage(`No ${publicWorkspaceLowerPlural} available.`); } renderPaginationControls(1, pageSize, 0); return; @@ -312,7 +321,7 @@ function updateCuratedListStatus() {
diff --git a/application/single_app/static/js/public/public_workspace.js b/application/single_app/static/js/public/public_workspace.js index 947b0640d..c75bc2b26 100644 --- a/application/single_app/static/js/public/public_workspace.js +++ b/application/single_app/static/js/public/public_workspace.js @@ -6,6 +6,8 @@ let userRoleInActivePublic = null; let userPublics = []; let activePublicId = null; let activePublicName = ''; +const publicWorkspaceLowerSingular = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel('lower_singular') : 'public workspace'; +const publicWorkspaceLowerPlural = window.getPublicWorkspaceLabel ? window.getPublicWorkspaceLabel('lower_plural') : 'public workspaces'; // Documents state let publicDocsCurrentPage = 1; @@ -251,7 +253,7 @@ async function downloadPublicDocumentFile(documentId, event) { event.stopPropagation(); } if (!publicFileDownloadsEnabled) { - showPublicDocumentDeleteFeedback('File downloads are disabled for this public workspace.', 'warning'); + showPublicDocumentDeleteFeedback(`File downloads are disabled for this ${publicWorkspaceLowerSingular}.`, 'warning'); return; } @@ -525,8 +527,8 @@ document.addEventListener('DOMContentLoaded', ()=>{ if(activePublicId) loadActivePublicData(); else { const noActivePublicMessage = userPublics.length === 0 - ? 'No public workspaces are available. Select My Workspaces to create one.' - : 'Please select an active public workspace.'; + ? `No ${publicWorkspaceLowerPlural} available. Select My Workspaces to create one.` + : `Please select an active ${publicWorkspaceLowerSingular}.`; setPublicTableMessage(publicDocsTableBody, 4, noActivePublicMessage); setPublicTableMessage(publicPromptsTableBody, 2, noActivePublicMessage); renderPublicPromptsEmptyState(noActivePublicMessage); @@ -704,7 +706,7 @@ async function fetchUserPublics(){ const emptyItem = document.createElement('div'); emptyItem.className = 'dropdown-item-text text-muted small text-wrap'; - emptyItem.textContent = 'No public workspaces are available. Select My Workspaces to create one.'; + emptyItem.textContent = `No ${publicWorkspaceLowerPlural} available. Select My Workspaces to create one.`; publicDropdownItems.appendChild(emptyItem); const emptyOption = document.createElement('option'); @@ -2532,7 +2534,7 @@ async function downloadPublicSelectedDocuments() { return; } if (!publicFileDownloadsEnabled) { - showPublicDocumentDeleteFeedback('File downloads are disabled for this public workspace.', 'warning'); + showPublicDocumentDeleteFeedback(`File downloads are disabled for this ${publicWorkspaceLowerSingular}.`, 'warning'); return; } diff --git a/application/single_app/templates/_sidebar_nav.html b/application/single_app/templates/_sidebar_nav.html index d093fcd97..431e03e82 100644 --- a/application/single_app/templates/_sidebar_nav.html +++ b/application/single_app/templates/_sidebar_nav.html @@ -6,6 +6,7 @@ {% set sidebar_menu_state = sidebar_menu_state_raw if sidebar_menu_state_raw is mapping else {} %} {% set sidebar_settings = settings if settings is defined else app_settings %} {% set latest_features_nav_is_hidden = latest_features_nav_hidden | default(false) %} +{% set public_workspace_labels = app_settings.public_workspace_labels %}