+
@@ -185,6 +213,9 @@ const logout = async () => {
+
+
+
@@ -314,7 +345,7 @@ const logout = async () => {
position: relative;
cursor: pointer;
margin: 0 4px;
- border-radius: 4px;
+ border-radius: 6px;
&:hover {
background-color: #1f23291a;
}
@@ -351,7 +382,7 @@ const logout = async () => {
padding-right: 8px;
margin-bottom: 2px;
position: relative;
- border-radius: 4px;
+ border-radius: 6px;
cursor: pointer;
&:not(.empty):hover {
background: #1f23291a;
diff --git a/frontend/src/components/layout/Workspace.vue b/frontend/src/components/layout/Workspace.vue
index 3d9f97eb3..456c083cd 100644
--- a/frontend/src/components/layout/Workspace.vue
+++ b/frontend/src/components/layout/Workspace.vue
@@ -9,6 +9,7 @@ import { ElMessage } from 'element-plus-secondary'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
+import { highlightKeyword } from '@/utils/xss'
const userStore = useUserStore()
const { t } = useI18n()
@@ -30,11 +31,8 @@ const defaultWorkspaceListWithSearch = computed(() => {
)
})
const formatKeywords = (item: string) => {
- if (!workspaceKeywords.value) return item
- return item.replaceAll(
- workspaceKeywords.value,
- `
${workspaceKeywords.value}`
- )
+ // Use XSS-safe highlight function
+ return highlightKeyword(item, workspaceKeywords.value, 'isSearch')
}
const emit = defineEmits(['selectWorkspace'])
@@ -200,7 +198,7 @@ onMounted(async () => {
padding-right: 8px;
margin-bottom: 2px;
position: relative;
- border-radius: 4px;
+ border-radius: 6px;
cursor: pointer;
&:not(.empty):hover {
background: #1f23291a;
diff --git a/frontend/src/components/layout/index.vue b/frontend/src/components/layout/index.vue
index f83ef983a..4a0097e41 100644
--- a/frontend/src/components/layout/index.vue
+++ b/frontend/src/components/layout/index.vue
@@ -362,7 +362,7 @@ onMounted(() => {
column-gap: 12px;
align-items: center;
padding: 8px 16px;
- border-radius: 4px;
+ border-radius: 6px;
cursor: pointer;
border: none;
font-weight: 500;
@@ -493,7 +493,7 @@ onMounted(() => {
column-gap: 12px;
align-items: center;
padding: 8px 16px;
- border-radius: 4px;
+ border-radius: 6px;
cursor: pointer;
border: none;
font-weight: 500;
diff --git a/frontend/src/entity/supplier.ts b/frontend/src/entity/supplier.ts
index 733cde9f4..bd4ddc971 100644
--- a/frontend/src/entity/supplier.ts
+++ b/frontend/src/entity/supplier.ts
@@ -10,6 +10,7 @@ import icon_txhy_colorful from '@/assets/model/icon_txhy_colorful.png'
import icon_hsyq_colorful from '@/assets/model/icon_hsyq_colorful.png'
// import icon_vllm_colorful from '@/assets/model/icon_vllm_colorful.png'
import icon_common_openai from '@/assets/model/icon_common_openai.png'
+import icon_minimax_colorful from '@/assets/model/icon_minimax_colorful.png'
// import icon_azure_openAI_colorful from '@/assets/model/icon_Azure_OpenAI_colorful.png'
type ModelArg = { key: string; val?: string | number; type: string; range?: string }
@@ -45,6 +46,10 @@ export const supplierList: Array<{
{ key: 'extra_body', val: '{"enable_thinking": false}', type: 'json' },
],
model_options: [
+ { name: 'qwen3.6-plus' },
+ { name: 'qwen3.5-plus' },
+ { name: 'qwen3.5-flash' },
+ { name: 'qwen3-coder-next' },
{ name: 'qwen3-coder-plus' },
{ name: 'qwen3-coder-flash' },
{ name: 'qwen-plus' },
@@ -271,11 +276,24 @@ export const supplierList: Array<{
{ name: 'doubao-1-5-pro-32k-character-250715' },
{ name: 'kimi-k2-250711' },
{ name: 'deepseek-v3-250324' },
- { name: 'deepseek-r1' },
+ { name: 'deepseek-v4-pro-260425' },
],
},
},
},
+ {
+ id: 13,
+ name: 'MiniMax',
+ i18nKey: 'supplier.minimax',
+ icon: icon_minimax_colorful,
+ model_config: {
+ 0: {
+ api_domain: 'https://api.minimax.io/v1',
+ common_args: [{ key: 'temperature', val: 0.7, type: 'number', range: '[0, 1]' }],
+ model_options: [{ name: 'MiniMax-M3' }, { name: 'MiniMax-M2.7' }],
+ },
+ },
+ },
/* {
id: 11,
name: 'vLLM',
diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json
index 455202620..4cdef7da8 100644
--- a/frontend/src/i18n/en.json
+++ b/frontend/src/i18n/en.json
@@ -4,23 +4,94 @@
"Data Q&A": "Data Q&A",
"Data Connections": "Data Sources",
"Dashboard": "Dashboard",
- "AI Model Configuration": "AI Model Configuration",
+ "AI Model Configuration": "AI Model Config",
"Details": "Details"
},
+ "variables": {
+ "please_select_date": "Please select a date",
+ "number_variable_error": "Please enter the correct numerical range.",
+ "built_in": "Built-in",
+ "normal_value": "Normal value",
+ "cannot_be_empty": "Variable values cannot be empty.",
+ "1_to_100": "{name}, the numerical range is {min} to {max}.",
+ "1_to_100_de": "{name}, the date range is {min} to {max}.",
+ "system_variables": "System Variables",
+ "variables": "Variables",
+ "system": "System",
+ "search_variables": "Search Variables",
+ "add_variable": "Add Variable",
+ "variable_name": "Variable Name",
+ "variable_type": "Variable Type",
+ "variable_value": "Variable Value",
+ "edit_variable": "Edit Variable",
+ "no_variables_yet": "No Variables Yet",
+ "please_enter_value": "Please Enter Value",
+ "date": "Date",
+ "start_date": "Start Date",
+ "end_date": "End Date",
+ "enter_variable_name": "Please Enter Variable Name",
+ "enter_variable_value": "Please Enter Variable Value"
+ },
+ "authorized_space": {
+ "authorized_space": "Authorized Space",
+ "authorized_space_list": "Authorized Space List",
+ "select_space": "Select Space",
+ "modify_authorized_space": "Modify Authorized Space",
+ "workspaces_authorized": "Authorized {num} Workspaces",
+ "number_of_members": "Number of Members",
+ "delete_selected_workspaces": "Are you sure you want to delete the selected {msg} workspaces?",
+ "delete_workspace": "Are you sure you want to delete the workspace: {msg}?",
+ "no_workspace": "No Workspaces"
+ },
+ "sync": {
+ "records": "Displaying {num} records out of {total}",
+ "confirm_upload": "Confirm Upload",
+ "field_details": "Field Details",
+ "integration": "Platform integration needs to be enabled.",
+ "the_existing_user": "If the user already exists, overwrite the existing user.",
+ "lazy_load": "Lazy Load",
+ "sync_users": "Sync Users",
+ "sync_wechat_users": "Sync WeChat Users",
+ "sync_dingtalk_users": "Sync DingTalk Users",
+ "sync_lark_users": "Sync Lark Users",
+ "sync_complete": "Sync Complete",
+ "synced_10_users": "Successfully synced {num} users",
+ "failed_3_users": "Successfully synced {success} users, sync failed {failed} users",
+ "download_failure_list": "Download Failure List",
+ "sync_failed": "Sync Failed",
+ "failed_10_users": "Sync failed {num} users",
+ "continue_syncing": "Continue Syncing",
+ "return_to_view": "Return to View"
+ },
"parameter": {
- "parameter_configuration": "Parameter Configuration",
+ "execution_details": "Execution Details",
+ "overview": "Overview",
+ "tokens_required": "Tokens Required",
+ "time_execution": "Time Execution",
+ "export_notes": "Export notes",
+ "import_notes": "Import notes",
+ "memo": "Memo update rules: Match table name and field name. If a match is found, update the table memo and field memo; otherwise, leave the memo unchanged.",
+ "parameter_configuration": "Parameter Config",
"question_count_settings": "Question Count Settings",
+ "context_record_count": "Context Record Count",
+ "context_record_count_hint": "Number of user question rounds",
"model_thinking_process": "Expand Model Thinking Process",
+ "hide_model_thinking_process": "Hide Model Thinking Process",
"rows_of_data": "Limit 1000 Rows of Data",
- "third_party_platform_settings": "Third-Party Platform Settings",
- "by_third_party_platform": "Automatic User Creation by Third-Party Platform",
- "platform_user_organization": "Third-Party Platform User Organization",
+ "third_party_platform_settings": "Authentication Settings",
+ "by_third_party_platform": "Automatic User Creation",
+ "platform_user_organization": "Default Workspace",
"platform_user_roles": "Third-Party Platform User Roles",
"excessive_data_volume": "Disabling the 1000-row data limit may cause system lag due to excessive data volume.",
+ "sqlbot_name": "Data Query Assistant Name",
+ "show_sql": "Allow Viewing SQL Statements",
+ "show_log": "Show Execution Log",
"prompt": "Prompt",
"disabling_successfully": "Disabling Successfully",
"closed_by_default": "In the Question Count window, control whether the model thinking process is expanded or closed by default.",
- "and_platform_integration": "Scope includes authentication settings and platform integration."
+ "and_platform_integration": "Authentication-related",
+ "login_settings": "Login Settings",
+ "default_login": "Default Login Method"
},
"prompt": {
"default_password": "Default password:{msg}",
@@ -50,7 +121,7 @@
"all_data_sources": "All data sources",
"partial_data_sources": "Partial data sources",
"add_it_here": "Drag the table name on the left to add it here",
- "table_relationship_management": "Table relationship management",
+ "table_relationship_management": "Table relationship manage",
"system_management": "System Management",
"data_training": "SQL Sample Lib",
"problem_description": "Problem Description",
@@ -72,7 +143,7 @@
"code_for_debugging": "Copy the following code for debugging",
"full_screen_mode": "Full screen mode",
"editing_terminology": "Editing Terminology",
- "professional_terminology": "Terminology Configuration",
+ "professional_terminology": "Terminology Config",
"term_name": "Term Name",
"term_description": "Term Description",
"search_term": "Search Term",
@@ -90,6 +161,8 @@
"the_term_gmv": "Delete the term: {msg}?"
},
"common": {
+ "start_time": "Start Time",
+ "end_time": "End Time",
"require": "Required",
"zoom_in": "Zoom in",
"zoom_out": "Zoom out",
@@ -118,6 +191,7 @@
"system_manage": "System Management",
"update_success": "Update",
"save_success": "Save Successful",
+ "operation_success": "Operation successful",
"next": "Next",
"save": "Save",
"logout": "Logout",
@@ -144,7 +218,8 @@
"password_reset_successful": "Password reset successful",
"or": "Or",
"refresh": "Refresh",
- "input_limit": "Length cannot exceed {0} characters"
+ "input_limit": "Length cannot exceed {0} characters",
+ "file_size_limit_error": "File size exceeds the limit (maximum {limit})"
},
"dashboard": {
"open_dashboard": "Open Dashboard",
@@ -187,7 +262,7 @@
"creator": "Creator",
"dashboard_id": "Dashboard ID",
"create_time": "Create Time",
- "updator": "Updater",
+ "updater": "Updater",
"update_time": "Update Time",
"edit": "Edit",
"edit_title": "Edit Title",
@@ -233,6 +308,7 @@
"continue_to_ask": "Continue asking:",
"data_analysis": "Data Analysis",
"data_predict": "Data Prediction",
+ "data_regenerated": "Regenerate",
"chat_search": "Search",
"thinking": "Thinking",
"thinking_step": "Thought Process",
@@ -333,11 +409,19 @@
"success": "Connect success",
"failed": "Connect failed"
},
- "timeout": "Timeout(second)",
- "address": "Address"
- }
+ "timeout": "Connection Timeout(second)",
+ "address": "Address",
+ "low_version": "Compatible with lower versions",
+ "ssl": "Enable SSL",
+ "file_path": "File Path",
+ "pool_size": "Connection Pool Size"
+ },
+ "sync_fields": "Sync Fields",
+ "sync_fields_success": "Sync fields successfully",
+ "sync_fields_failed": "Sync fields failed"
},
"datasource": {
+ "recommended_question": "Recommended Question",
"recommended_repetitive_tips": "Duplicate questions exist",
"recommended_problem_tips": "Custom configuration requires at least one problem, each problem should be 2-200 characters",
"recommended_problem_configuration": "Recommended Problem Configuration",
@@ -382,13 +466,16 @@
"get_schema": "Get Schema"
},
"model": {
+ "int": "Integer",
+ "float": "Float",
+ "string": "String",
"default_model": "Default model",
"model_type": "Model type",
"basic_model": "Basic model",
"set_successfully": "Set successfully",
"operate_with_caution": "After the system default model is replaced, the result of intelligent question will be affected, please operate with caution.",
"system_default_model": "Do you want to set {msg} as the system default model?",
- "ai_model_configuration": "AI model configuration",
+ "ai_model_configuration": "AI model config",
"system_default_model_de": "System default model",
"relevant_results_found": "No relevant results found",
"add_model": "Add model",
@@ -438,15 +525,17 @@
"enable": "Enabled",
"disable": "Disabled",
"local_creation": "Local",
- "feishu": "Feishu",
+ "lark": "Lark",
+ "larksuite": "Larksuite",
"dingtalk": "DingTalk",
- "wechat_for_business": "WeChat for business",
+ "wecom": "WeCom",
"1240_results": "{msg} results",
"clear_conditions": "Clear conditions",
"disabled": "Disabled",
"enabled": "Enabled",
"edit_user": "Edit User",
"del_user": "Do you want to delete user: {msg}?",
+ "del_key": "Do you want to delete key: {msg}?",
"please_first": "Please first",
"download_the_template": "download the template",
"required_and_upload": ", fill in as required and upload",
@@ -456,6 +545,7 @@
"import": "Import",
"change_file": "Change file",
"data_import_completed": "Data import completed",
+ "notes_import_completed": "Import of notes complete",
"imported_100_data": "Successfully imported {msg} data",
"return_to_view": "Return to view",
"continue_importing": "Continue importing",
@@ -496,6 +586,7 @@
"member_feng_yibudao": "Do you want to remove the member: {msg}?",
"select_member": "Select member",
"selected_2_people": "Selected: {msg} people",
+ "selected_number": "Selected: {msg} items",
"clear": "Clear",
"historical_dialogue": "No historical dialogue",
"rename_a_workspace": "Rename a workspace",
@@ -508,11 +599,12 @@
"find_user": "Find user",
"add_successfully": "Add successfully",
"member_management": "Member management",
- "permission_configuration": "Permission configuration",
+ "permission_configuration": "Permission Config",
"set": "Settings",
"operate_with_caution": "After deletion, the users under the workspace will be removed and all resources will be deleted. Please operate with caution."
},
"permission": {
+ "search_field": "Search field",
"search_rule_group": "Search rule group",
"add_rule_group": "Add rule group",
"permission_rule": "Permission rules",
@@ -584,8 +676,8 @@
"embedded_page": "Embedded page",
"no_application": "No application",
"create_application": "Create application",
- "basic_application": "Basic application",
- "advanced_application": "Advanced application",
+ "basic_application": "Basic app",
+ "advanced_application": "Advanced app",
"embed_third_party": "Embed third party",
"support_is_required": "Applicable to scenarios where data verification is not required, just embed the embedded code into the third party code, no additional support is required",
"data_permissions_etc": "Applicable to scenarios where data verification is required, requiring the third party system to provide data source interface, user interface data permissions, etc.",
@@ -595,6 +687,10 @@
"application_name": "Application name",
"application_description": "Application description",
"cross_domain_settings": "Cross-domain settings",
+ "enableCustomModel": "Use specified model",
+ "useModel": "Use model",
+ "defaultModel": "Default model",
+ "customModel": "Specified model",
"third_party_address": "Please enter the embedded third party address,multiple items separated by semicolons",
"set_to_private": "Set as private",
"set_to_public": "Set as public",
@@ -605,7 +701,7 @@
"interface_url": "Interface URL",
"format_is_incorrect": "format is incorrect{msg}",
"domain_format_incorrect": ", start with http:// or https://, no trailing slash (/), multiple domains separated by semicolons",
- "interface_url_incorrect": ",enter a relative path starting with /",
+ "interface_url_incorrect": ", enter a relative path starting with /, or an absolute path starting with http:// or https://",
"aes_enable": "Enable AES encryption",
"aes_enable_tips": "The fields (host, user, password, dataBase, schema) are all encrypted using the AES-CBC-PKCS5Padding encryption method",
"bit": "bit",
@@ -645,7 +741,7 @@
"display_settings": "Display Settings",
"header_text_color": "Header Text Color",
"app_logo": "App Logo",
- "maximum_size_10mb": "Recommended size: 32 x 32, supports JPG, PNG, and SVG, maximum size: 10MB",
+ "maximum_size_10mb": "Recommended size: 32 x 32, supports JPG, PNG, maximum size: 10MB",
"replace": "Replace",
"default_icon_position": "Default Icon Position",
"draggable_position": "Draggable Position",
@@ -655,7 +751,10 @@
"right": "Right",
"welcome_description": "Welcome Description",
"data_analysis_now": "I can query data, generate charts, detect data anomalies, predict data, and more! Enable Smart Data Analysis now!",
- "window_entrance_icon": "Floating window entrance icon"
+ "window_entrance_icon": "Floating window entrance icon",
+ "preview_error": "The current page prohibits embedding using Iframe!",
+ "assistant_app": "Assistant App",
+ "auto_select_ds": "Automatically select data source"
},
"chat": {
"type": "Chart Type",
@@ -666,6 +765,11 @@
"line": "Line",
"pie": "Pie"
},
+ "hide_label": "Hide Label",
+ "show_label": "Show Label",
+ "sort_asc": "Ascending",
+ "sort_desc": "Descending",
+ "sort_none": "No Sort",
"show_sql": "View SQL",
"export_to": "Export As",
"excel": "Excel",
@@ -685,7 +789,33 @@
"exec-sql-err": "Execute SQL failed",
"no_data": "No Data",
"loading_data": "Loading ...",
- "show_error_detail": "Show error info"
+ "show_error_detail": "Show error info",
+ "thousands_separator_setting": "Thousands Separator Setting",
+ "thousands_separator_display": "Apply Thousands Separator Display",
+ "log": {
+ "GENERATE_SQL": "Generate SQL",
+ "GENERATE_CHART": "Generate Chart Structure",
+ "ANALYSIS": "Analyze",
+ "PREDICT_DATA": "Predict",
+ "GENERATE_SQL_WITH_PERMISSIONS": "Apply Row Permissions to SQL",
+ "CHOOSE_DATASOURCE": "Match Datasource",
+ "GENERATE_DYNAMIC_SQL": "Generate Dynamic SQL",
+ "CHOOSE_TABLE": "Match Data Table (Schema)",
+ "FILTER_TERMS": "Match Terms",
+ "FILTER_SQL_EXAMPLE": "Match SQL Examples",
+ "FILTER_CUSTOM_PROMPT": "Match Custom Prompts",
+ "EXECUTE_SQL": "Execute SQL",
+ "GENERATE_PICTURE": "Generate Picture"
+ },
+ "log_system": "Conversation Template",
+ "log_question": "Current Question",
+ "log_answer": "AI Response",
+ "log_history": "History",
+ "find_term_title": "Matched {0} terms",
+ "find_sql_sample_title": "Matched {0} SQL examples",
+ "find_custom_prompt_title": "Matched {0} custom prompts",
+ "query_count_title": "Found {0} data entries",
+ "generate_picture_success": "Image generated"
},
"about": {
"title": "About",
@@ -726,11 +856,11 @@
"website_logo": "Website Logo",
"tab": "Tab",
"replace_image": "Replace Image",
- "larger_than_200kb": "Logo displayed at the top of the website: Recommended size: 48 x 48 pixels, supports JPG, PNG, and SVG, and no larger than 200KB",
+ "larger_than_200kb": "Logo displayed at the top of the website: Recommended size: 48 x 48 pixels, supports JPG, PNG, and no larger than 200KB",
"login_logo": "System Logo",
- "larger_than_200kb_de": "Logo on the right side of the login page: Recommended size: 204 x 52 pixels, supports JPG, PNG, and SVG, and no larger than 200KB",
+ "larger_than_200kb_de": "Logo on the right side of the login page: Recommended size: 204 x 52 pixels, supports JPG, PNG, and no larger than 200KB",
"login_background_image": "Login Background Image",
- "larger_than_5mb": "Background image on the left: Recommended size: 576 x 900 for vector images, 1152 x 1800 for bitmap images, supports JPG, PNG, and SVG, and no larger than 5MB",
+ "larger_than_5mb": "Background image on the left: Recommended size: 576 x 900 for vector images, 1152 x 1800 for bitmap images, supports JPG, PNG, and no larger than 5MB",
"website_name": "Website Name",
"on_webpage_tabs": "Platform name displayed on webpage tabs",
"welcome_message": "Display a welcome message",
@@ -745,6 +875,13 @@
"setting_successfully": "Setting Successfully",
"customize_theme_color": "Customize theme color"
},
+ "platform": {
+ "title": "Platform Integration",
+ "status_open": "Enabled",
+ "status_close": "Disabled",
+ "access_in": "Access",
+ "can_enable_it": "Can be enabled after successful test connection"
+ },
"authentication": {
"invalid": "Invalid",
"valid": "Valid",
@@ -782,7 +919,7 @@
"ou": "User OU",
"ou_placeholder": "Example: ou=users,dc=example,dc=com",
"user_filter": "User Filter",
- "user_filter_placeholder": "Example: uid",
+ "user_filter_placeholder": "For example: uid or uid {0} email. Multiple attributes should be separated by {1}",
"ldap_field_mapping_placeholder": "Example: {\"account\": \"ldapAccount\", \"name\": \"ldapName\", \"email\": \"mail\"}",
"be_turned_on": "Once the test connection is confirmed to be effective, it can be enabled"
},
@@ -797,7 +934,10 @@
"platform_disable": "{0} settings are not enabled!",
"input_account": "Please enter account",
"redirect_2_auth": "Redirecting to {0} authentication, {1} seconds...",
- "redirect_immediately": "Redirecting immediately"
+ "redirect_immediately": "Redirecting immediately",
+ "permission_invalid": "Authentication invalid [Current account has insufficient permissions]",
+ "scan_qr_login": " Scan QR Code",
+ "no_auth_error": "Not authenticated. Redirecting to login page in {0} seconds..."
},
"supplier": {
"alibaba_cloud_bailian": "Alibaba Cloud Bailian",
@@ -810,9 +950,36 @@
"kimi": "Kimi",
"tencent_cloud": "Tencent Cloud",
"volcano_engine": "Volcano Engine",
+ "minimax": "MiniMax",
"generic_openai": "Generic OpenAI"
},
"modelType": {
"llm": "Large Language Model"
+ },
+ "audit": {
+ "system_log": "System Log",
+ "search_log": "Search Log",
+ "operation_type": "Operation Type",
+ "operation_details": "Operation Details",
+ "operation_user_name": "Operating User",
+ "operation_status": "Operation Status",
+ "user_name": "User Name",
+ "oid_name": "Workspace",
+ "ip_address": "IP Address",
+ "create_time": "Creation Time",
+ "no_log": "No Logs Available",
+ "all_236_terms": "Export all {msg} log entries?",
+ "export_hint": "Export all logs?",
+ "export": "Export",
+ "success": "Success",
+ "failed": "Failed",
+ "failed_info": "Operation Failure Details",
+ "opt_time": "Operation Time"
+ },
+ "api_key": {
+ "info_tips": "The API Key is your credential for accessing the SQLBot API, with full permissions for your account. Please keep it secure! Do not disclose the API Key in any public channels to avoid security risks from unauthorized use.",
+ "create": "Create",
+ "to_doc": "View API",
+ "trigger_limit": "Supports up to {0} API Keys"
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts
index df718e652..c06dfcd98 100644
--- a/frontend/src/i18n/index.ts
+++ b/frontend/src/i18n/index.ts
@@ -1,16 +1,42 @@
import { createI18n } from 'vue-i18n'
import en from './en.json'
import zhCN from './zh-CN.json'
+import zhTw from './zh-TW.json'
import koKR from './ko-KR.json'
import elementEnLocale from 'element-plus-secondary/es/locale/lang/en'
import elementZhLocale from 'element-plus-secondary/es/locale/lang/zh-cn'
+import elementTwLocale from 'element-plus-secondary/es/locale/lang/zh-tw'
import { useCache } from '@/utils/useCache'
import { getBrowserLocale } from '@/utils/utils'
const elementKoLocale = elementEnLocale
const { wsCache } = useCache()
+const isEmbeddedRoute = () => {
+ const hash = window.location.hash
+ if (!hash) return false
+ const hashPath = hash.substring(1).split('?')[0]
+ return ['/assistant', '/embeddedPage', '/embeddedCommon'].includes(hashPath)
+}
+
+const getUrlLang = () => {
+ try {
+ const hash = window.location.hash
+ if (!hash) return null
+ const hashQuery = hash.substring(1).split('?')[1]
+ if (!hashQuery) return null
+ return new URLSearchParams(hashQuery).get('lang')
+ } catch {
+ return null
+ }
+}
+
const getDefaultLocale = () => {
+ // 嵌入式页面的 URL lang 参数(第三种国际化渠道),优先级最高
+ const urlLang = getUrlLang()
+ if (urlLang && isEmbeddedRoute()) {
+ return urlLang
+ }
return wsCache.get('user.language') || getBrowserLocale() || 'zh-CN'
}
@@ -23,6 +49,10 @@ const messages = {
...zhCN,
el: elementZhLocale,
},
+ 'zh-TW': {
+ ...zhTw,
+ el: elementTwLocale,
+ },
'ko-KR': {
...koKR,
el: elementKoLocale,
@@ -40,6 +70,7 @@ export const i18n = createI18n({
const elementLocales = {
en: elementEnLocale,
'zh-CN': elementZhLocale,
+ 'zh-TW': elementTwLocale,
'ko-KR': elementKoLocale,
} as const
diff --git a/frontend/src/i18n/ko-KR.json b/frontend/src/i18n/ko-KR.json
index 00724c6dd..100efe262 100644
--- a/frontend/src/i18n/ko-KR.json
+++ b/frontend/src/i18n/ko-KR.json
@@ -7,20 +7,91 @@
"AI Model Configuration": "모델 구성",
"Details": "세부"
},
+ "variables": {
+ "please_select_date": "날짜를 선택하십시오",
+ "number_variable_error": "올바른 숫자 범위를 입력하십시오.",
+ "built_in": "내장형",
+ "normal_value": "정상값",
+ "cannot_be_empty": "변수 값은 비어 있을 수 없습니다.",
+ "1_to_100": "{name}, 숫자 범위는 {min}부터 {max}까지입니다.",
+ "1_to_100_de": "{name}, 날짜 범위는 {min}부터 {max}까지입니다.",
+ "system_variables": "시스템 변수",
+ "variables": "변수",
+ "system": "체계",
+ "search_variables": "변수 검색",
+ "add_variable": "변수 추가",
+ "variable_name": "변수 이름",
+ "variable_type": "변수 유형",
+ "variable_value": "변수 값",
+ "edit_variable": "변수 편집",
+ "no_variables_yet": "아직 변수가 없습니다",
+ "please_enter_value": "값을 입력하세요",
+ "date": "날짜",
+ "start_date": "시작일",
+ "end_date": "종료일",
+ "enter_variable_name": "변수 이름을 입력하세요",
+ "enter_variable_value": "변수 값을 입력하세요"
+ },
+ "authorized_space": {
+ "authorized_space": "권한 있는 공간",
+ "authorized_space_list": "권한 있는 공간 목록",
+ "select_space": "공간 선택",
+ "modify_authorized_space": "권한 있는 공간 수정",
+ "workspaces_authorized": "{num}개의 권한 있는 작업 공간",
+ "number_of_members": "회원 수",
+ "delete_selected_workspaces": "선택한 {msg}개의 작업 공간을 삭제하시겠습니까?",
+ "delete_workspace": "작업 공간을 삭제하시겠습니까: {msg}?",
+ "no_workspace": "작업 공간 없음"
+ },
+ "sync": {
+ "records": "{total}개 중 {num}개의 레코드 표시",
+ "confirm_upload": "업로드 확인",
+ "field_details": "필드 세부 정보",
+ "integration": "플랫폼 통합을 활성화해야 합니다.",
+ "the_existing_user": "해당 사용자가 이미 존재하는 경우 기존 사용자를 덮어씁니다.",
+ "lazy_load": "지연 로딩",
+ "sync_users": "사용자 동기화",
+ "sync_wechat_users": "위챗 사용자 동기화",
+ "sync_dingtalk_users": "딩톡 사용자 동기화",
+ "sync_lark_users": "라크 사용자 동기화",
+ "sync_complete": "동기화 완료",
+ "synced_10_users": "{num}명 동기화 성공",
+ "failed_3_users": "{failed}명 동기화 성공, {success}명 동기화 실패",
+ "download_failure_list": "실패 목록 다운로드",
+ "sync_failed": "동기화 실패",
+ "failed_10_users": "{failed}명 동기화 실패",
+ "continue_syncing": "동기화 계속",
+ "return_to_view": "화면으로 돌아가기"
+ },
"parameter": {
+ "execution_details": "실행 세부 정보",
+ "overview": "개요",
+ "tokens_required": "필요한 토큰 수",
+ "time_execution": "실행 시간",
+ "export_notes": "수출 참고사항",
+ "import_notes": "수입 참고사항",
+ "memo": "메모 업데이트 규칙: 테이블 이름과 필드 이름이 일치하는지 확인합니다. 일치하는 항목이 있으면 테이블 메모와 필드 메모를 업데이트하고, 그렇지 않으면 메모를 변경하지 않고 그대로 둡니다.",
"parameter_configuration": "매개변수 구성",
"question_count_settings": "질문 수 설정",
+ "context_record_count": "컨텍스트 기록 수",
+ "context_record_count_hint": "사용자 질문 라운드 수",
"model_thinking_process": "모델 사고 프로세스 확장",
+ "hide_model_thinking_process": "모델 사고 과정 숨기기",
"rows_of_data": "데이터 1,000행 제한",
- "third_party_platform_settings": "타사 플랫폼 설정",
- "by_third_party_platform": "타사 플랫폼에 의한 자동 사용자 생성",
- "platform_user_organization": "타사 플랫폼 사용자 구성",
+ "third_party_platform_settings": "로그인 인증 설정",
+ "by_third_party_platform": "자동 사용자 생성",
+ "platform_user_organization": "기본 작업 공간",
"platform_user_roles": "타사 플랫폼 사용자 역할",
"excessive_data_volume": "1,000행 데이터 제한을 비활성화하면 과도한 데이터 양으로 인해 시스템 지연이 발생할 수 있습니다.",
+ "sqlbot_name": "데이터 질의 도우미 이름",
+ "show_sql": "SQL 문 보기 허용",
+ "show_log": "실행 로그 표시",
"prompt": "프롬프트",
"disabling_successfully": "비활성화 완료",
"closed_by_default": "질문 수 창에서 모델 사고 프로세스를 기본적으로 확장할지 또는 닫을지 여부를 제어합니다.",
- "and_platform_integration": "범위에는 인증 설정 및 플랫폼 통합이 포함됩니다."
+ "and_platform_integration": "인증 관련",
+ "login_settings": "로그인 설정",
+ "default_login": "기본 로그인 방식"
},
"prompt": {
"default_password:": "기본 비밀번호:{msg}",
@@ -90,6 +161,8 @@
"the_term_gmv": "용어를 삭제하시겠습니까: {msg}?"
},
"common": {
+ "start_time": "시작 시간",
+ "end_time": "종료 시간",
"require": "필수 입력 항목",
"zoom_in": "확대",
"zoom_out": "축소",
@@ -118,6 +191,7 @@
"system_manage": "시스템 관리",
"update_success": "업데이트 성공",
"save_success": "저장 성공",
+ "operation_success": "작업 성공",
"next": "다음 단계",
"save": "저장",
"logout": "로그아웃",
@@ -144,7 +218,8 @@
"password_reset_successful": "비밀번호 재설정 성공",
"or": "또는",
"refresh": "새로고침",
- "input_limit": "길이는 {0}자에서 {1}자 사이여야 합니다"
+ "input_limit": "길이는 {0}자에서 {1}자 사이여야 합니다",
+ "file_size_limit_error": "파일 크기가 제한을 초과했습니다 (최대 {limit})"
},
"dashboard": {
"open_dashboard": "대시보드 열기",
@@ -187,7 +262,7 @@
"creator": "생성자",
"dashboard_id": "대시보드 ID",
"create_time": "생성 시간",
- "updator": "업데이트자",
+ "updater": "업데이트자",
"update_time": "업데이트 시간",
"edit": "편집",
"edit_title": "제목 편집",
@@ -233,6 +308,7 @@
"continue_to_ask": "계속 질문하기:",
"data_analysis": "데이터 분석",
"data_predict": "데이터 예측",
+ "data_regenerated": "재생성",
"chat_search": "검색",
"thinking": "생각 중",
"thinking_step": "사고 과정",
@@ -333,11 +409,19 @@
"success": "연결 성공",
"failed": "연결 실패"
},
- "timeout": "쿼리 시간 초과(초)",
- "address": "주소"
- }
+ "timeout": "연결 시간 초과(초)",
+ "address": "주소",
+ "low_version": "낮은 버전 호환",
+ "ssl": "SSL 활성화",
+ "file_path": "파일 경로",
+ "pool_size": "연결 풀 크기"
+ },
+ "sync_fields": "동기화된 테이블 구조",
+ "sync_fields_success": "테이블 구조 동기화 성공",
+ "sync_fields_failed": "테이블 구조 동기화 실패"
},
"datasource": {
+ "recommended_question": "추천 질문",
"recommended_repetitive_tips": "중복된 문제가 존재합니다",
"recommended_problem_tips": "사용자 정의 구성으로 최소 한 개의 문제를 생성하세요, 각 문제는 2~200자로 작성",
"recommended_problem_configuration": "추천 문제 구성",
@@ -382,6 +466,9 @@
"get_schema": "스키마 가져오기"
},
"model": {
+ "int": "정수",
+ "float": "소수",
+ "string": "문자열",
"default_model": "기본 모델",
"model_type": "모델 유형",
"basic_model": "기본 모델",
@@ -438,15 +525,17 @@
"enable": "활성화",
"disable": "비활성화",
"local_creation": "로컬 생성",
- "feishu": "페이슈",
+ "lark": "페이슈",
+ "larksuite": "페이수 (Pei-su)",
"dingtalk": "딩톡",
- "wechat_for_business": "기업용 위챗",
+ "wecom": "기업용 위챗",
"1240_results": "{msg}개 결과 ",
"clear_conditions": "조건 지우기",
"disabled": "비활성화됨",
"enabled": "활성화됨",
"edit_user": "사용자 편집",
"del_user": "사용자를 삭제하시겠습니까: {msg}?",
+ "del_key": "키를 삭제하시겠습니까?: {msg}?",
"please_first": "먼저",
"download_the_template": "템플릿 다운로드",
"required_and_upload": "하여 요구 사항에 따라 작성한 후 업로드하십시오",
@@ -456,6 +545,7 @@
"import": "가져오기",
"change_file": "파일 변경",
"data_import_completed": "데이터 가져오기 완료",
+ "notes_import_completed": "메모 가져오기 완료",
"imported_100_data": "데이터 {msg}개를 성공적으로 가져왔습니다",
"return_to_view": "돌아가서 보기",
"continue_importing": "계속 가져오기",
@@ -496,6 +586,7 @@
"member_feng_yibudao": "멤버를 제거하시겠습니까: {msg}?",
"select_member": "멤버 선택",
"selected_2_people": "선택됨: {msg}명",
+ "selected_number": "선택됨: {msg}개",
"clear": "지우기",
"historical_dialogue": "과거 대화가 없습니다",
"rename_a_workspace": "작업 공간 이름 바꾸기",
@@ -513,6 +604,7 @@
"operate_with_caution": "삭제 후 해당 작업 공간의 사용자가 제거되고 모든 리소스도 삭제됩니다. 신중하게 작업하십시오."
},
"permission": {
+ "search_field": "검색 필드",
"search_rule_group": "규칙 그룹 검색",
"add_rule_group": "규칙 그룹 추가",
"permission_rule": "권한 규칙",
@@ -595,6 +687,10 @@
"application_name": "애플리케이션 이름",
"application_description": "애플리케이션 설명",
"cross_domain_settings": "교차 도메인 설정",
+ "enableCustomModel": "지정된 모델 사용",
+ "useModel": "사용 모델",
+ "defaultModel": "기본 모델",
+ "customModel": "지정 모델",
"third_party_address": "임베디드할 제3자 주소를 입력하십시오, 여러 항목을 세미콜론으로 구분",
"set_to_private": "비공개로 설정",
"set_to_public": "공개로 설정",
@@ -604,7 +700,7 @@
"interface_url": "인터페이스 URL",
"format_is_incorrect": "형식이 올바르지 않습니다{msg}",
"domain_format_incorrect": ", http:// 또는 https://로 시작해야 하며, 슬래시(/)로 끝날 수 없습니다. 여러 도메인은 세미콜론으로 구분합니다",
- "interface_url_incorrect": ", 상대 경로를 입력해주세요. /로 시작합니다",
+ "interface_url_incorrect": ", 상대 경로는 /로 시작하거나, 절대 경로는 http:// 또는 https://로 시작하는 경로를 입력해 주세요.",
"aes_enable": "AES 암호화 활성화",
"aes_enable_tips": "암호화 필드 (host, user, password, dataBase, schema)는 모두 AES-CBC-PKCS5Padding 암호화 방식을 사용합니다",
"bit": "비트",
@@ -645,7 +741,7 @@
"display_settings": "표시 설정",
"header_text_color": "헤더 텍스트 색상",
"app_logo": "애플리케이션 로고",
- "maximum_size_10mb": "권장 크기 32 x 32, JPG, PNG, SVG 지원, 크기 10MB 이하",
+ "maximum_size_10mb": "권장 크기 32 x 32, JPG, PNG 지원, 크기 10MB 이하",
"replace": "교체",
"default_icon_position": "아이콘 기본 위치",
"draggable_position": "드래그 가능한 위치",
@@ -655,7 +751,10 @@
"right": "오른쪽",
"welcome_description": "환영 메시지 설명",
"data_analysis_now": "저는 데이터 조회, 차트 생성, 데이터 이상 감지, 데이터 예측 등을 할 수 있습니다. 빨리 스마트 데이터 조회를 시작하세요~",
- "window_entrance_icon": "플로팅 윈도우 입구 아이콘"
+ "window_entrance_icon": "플로팅 윈도우 입구 아이콘",
+ "preview_error": "현재 페이지는 Iframe 임베딩을 허용하지 않습니다!",
+ "assistant_app": "어시스턴트 앱",
+ "auto_select_ds": "자동 데이터 소스 선택"
},
"chat": {
"type": "차트 유형",
@@ -666,6 +765,11 @@
"line": "선 차트",
"pie": "원형 차트"
},
+ "hide_label": "라벨 숨기기",
+ "show_label": "라벨 표시",
+ "sort_asc": "오름차순",
+ "sort_desc": "내림차순",
+ "sort_none": "정렬 안 함",
"show_sql": "SQL 보기",
"export_to": "다음으로 내보내기",
"excel": "Excel",
@@ -685,7 +789,33 @@
"exec-sql-err": "SQL 실행 실패",
"no_data": "데이터가 없습니다",
"loading_data": "로딩 중 ...",
- "show_error_detail": "구체적인 정보 보기"
+ "show_error_detail": "구체적인 정보 보기",
+ "thousands_separator_setting": "천 단위 구분 기호 설정",
+ "thousands_separator_display": "천 단위 구분 기호 표시 적용",
+ "log": {
+ "GENERATE_SQL": "SQL 생성",
+ "GENERATE_CHART": "차트 구조 생성",
+ "ANALYSIS": "분석",
+ "PREDICT_DATA": "예측",
+ "GENERATE_SQL_WITH_PERMISSIONS": "SQL에 행 권한 적용",
+ "CHOOSE_DATASOURCE": "데이터 소스 매칭",
+ "GENERATE_DYNAMIC_SQL": "동적 SQL 생성",
+ "CHOOSE_TABLE": "데이터 테이블 매칭 (스키마)",
+ "FILTER_TERMS": "용어 매칭",
+ "FILTER_SQL_EXAMPLE": "SQL 예시 매칭",
+ "FILTER_CUSTOM_PROMPT": "사용자 정의 프롬프트 매칭",
+ "EXECUTE_SQL": "SQL 실행",
+ "GENERATE_PICTURE": "이미지 생성"
+ },
+ "log_system": "대화 템플릿",
+ "log_question": "현재 질문",
+ "log_answer": "AI 응답",
+ "log_history": "기록",
+ "find_term_title": "{0}개의 용어 매칭됨",
+ "find_sql_sample_title": "{0}개의 SQL 예시 매칭됨",
+ "find_custom_prompt_title": "{0}개의 사용자 정의 프롬프트 매칭됨",
+ "query_count_title": "{0}개의 데이터 항목 발견됨",
+ "generate_picture_success": "이미지가 생성되었습니다"
},
"about": {
"title": "정보",
@@ -726,11 +856,11 @@
"website_logo": "웹사이트 로고",
"tab": "페이지 탭",
"replace_image": "이미지 교체",
- "larger_than_200kb": "상단 웹사이트에 표시되는 로고, 권장 크기 48 x 48, JPG, PNG, SVG 지원, 크기 200KB 이하",
+ "larger_than_200kb": "상단 웹사이트에 표시되는 로고, 권장 크기 48 x 48, JPG, PNG 지원, 크기 200KB 이하",
"login_logo": "시스템 로고",
- "larger_than_200kb_de": "로그인 페이지 오른쪽 로고, 권장 크기 204*52, JPG, PNG, SVG 지원, 크기 200KB 이하",
+ "larger_than_200kb_de": "로그인 페이지 오른쪽 로고, 권장 크기 204*52, JPG, PNG 지원, 크기 200KB 이하",
"login_background_image": "로그인 배경 이미지",
- "larger_than_5mb": "왼쪽 배경 이미지, 벡터 이미지 권장 크기 576*900, 비트맵 권장 크기 1152*1800; JPG, PNG, SVG 지원, 크기 5MB 이하",
+ "larger_than_5mb": "왼쪽 배경 이미지, 벡터 이미지 권장 크기 576*900, 비트맵 권장 크기 1152*1800; JPG, PNG 지원, 크기 5MB 이하",
"website_name": "웹사이트 이름",
"on_webpage_tabs": "웹페이지 탭에 표시되는 플랫폼 이름",
"welcome_message": "환영 메시지 표시",
@@ -745,6 +875,13 @@
"setting_successfully": "설정 성공",
"customize_theme_color": "사용자 정의 테마 색상"
},
+ "platform": {
+ "title": "플랫폼 연동",
+ "status_open": "활성화됨",
+ "status_close": "비활성화됨",
+ "access_in": "접속",
+ "can_enable_it": "연결 테스트 성공 후 활성화 가능"
+ },
"authentication": {
"invalid": "유효하지 않음",
"valid": "유효함",
@@ -782,7 +919,7 @@
"ou": "사용자 OU",
"ou_placeholder": "예: ou=users,dc=example,dc=com",
"user_filter": "사용자 필터",
- "user_filter_placeholder": "예: uid",
+ "user_filter_placeholder": "예: uid 또는 uid {0} email. 여러 속성은 {1}로 구분합니다",
"ldap_field_mapping_placeholder": "예: {\"account\": \"ldapAccount\", \"name\": \"ldapName\", \"email\": \"mail\"}",
"be_turned_on": "테스트 연결이 유효한 것으로 확인되면 활성화할 수 있습니다"
},
@@ -797,7 +934,10 @@
"platform_disable": "{0} 설정이 활성화되지 않았습니다!",
"input_account": "계정을 입력해 주세요",
"redirect_2_auth": "{0} 인증으로 리디렉션 중입니다, {1}초...",
- "redirect_immediately": "지금 이동"
+ "redirect_immediately": "지금 이동",
+ "permission_invalid": "인증 무효 [현재 계정의 권한이 부족합니다]",
+ "scan_qr_login": " QR 코드 스캔",
+ "no_auth_error": "인증되지 않았습니다. {0}초 후 로그인 페이지로 이동합니다..."
},
"supplier": {
"alibaba_cloud_bailian": "알리바바 클라우드 바이리엔",
@@ -810,9 +950,36 @@
"kimi": "Kimi",
"tencent_cloud": "텐센트 클라우드",
"volcano_engine": "볼케이노 엔진",
+ "minimax": "MiniMax",
"generic_openai": "범용 OpenAI"
},
"modelType": {
"llm": "대형 언어 모델"
+ },
+ "audit": {
+ "system_log": "시스템 로그",
+ "search_log": "검색 로그",
+ "operation_type": "작업 유형",
+ "operation_details": "작업 상세",
+ "operation_user_name": "작업 사용자",
+ "operation_status": "작업 상태",
+ "user_name": "사용자 이름",
+ "oid_name": "작업 공간",
+ "ip_address": "IP 주소",
+ "create_time": "생성 시간",
+ "no_log": "로그 없음",
+ "all_236_terms": "전체 {msg}개의 로그를 내보내시겠습니까?",
+ "export_hint": "모든 로그를 내보내시겠습니까?",
+ "export": "내보내기",
+ "success": "성공",
+ "failed": "실패",
+ "failed_info": "작업 실패 상세",
+ "opt_time": "작업 시간"
+ },
+ "api_key": {
+ "info_tips": "API 키는 SQLBot API에 액세스하는 비밀키로 계정의 모든 권한을 갖고 있습니다. 꼭 안전하게 보관하세요! 외부 채널에 어떠한 방식으로도 API 키를 공개하지 마시고, 타인이 악용하여 보안 위협이 발생하는 것을 방지하세요.",
+ "create": "생성",
+ "to_doc": "API 보기",
+ "trigger_limit": "최대 {0}개의 API 키 생성 지원"
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/i18n/zh-CN.json b/frontend/src/i18n/zh-CN.json
index 0be8b96bd..9f5cc9b24 100644
--- a/frontend/src/i18n/zh-CN.json
+++ b/frontend/src/i18n/zh-CN.json
@@ -7,20 +7,91 @@
"AI Model Configuration": "模型配置",
"Details": "详情"
},
+ "variables": {
+ "please_select_date": "请选择日期",
+ "number_variable_error": "请输入正确的数值范围",
+ "built_in": "已内置",
+ "normal_value": "常规值",
+ "cannot_be_empty": "变量值不能为空",
+ "1_to_100": "{name}数值范围 {min}~{max}",
+ "1_to_100_de": "{name}日期范围 {min}~{max}",
+ "system_variables": "系统变量",
+ "variables": "变量",
+ "system": "系统",
+ "search_variables": "搜索变量",
+ "add_variable": "添加变量",
+ "variable_name": "变量名称",
+ "variable_type": "变量类型",
+ "variable_value": "变量值",
+ "edit_variable": "编辑变量",
+ "no_variables_yet": "暂无变量",
+ "please_enter_value": "请输入数值",
+ "date": "日期",
+ "start_date": "开始日期",
+ "end_date": "结束日期",
+ "enter_variable_name": "请输入变量名称",
+ "enter_variable_value": "请输入变量值"
+ },
+ "authorized_space": {
+ "authorized_space": "授权空间",
+ "authorized_space_list": "授权空间列表",
+ "select_space": "选择空间",
+ "modify_authorized_space": "修改授权空间",
+ "workspaces_authorized": "已授权 {num} 个工作空间",
+ "number_of_members": "成员数量",
+ "delete_selected_workspaces": "是否移除选中的 {msg} 个工作空间?",
+ "delete_workspace": "是否移除工作空间:{msg}?",
+ "no_workspace": "暂无工作空间"
+ },
+ "sync": {
+ "records": "显示 {num} 条数据,共 {total} 条",
+ "confirm_upload": "确定上传",
+ "field_details": "字段详情",
+ "integration": "需开启平台对接",
+ "the_existing_user": "若用户已存在,覆盖旧用户",
+ "lazy_load": "懒加载",
+ "sync_users": "同步用户",
+ "sync_wechat_users": "同步企业微信用户",
+ "sync_dingtalk_users": "同步钉钉用户",
+ "sync_lark_users": "同步飞书用户",
+ "sync_complete": "同步完成",
+ "synced_10_users": "成功同步 {num} 个用户",
+ "failed_3_users": "成功同步 {success} 个用户,同步失败 {failed} 个用户,",
+ "download_failure_list": "下载失败列表",
+ "sync_failed": "同步失败",
+ "failed_10_users": "同步失败 {num} 个用户,",
+ "continue_syncing": "继续同步",
+ "return_to_view": "返回查看"
+ },
"parameter": {
+ "execution_details": "执行详情",
+ "overview": "概览",
+ "tokens_required": "消耗 Tokens",
+ "time_execution": "耗时",
+ "export_notes": "导出备注",
+ "import_notes": "导入备注",
+ "memo": "备注更新规则:根据表名和字段名匹配,如果匹配则更新表备注和字段备注;如果匹配不上,备注保持不变。",
"parameter_configuration": "参数配置",
"question_count_settings": "问数设置",
+ "context_record_count": "上下文记录数",
+ "context_record_count_hint": "用户提问轮数",
"model_thinking_process": "展开模型思考过程",
+ "hide_model_thinking_process": "隐藏模型思考过程",
"rows_of_data": "限制 1000 行数据",
- "third_party_platform_settings": "第三方平台设置",
- "by_third_party_platform": "第三方自动创建用户",
- "platform_user_organization": "第三方平台用户组织",
+ "third_party_platform_settings": "登录认证设置",
+ "by_third_party_platform": "自动创建用户",
+ "platform_user_organization": "默认工作空间",
"platform_user_roles": "第三方平台用户角色",
"excessive_data_volume": "关闭1000行的数据限制后,数据量过大,可能会造成系统卡顿",
+ "sqlbot_name": "问数小助手名称",
+ "show_sql": "允许查看SQL语句",
+ "show_log": "显示执行日志",
"prompt": "提示",
"disabling_successfully": "关闭成功",
"closed_by_default": "在问数窗口中,控制模型思考过程默认展开或者关闭",
- "and_platform_integration": "作用域包括认证设置和平台对接"
+ "and_platform_integration": "登录认证相关",
+ "login_settings": "登录设置",
+ "default_login": "默认登录方式"
},
"prompt": {
"default_password": "默认密码:{msg}",
@@ -90,6 +161,8 @@
"the_term_gmv": "是否删除术语:{msg}?"
},
"common": {
+ "start_time": "开始时间",
+ "end_time": "结束时间",
"require": "必填",
"zoom_in": "放大",
"zoom_out": "缩小",
@@ -118,6 +191,7 @@
"system_manage": "系统管理",
"update_success": "更新成功",
"save_success": "保存成功",
+ "operation_success": "操作成功",
"next": "下一步",
"save": "保存",
"logout": "退出登录",
@@ -138,13 +212,14 @@
"click_to_select_file": "点击选择文件",
"upload_hint_first": "先",
"upload_hint_download_template": "下载模板",
- "upload_hint_end": ",按要求填写后上传",
+ "upload_hint_end": ",按要求填写后上传。",
"continue_to_upload": "继续导入",
"reset_password": "重置密码",
"password_reset_successful": "重置密码成功",
"or": "或者",
"refresh": "刷新",
- "input_limit": "长度在 {0} 到 {1} 个字符"
+ "input_limit": "长度在 {0} 到 {1} 个字符",
+ "file_size_limit_error": "文件大小超出限制(最大{limit})"
},
"dashboard": {
"open_dashboard": "打开仪表板",
@@ -187,7 +262,7 @@
"creator": "创建人",
"dashboard_id": "仪表板ID",
"create_time": "创建时间",
- "updator": "更新人",
+ "updater": "更新人",
"update_time": "更新时间",
"edit": "编辑",
"edit_title": "编辑标题",
@@ -208,7 +283,6 @@
"chart_selected": "已选{0}"
},
"qa": {
- "recommended_repetitive_tips": "Duplicate questions exist",
"retrieve_error": "暂无内容",
"retrieve_again": "重新获取",
"recently": "最近",
@@ -234,6 +308,7 @@
"continue_to_ask": "继续提问:",
"data_analysis": "数据分析",
"data_predict": "数据预测",
+ "data_regenerated": "重新生成",
"chat_search": "搜索",
"thinking": "思考中",
"thinking_step": "思考过程",
@@ -334,11 +409,19 @@
"success": "连接成功",
"failed": "连接失败"
},
- "timeout": "查询超时(秒)",
- "address": "地址"
- }
+ "timeout": "连接超时(秒)",
+ "address": "地址",
+ "low_version": "兼容低版本",
+ "ssl": "启用 SSL",
+ "file_path": "文件路径",
+ "pool_size": "连接池大小"
+ },
+ "sync_fields": "同步表结构",
+ "sync_fields_success": "同步表结构成功",
+ "sync_fields_failed": "同步表结构失败"
},
"datasource": {
+ "recommended_question": "推荐问题",
"recommended_repetitive_tips": "存在重复问题",
"recommended_problem_tips": "自定义配置至少一个问题,每个问题2-200个字符",
"recommended_problem_configuration": "推荐问题配置",
@@ -383,6 +466,9 @@
"get_schema": "获取Schema"
},
"model": {
+ "int": "整数",
+ "float": "小数",
+ "string": "字符串",
"default_model": "默认模型",
"model_type": "模型类型",
"basic_model": "基础模型",
@@ -439,15 +525,17 @@
"enable": "启用",
"disable": "禁用",
"local_creation": "本地创建",
- "feishu": "飞书",
+ "lark": "飞书",
+ "larksuite": "国际飞书",
"dingtalk": "钉钉",
- "wechat_for_business": "企业微信",
+ "wecom": "企业微信",
"1240_results": "{msg} 个结果 ",
"clear_conditions": "清空条件",
"disabled": "已禁用",
"enabled": "已启用",
"edit_user": "编辑用户",
"del_user": "是否删除用户: {msg}?",
+ "del_key": "是否删除key: {msg}?",
"please_first": "请先",
"download_the_template": "下载模板",
"required_and_upload": ",按要求填写后上传",
@@ -457,6 +545,7 @@
"import": "导入",
"change_file": "更换文件",
"data_import_completed": "数据导入完成",
+ "notes_import_completed": "备注导入完成",
"imported_100_data": "成功导入数据 {msg} 条",
"return_to_view": "返回查看",
"continue_importing": "继续导入",
@@ -497,6 +586,7 @@
"member_feng_yibudao": "是否移除成员:{msg}?",
"select_member": "选择成员",
"selected_2_people": "已选:{msg} 人",
+ "selected_number": "已选:{msg} 个",
"clear": "清空",
"historical_dialogue": "暂无历史对话",
"rename_a_workspace": "重命名工作空间",
@@ -514,6 +604,7 @@
"operate_with_caution": "删除后,该工作空间下的用户将被移除,所有资源也将被删除,请谨慎操作。"
},
"permission": {
+ "search_field": "搜索字段",
"search_rule_group": "搜索规则组",
"add_rule_group": "添加规则组",
"permission_rule": "权限规则",
@@ -596,6 +687,10 @@
"application_name": "应用名称",
"application_description": "应用描述",
"cross_domain_settings": "跨域设置",
+ "enableCustomModel": "使用指定大模型",
+ "useModel": "使用模型",
+ "defaultModel": "默认模型",
+ "customModel": "指定模型",
"third_party_address": "请输入嵌入的第三方地址,多个以分号分割",
"set_to_private": "设为私有",
"set_to_public": "设为公共",
@@ -605,7 +700,7 @@
"interface_url": "接口 URL",
"format_is_incorrect": "格式不对{msg}",
"domain_format_incorrect": ",http或https开头,不能以 / 结尾,多个域名以分号(半角)分隔",
- "interface_url_incorrect": ",请填写相对路径,以/开头",
+ "interface_url_incorrect": ",请填写相对路径,以/开头,或者绝对路径以http或https开头",
"aes_enable": "开启 AES 加密",
"aes_enable_tips": "加密字段 (host, user, password, dataBase, schema) 均采用 AES-CBC-PKCS5Padding 加密方式",
"bit": "位",
@@ -646,7 +741,7 @@
"display_settings": "显示设置",
"header_text_color": "头部文本颜色",
"app_logo": "应用 Logo",
- "maximum_size_10mb": "建议尺寸 32 x 32,支持 JPG、PNG、SVG,大小不超过 10MB",
+ "maximum_size_10mb": "建议尺寸 32 x 32,支持 JPG、PNG,大小不超过 10MB",
"replace": "替换",
"default_icon_position": "图标默认位置",
"draggable_position": "可拖拽位置",
@@ -656,7 +751,10 @@
"right": "右",
"welcome_description": "欢迎语描述",
"data_analysis_now": "我可以查询数据、生成图表、检测数据异常、预测数据等赶快开启智能问数吧~",
- "window_entrance_icon": "浮窗入口图标"
+ "window_entrance_icon": "浮窗入口图标",
+ "preview_error": "当前页面禁止使用 Iframe 嵌入!",
+ "assistant_app": "小助手应用",
+ "auto_select_ds": "自动选择数据源"
},
"chat": {
"type": "图表类型",
@@ -667,6 +765,11 @@
"line": "折线图",
"pie": "饼图"
},
+ "hide_label": "隐藏标签",
+ "show_label": "显示标签",
+ "sort_asc": "升序",
+ "sort_desc": "降序",
+ "sort_none": "不排序",
"show_sql": "查看SQL",
"export_to": "导出为",
"excel": "Excel",
@@ -686,7 +789,33 @@
"exec-sql-err": "执行SQL失败",
"no_data": "暂无数据",
"loading_data": "加载中...",
- "show_error_detail": "查看具体信息"
+ "show_error_detail": "查看具体信息",
+ "thousands_separator_setting": "千分位符设置",
+ "thousands_separator_display": "应用千分位符展示",
+ "log": {
+ "GENERATE_SQL": "生成 SQL",
+ "GENERATE_CHART": "生成图表结构",
+ "ANALYSIS": "分析",
+ "PREDICT_DATA": "预测",
+ "GENERATE_SQL_WITH_PERMISSIONS": "拼接 SQL 行权限",
+ "CHOOSE_DATASOURCE": "匹配数据源",
+ "GENERATE_DYNAMIC_SQL": "生成动态 SQL",
+ "CHOOSE_TABLE": "匹配数据表 (Schema)",
+ "FILTER_TERMS": "匹配术语",
+ "FILTER_SQL_EXAMPLE": "匹配 SQL 示例",
+ "FILTER_CUSTOM_PROMPT": "匹配自定义提示词",
+ "EXECUTE_SQL": "执行 SQL",
+ "GENERATE_PICTURE": "生成图片"
+ },
+ "log_system": "对话模板",
+ "log_question": "本次提问",
+ "log_answer": "AI 回答",
+ "log_history": "历史记录",
+ "find_term_title": "匹配到 {0} 个术语",
+ "find_sql_sample_title": "匹配到 {0} 个SQL示例",
+ "find_custom_prompt_title": "匹配到 {0} 个自定义提示词",
+ "query_count_title": "查询到 {0} 条数据",
+ "generate_picture_success": "已生成图片"
},
"about": {
"title": "关于",
@@ -727,11 +856,11 @@
"website_logo": "网站 Logo",
"tab": "页签",
"replace_image": "替换图片",
- "larger_than_200kb": "顶部网站显示的 Logo,建议尺寸 48 x 48,支持 JPG、PNG、SVG,大小不超过 200KB",
+ "larger_than_200kb": "顶部网站显示的 Logo,建议尺寸 48 x 48,支持 JPG、PNG,大小不超过 200KB",
"login_logo": "系统 Logo",
- "larger_than_200kb_de": "登录页面右侧 Logo,建议尺寸 204*52,支持 JPG、PNG、SVG,大小不超过 200KB",
+ "larger_than_200kb_de": "登录页面右侧 Logo,建议尺寸 204*52,支持 JPG、PNG,大小不超过 200KB",
"login_background_image": "登录背景图",
- "larger_than_5mb": "左侧背景图,矢量图建议尺寸 576*900,位图建议尺寸 1152*1800;支持 JPG、PNG、SVG,大小不超过 5M",
+ "larger_than_5mb": "左侧背景图,矢量图建议尺寸 576*900,位图建议尺寸 1152*1800;支持 JPG、PNG,大小不超过 5M",
"website_name": "网站名称",
"on_webpage_tabs": "显示在网页 Tab 的平台名称",
"welcome_message": "显示欢迎语",
@@ -746,6 +875,13 @@
"setting_successfully": "设置成功",
"customize_theme_color": "自定义主题色"
},
+ "platform": {
+ "title": "平台对接",
+ "status_open": "已开启",
+ "status_close": "已关闭",
+ "access_in": "接入",
+ "can_enable_it": "测试连接有效后,可开启"
+ },
"authentication": {
"invalid": "无效",
"valid": "有效",
@@ -783,7 +919,7 @@
"ou": "用户 OU",
"ou_placeholder": "例如:ou=users,dc=example,dc=com",
"user_filter": "用户过滤器",
- "user_filter_placeholder": "例如:uid",
+ "user_filter_placeholder": "例如:uid 或 uid {0} email, 多属性以 {1} 分割",
"ldap_field_mapping_placeholder": "例如:{'{'}\"account\": \"ldapAccount\", \"name\": \"ldapName\", \"email\": \"mail\"{'}'}",
"be_turned_on": "测试连接有效后,可开启"
},
@@ -798,7 +934,10 @@
"platform_disable": "{0}设置未开启!",
"input_account": "请输入账号",
"redirect_2_auth": "正在跳转至 {0} 认证,{1} 秒...",
- "redirect_immediately": "立即跳转"
+ "redirect_immediately": "立即跳转",
+ "permission_invalid": "认证无效【当前账号权限不够】",
+ "scan_qr_login": "扫码登录",
+ "no_auth_error": "未认证,即将跳转登录页面,{0} 秒..."
},
"supplier": {
"alibaba_cloud_bailian": "阿里云百炼",
@@ -811,9 +950,36 @@
"kimi": "Kimi",
"tencent_cloud": "腾讯云",
"volcano_engine": "火山引擎",
+ "minimax": "MiniMax",
"generic_openai": "通用OpenAI"
},
"modelType": {
"llm": "大语言模型"
+ },
+ "audit": {
+ "system_log": "操作日志",
+ "search_log": "搜索日志",
+ "operation_type": "操作类型",
+ "operation_details": "操作详情",
+ "operation_user_name": "操作用户",
+ "operation_status": "操作状态",
+ "user_name": "操作日志",
+ "oid_name": "工作空间",
+ "ip_address": "IP 地址",
+ "create_time": "创建时间",
+ "no_log": "暂无日志",
+ "all_236_terms": "是否导出全部 {msg} 条日志?",
+ "export_hint": "是否导出全部日志?",
+ "export": "导出",
+ "success": "成功",
+ "failed": "失败",
+ "failed_info": "操作失败详情",
+ "opt_time": "操作时间"
+ },
+ "api_key": {
+ "info_tips": "API Key 是您访问 SQLBot API 的密钥,具有账户的完全权限,请您务必妥善保管!不要以任何方式公开 API Key 到外部渠道,避免被他人利用造成安全威胁。",
+ "create": "创建",
+ "to_doc": "查看 API",
+ "trigger_limit": "最多支持创建 {0} 个 API Key"
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/i18n/zh-TW.json b/frontend/src/i18n/zh-TW.json
new file mode 100644
index 000000000..b64649f9a
--- /dev/null
+++ b/frontend/src/i18n/zh-TW.json
@@ -0,0 +1,985 @@
+{
+ "menu": {
+ "add_interface_credentials": "請新增介面憑證",
+ "Data Q&A": "智慧問數",
+ "Data Connections": "資料來源",
+ "Dashboard": "儀表板",
+ "AI Model Configuration": "模型配置",
+ "Details": "詳情"
+ },
+ "variables": {
+ "please_select_date": "請選擇日期",
+ "number_variable_error": "請輸入正確的數值範圍",
+ "built_in": "已內建",
+ "normal_value": "一般值",
+ "cannot_be_empty": "變數值不能為空",
+ "1_to_100": "{name}數值範圍 {min}~{max}",
+ "1_to_100_de": "{name}日期範圍 {min}~{max}",
+ "system_variables": "系統變數",
+ "variables": "變數",
+ "system": "系統",
+ "search_variables": "搜尋變數",
+ "add_variable": "新增變數",
+ "variable_name": "變數名稱",
+ "variable_type": "變數類型",
+ "variable_value": "變數值",
+ "edit_variable": "編輯變數",
+ "no_variables_yet": "暫無變數",
+ "please_enter_value": "請輸入數值",
+ "date": "日期",
+ "start_date": "開始日期",
+ "end_date": "結束日期",
+ "enter_variable_name": "請輸入變數名稱",
+ "enter_variable_value": "請輸入變數值"
+ },
+ "authorized_space": {
+ "authorized_space": "授權空間",
+ "authorized_space_list": "授權空間列表",
+ "select_space": "選擇空間",
+ "modify_authorized_space": "修改授權空間",
+ "workspaces_authorized": "已授權 {num} 個工作空間",
+ "number_of_members": "成員數量",
+ "delete_selected_workspaces": "是否移除選中的 {msg} 個工作空間?",
+ "delete_workspace": "是否移除工作空間:{msg}?",
+ "no_workspace": "暫無工作空間"
+ },
+ "sync": {
+ "records": "顯示 {num} 筆資料,共 {total} 筆",
+ "confirm_upload": "確定上傳",
+ "field_details": "欄位詳情",
+ "integration": "需開啟平台對接",
+ "the_existing_user": "若使用者已存在,覆蓋舊使用者",
+ "lazy_load": "懶加載",
+ "sync_users": "同步使用者",
+ "sync_wechat_users": "同步企業微信使用者",
+ "sync_dingtalk_users": "同步釘釘使用者",
+ "sync_lark_users": "同步飛書使用者",
+ "sync_complete": "同步完成",
+ "synced_10_users": "成功同步 {num} 個使用者",
+ "failed_3_users": "成功同步 {success} 個使用者,同步失敗 {failed} 個使用者,",
+ "download_failure_list": "下載失敗列表",
+ "sync_failed": "同步失敗",
+ "failed_10_users": "同步失敗 {num} 個使用者,",
+ "continue_syncing": "繼續同步",
+ "return_to_view": "返回查看"
+ },
+ "parameter": {
+ "execution_details": "執行詳情",
+ "overview": "概覽",
+ "tokens_required": "消耗 Tokens",
+ "time_execution": "耗時",
+ "export_notes": "匯出備註",
+ "import_notes": "匯入備註",
+ "memo": "備註更新規則:根據表名和欄位名稱匹配,如果匹配則更新表備註和欄位備註;如果匹配不上,備註保持不變。",
+ "parameter_configuration": "參數配置",
+ "question_count_settings": "問數設定",
+ "context_record_count": "上下文記錄數",
+ "context_record_count_hint": "使用者提問輪數",
+ "model_thinking_process": "展開模型思考過程",
+ "hide_model_thinking_process": "隱藏模型思考過程",
+ "rows_of_data": "限制 1000 列資料",
+ "third_party_platform_settings": "登入認證設定",
+ "by_third_party_platform": "自動建立使用者",
+ "platform_user_organization": "預設工作區",
+ "platform_user_roles": "第三方平台使用者角色",
+ "excessive_data_volume": "關閉1000列的資料限制後,資料量過大,可能會造成系統卡頓",
+ "sqlbot_name": "問數小助手名稱",
+ "show_sql": "允許查看SQL語句",
+ "show_log": "顯示執行日誌",
+ "prompt": "提示",
+ "disabling_successfully": "關閉成功",
+ "closed_by_default": "在問數視窗中,控制模型思考過程預設展開或者關閉",
+ "and_platform_integration": "登入認證相關",
+ "login_settings": "登入設定",
+ "default_login": "預設登入方式"
+ },
+ "prompt": {
+ "default_password": "預設密碼:{msg}",
+ "no_sql_sample": "暫無 SQL 範例",
+ "add_sql_sample": "新增 SQL 範例",
+ "no_prompt_words": "暫無提示詞",
+ "customize_prompt_words": "自訂提示詞",
+ "ask_sql": "問數 SQL",
+ "data_analysis": "資料分析",
+ "data_prediction": "資料預測",
+ "add_prompt_word": "新增提示詞",
+ "prompt_word_name": "提示詞名稱",
+ "prompt_word_content": "提示詞內容",
+ "training_data_details": "訓練資料詳情",
+ "selected_prompt_words": "是否刪除選中的 {msg} 筆提示詞?",
+ "replaced_with": "請輸入提示詞,範例:回傳姓名時,只顯示姓氏,名字用*代替",
+ "loss_exercise_caution": "若輸入刪除資料庫、資料表或修改表結構等高風險的操作指令,可能導致資料永久遺失,請務必謹慎!",
+ "edit_prompt_word": "編輯提示詞",
+ "all_236_terms": "是否匯出全部 {msg} 筆{type}提示詞?",
+ "export_hint": "是否匯出全部{type}提示詞?",
+ "prompt_word_name_de": "是否刪除提示詞:{msg}?",
+ "disable_field": "停用欄位",
+ "to_disable_it": "該欄位已配置表關聯關係,若停用後,關聯關係將失效,確定停用?"
+ },
+ "training": {
+ "effective_data_sources": "生效資料來源",
+ "all_data_sources": "所有資料來源",
+ "partial_data_sources": "部分資料來源",
+ "add_it_here": "拖曳左側表名,新增到這裡",
+ "table_relationship_management": "表關係管理",
+ "system_management": "系統管理",
+ "data_training": "SQL 範例庫",
+ "problem_description": "問題描述",
+ "sample_sql": "範例 SQL",
+ "search_problem": "搜尋問題",
+ "add_training_data": "新增範例 SQL",
+ "training_data_details": "範例 SQL 詳情",
+ "training_data_items": "是否刪除選中的 {msg} 筆範例 SQL?",
+ "sql_statement": "SQL 語句",
+ "edit_training_data": "編輯範例 SQL",
+ "all_236_terms": "是否匯出全部 {msg} 筆範例 SQL?",
+ "export_hint": "是否匯出全部範例 SQL?",
+ "sales_this_year": "是否刪除範例 SQL:{msg}?",
+ "upload_success": "匯入成功",
+ "upload_failed": "匯入成功 {success} 筆,失敗 {fail} 筆,失敗資訊詳見:{fail_info}"
+ },
+ "professional": {
+ "cannot_be_repeated": "術語名稱,同義詞不能重複",
+ "code_for_debugging": "複製以下程式碼進行除錯",
+ "full_screen_mode": "全螢幕模式",
+ "editing_terminology": "編輯術語",
+ "professional_terminology": "術語配置",
+ "term_name": "術語名稱",
+ "term_description": "術語描述",
+ "search_term": "搜尋術語",
+ "no_term": "暫無術語",
+ "create_new_term": "新建術語",
+ "export_all": "全部匯出",
+ "professional_term_details": "專業術語詳情",
+ "business_term": "業務術語",
+ "synonyms": "同義詞",
+ "all_236_terms": "是否匯出全部 {msg} 筆術語?",
+ "export_hint": "是否匯出全部術語?",
+ "export": "匯出",
+ "selected_2_terms": "是否刪除選中的 {msg} 筆術語?",
+ "selected_2_terms_de": "是否匯出選中的 {msg} 筆術語?",
+ "the_term_gmv": "是否刪除術語:{msg}?"
+ },
+ "common": {
+ "start_time": "開始時間",
+ "end_time": "結束時間",
+ "require": "必填",
+ "zoom_in": "放大",
+ "zoom_out": "縮小",
+ "the_default_model": "已是預設模型",
+ "as_default_model": "設為預設模型",
+ "no_model_yet": "暫無模型",
+ "intelligent_questioning_platform": "歡迎使用 SQLBot 智慧問數平台",
+ "login": "帳號登入",
+ "login_": "登入",
+ "confirm2": "確定",
+ "excessive_tables_selected": "選擇資料表過量",
+ "to_continue_saving": "選擇的資料表數量為:{msg},已超出30,可能會導致操作逾時或者無回應,是否繼續儲存?",
+ "proceed_with_caution": "刪除後無法復原,請謹慎操作。",
+ "sales_in_2024": "是否刪除對話:{msg}?",
+ "limited_to_30": "資料表數量限制30",
+ "enter_your_password": "請輸入密碼",
+ "your_account_email_address": "請輸入帳號/郵箱",
+ "the_correct_password": "請輸入正確的密碼",
+ "input_content": "請輸入內容",
+ "may_not_exist": "暫無結果,使用者可能不存在",
+ "empty": "",
+ "back": "返回",
+ "confirm": "確認",
+ "cancel": "取消",
+ "search": "查詢",
+ "system_manage": "系統管理",
+ "update_success": "更新成功",
+ "save_success": "儲存成功",
+ "operation_success": "操作成功",
+ "next": "下一步",
+ "save": "儲存",
+ "logout": "登出",
+ "please_input": "請輸入{msg}",
+ "switch_success": "切換成功",
+ "result_count": "個結果",
+ "clear_filter": "清空條件",
+ "reset": "重設",
+ "simplified_chinese": "簡體中文",
+ "korean": "한국어",
+ "traditional_chinese": "繁體中文",
+ "help": "說明",
+ "language": "語言",
+ "english": "English",
+ "re_upload": "重新上傳",
+ "not_exceed_50mb": "支援 XLS、XLSX、CSV 格式,檔案大小不超過 50MB",
+ "excel_file_type_limit": "僅支援 XLS、XLSX 格式",
+ "click_to_select_file": "點擊選擇檔案",
+ "upload_hint_first": "先",
+ "upload_hint_download_template": "下載範本",
+ "upload_hint_end": ",按要求填寫後上傳。",
+ "continue_to_upload": "繼續匯入",
+ "reset_password": "重設密碼",
+ "password_reset_successful": "重設密碼成功",
+ "or": "或者",
+ "refresh": "重新整理",
+ "input_limit": "長度在 {0} 到 {1} 個字元",
+ "file_size_limit_error": "檔案大小超出限制(最大{limit})"
+ },
+ "dashboard": {
+ "open_dashboard": "開啟儀表板",
+ "add_dashboard_name_tips": "請輸入儀表板名稱",
+ "existing_dashboard": "存量儀表板",
+ "add_success": "新增成功",
+ "no_data": "沒有找到相關內容",
+ "new_tab": "新建Tab",
+ "length_limit64": "欄位長度需要在1-64之間",
+ "sort_column": "排序欄位",
+ "sort_type": "排序方式",
+ "time": "時間",
+ "sort_asc": "升序",
+ "sort_desc": "降序",
+ "name_repeat": "儀表板名稱已被使用",
+ "rich_text_tips": "雙擊輸入文字內容",
+ "exit_preview": "退出預覽",
+ "no_chat": "暫無對話",
+ "today": "今日",
+ "this_week": "本週",
+ "earlier": "更早",
+ "add_component_tips": "從頂部工具列中選擇元件,新增到這裡建立儀表板",
+ "add_view": "新增圖表",
+ "delete_dashboard_warn": "是否刪除儀表板:{0}",
+ "rename_dashboard": "重新命名儀表板",
+ "dashboard_name": "儀表板名稱",
+ "select_dashboard_tips": "請在左側選擇儀表板",
+ "no_dashboard": "暫無儀表板",
+ "no_dashboard_info": "暫無內容,點擊下列按鈕進行新增",
+ "search": "搜尋",
+ "time_asc": "按時間升序",
+ "time_desc": "按時間降序",
+ "name_asc": "按名稱升序",
+ "name_desc": "按名稱降序",
+ "select_dashboard": "請選擇儀表板",
+ "name": "名稱",
+ "view": "圖表",
+ "text": "富文字",
+ "preview": "預覽",
+ "creator": "建立人",
+ "dashboard_id": "儀表板ID",
+ "create_time": "建立時間",
+ "updater": "更新人",
+ "update_time": "更新時間",
+ "edit": "編輯",
+ "edit_title": "編輯標題",
+ "length_1_64_characters": "名稱欄位長度1-64個字元",
+ "rename": "重新命名",
+ "delete": "刪除",
+ "delete_tips": "刪除後,此目錄的所有資源都會被刪除,請謹慎操作。",
+ "undo": "復原",
+ "reduction": "復原",
+ "folder": "目錄",
+ "dashboard": "儀表板",
+ "delete_warning": "確定要刪除嗎?",
+ "delete_resource_warn": "確定刪除該{0}嗎?",
+ "delete_success": "刪除成功",
+ "new_folder": "新建目錄",
+ "new_dashboard": "新建儀表板",
+ "add_chart": "新增圖表",
+ "chart_selected": "已選{0}"
+ },
+ "qa": {
+ "retrieve_error": "暫無內容",
+ "retrieve_again": "重新取得",
+ "recently": "最近",
+ "recommend": "推薦",
+ "quick_question": "快捷提問",
+ "new_chat": "新建對話",
+ "start_sqlbot": "開啟問數",
+ "title": "智慧問數",
+ "placeholder": "請輸入您的問題",
+ "ask": "提問",
+ "loading": "載入中...",
+ "no_data": "暫無資料",
+ "error": "發生錯誤,請稍後再試",
+ "question_placeholder": "按下 Enter 提交問題, 或使用 Ctrl + Enter 換行",
+ "greeting": "你好,我是SQLBot,很高興為你服務",
+ "hint_description": "我可以查詢資料、產生圖表、分析資料、預測資料等,請選擇一個資料來源,開啟智慧問數吧~",
+ "select_datasource": "選擇資料來源",
+ "view_more": "查看更多",
+ "selected_datasource": "已選擇資料來源",
+ "empty_datasource": "資料來源為空,請新建後再開啟智慧問數!",
+ "datasource_not_exist": "資料來源不存在",
+ "guess_u_ask": "猜你想問:",
+ "continue_to_ask": "繼續提問:",
+ "data_analysis": "資料分析",
+ "data_predict": "資料預測",
+ "data_regenerated": "重新產生",
+ "chat_search": "搜尋",
+ "thinking": "思考中",
+ "thinking_step": "思考過程",
+ "ask_again": "重新產生",
+ "today": "今天",
+ "week": "7天內",
+ "earlier": "更早以前",
+ "no_time": "沒有時間",
+ "rename_conversation_title": "重新命名對話標題",
+ "conversation_title": "對話標題",
+ "copied": "已複製",
+ "ask_failed": "問數失敗"
+ },
+ "ds": {
+ "title": "資料來源",
+ "local_excelcsv": "本機 Excel/CSV",
+ "add": "新增資料來源",
+ "delete": "刪除資料來源",
+ "name": "資料來源名稱",
+ "type": "資料來源類型",
+ "status": "狀態",
+ "pieces_in_total": "顯示 {ms} 筆資料",
+ "actions": "操作",
+ "test_connection": "測試連線",
+ "check": "驗證",
+ "connection_success": "連線成功",
+ "connection_failed": "連線失敗,請檢查配置",
+ "Search Datasource": "搜尋資料來源",
+ "tables": "資料表",
+ "no_data_tip": "暫無資料,請從左側選擇資料表",
+ "comment": "註解",
+ "table_schema": "表結構",
+ "previous": "上一步",
+ "preview": "資料預覽",
+ "preview_tip": "預覽100筆資料",
+ "field": {
+ "name": "欄位名",
+ "type": "類型",
+ "comment": "註解",
+ "custom_comment": "自訂註解",
+ "status": "狀態"
+ },
+ "edit": {
+ "table_comment": "編輯表註解",
+ "field_comment": "編輯欄位註解",
+ "table_comment_label": "表註解",
+ "field_comment_label": "欄位註解"
+ },
+ "form": {
+ "title": {
+ "add": "新增資料來源",
+ "edit": "編輯資料來源",
+ "choose_tables": "選擇資料表"
+ },
+ "base_info": "基本資訊",
+ "choose_tables": "選擇資料表",
+ "name": "名稱",
+ "description": "描述",
+ "host": "主機名/IP位址",
+ "port": "連接埠",
+ "username": "使用者名稱",
+ "password": "密碼",
+ "database": "資料庫",
+ "connect_mode": "連線方式",
+ "service_name": "服務名",
+ "extra_jdbc": "額外的資料庫連線配置",
+ "schema": "模式",
+ "get_schema": "取得模式",
+ "file": "檔案",
+ "upload_tip": "僅支援 .xls, .xlsx, .csv 格式,大小不超過50MB",
+ "version_tip": {
+ "sqlServer": "支援版本: 2012+",
+ "oracle": "支援版本: 12+",
+ "mysql": "支援版本: 5.6+",
+ "pg": "支援版本: 9.6+"
+ },
+ "selected": "已選擇: {0}/{1}",
+ "validate": {
+ "name_required": "請輸入名稱",
+ "name_length": "長度應在1到50個字元之間",
+ "type_required": "請選擇資料庫類型",
+ "host_required": "請輸入主機位址",
+ "port_required": "請輸入連接埠號",
+ "database_required": "請輸入資料庫名",
+ "mode_required": "請選擇連線方式",
+ "schema_required": "請輸入模式名"
+ },
+ "mode": {
+ "sid": "SID",
+ "service_name": "服務名"
+ },
+ "support_version": "支援版本",
+ "upload": {
+ "button": "上傳",
+ "tip": "僅支援 .xls, .xlsx, .csv 格式,檔案小於 50MB."
+ },
+ "connect": {
+ "success": "連線成功",
+ "failed": "連線失敗"
+ },
+ "timeout": "連線逾時(秒)",
+ "address": "位址",
+ "low_version": "相容低版本",
+ "ssl": "啟用 SSL",
+ "file_path": "文件路徑",
+ "pool_size": "連線池大小"
+ },
+ "sync_fields": "同步表結構",
+ "sync_fields_success": "同步表結構成功",
+ "sync_fields_failed": "同步表結構失敗"
+ },
+ "datasource": {
+ "recommended_question": "推薦問題",
+ "recommended_repetitive_tips": "存在重複問題",
+ "recommended_problem_tips": "自訂配置至少一個問題,每個問題2-200個字元",
+ "recommended_problem_configuration": "推薦問題配置",
+ "problem_generation_method": "問題產生方式",
+ "ai_automatic_generation": "AI 自動產生",
+ "user_defined": "使用者自訂",
+ "question_tips": "請輸入推薦問題",
+ "add_question": "新增問題",
+ "data_source_yet": "暫無資料來源",
+ "search_by_name": "透過名稱搜尋",
+ "search": "搜尋",
+ "all_types": "全部類型",
+ "new_data_source": "新建資料來源",
+ "open_query": "開啟問數",
+ "edit": "編輯",
+ "source_connection_failed": "資料來源連線失敗",
+ "confirm": "確定",
+ "copy": "複製",
+ "the_original_one": "確認恢復為初始密碼嗎?",
+ "incorrect_email_format": "郵箱格式不對",
+ "enabled_status": "啟用狀態",
+ "custom_notes": "自訂備註",
+ "field_type": "欄位類型",
+ "field_name": "欄位名稱",
+ "relevant_content_found": "沒有找到相關內容",
+ "please_enter": "請輸入",
+ "Please_select": "請選擇",
+ "table_notes": "表備註",
+ "field_notes": "欄位備註",
+ "select_all": "全選",
+ "mysql_data_source": "修改 {msg} 資料來源",
+ "configuration_information": "配置資訊",
+ "data_source": "是否刪除資料來源:{msg}?",
+ "operate_with_caution": "被刪除後,將無法對該資料來源進行智慧問數,請謹慎操作。",
+ "data_source_de": "無法刪除資料來源:{msg}",
+ "cannot_be_deleted": "儀表板的圖表使用了該資料來源,不可刪除。",
+ "got_it": "知道了",
+ "field_original_notes": "欄位原始備註",
+ "field_notes_1": "欄位備註",
+ "no_table": "暫無資料表",
+ "go_add": "去新增",
+ "get_schema": "取得Schema"
+ },
+ "model": {
+ "int": "整數",
+ "float": "小數",
+ "string": "字串",
+ "default_model": "預設模型",
+ "model_type": "模型類型",
+ "basic_model": "基礎模型",
+ "set_successfully": "設定成功",
+ "operate_with_caution": "系統預設模型被取代後,智慧問數的結果將會受到影響,請謹慎操作。",
+ "system_default_model": "是否設定 {msg} 為系統預設模型?",
+ "ai_model_configuration": "AI 模型配置",
+ "system_default_model_de": "系統預設模型",
+ "relevant_results_found": "沒有找到相關結果",
+ "add_model": "新增模型",
+ "select_supplier": "選擇供應商",
+ "the_basic_model": "請給基礎模型設定一個名稱",
+ "the_basic_model_de": "請選擇基礎模型",
+ "length_max_error": "{msg}長度不能超過{max}個字元",
+ "model_name": "模型名稱",
+ "custom_model_name": "自訂的模型名稱",
+ "enter_to_add": "清單中未列出的模型,直接輸入模型名稱,Enter 即可新增",
+ "api_domain_name": "API 網域",
+ "advanced_settings": "進階設定",
+ "model_parameters": "模型參數",
+ "add": "新增",
+ "parameters": "參數",
+ "display_name": "顯示名稱",
+ "parameter_value": "參數值",
+ "text": "文字",
+ "number": "數值",
+ "parameter_type": "參數類型",
+ "verification_successful": "驗證成功",
+ "del_default_tip": "無法刪除模型: {msg}",
+ "del_default_warn": "該模型為系統預設模型,請先設定其他模型為系統預設模型,再刪除此模型。",
+ "del_warn_tip": "是否刪除模型: {msg}?",
+ "check_failed": "模型無效【{msg}】",
+ "default_miss": "尚未設定預設大語言模型,因此無法開啟問數,請聯繫管理員設定。",
+ "default_miss_admin": "尚未設定預設大語言模型,因此無法開啟問數。",
+ "to_config": "去設定"
+ },
+ "user": {
+ "workspace": "工作區",
+ "creation_time": "建立時間",
+ "user_source": "使用者來源",
+ "phone_number": "手機號",
+ "email": "郵箱",
+ "user_status": "使用者狀態",
+ "account": "帳號",
+ "name": "姓名",
+ "selected_2_items": "已選 {msg} 筆",
+ "name_account_email": "搜尋姓名、帳號、郵箱",
+ "user_management": "使用者管理",
+ "filter": "篩選",
+ "batch_import": "批次匯入",
+ "add_users": "新增使用者",
+ "selected_2_users": "是否刪除選中的 {msg} 個使用者?",
+ "filter_conditions": "篩選條件",
+ "enable": "啟用",
+ "disable": "停用",
+ "local_creation": "本機建立",
+ "lark": "飛書",
+ "larksuite": "國際飛書",
+ "dingtalk": "釘釘",
+ "wecom": "企業微信",
+ "1240_results": "{msg} 個結果 ",
+ "clear_conditions": "清空條件",
+ "disabled": "已停用",
+ "enabled": "已啟用",
+ "edit_user": "編輯使用者",
+ "del_user": "是否刪除使用者: {msg}?",
+ "del_key": "是否刪除key: {msg}?",
+ "please_first": "請先",
+ "download_the_template": "下載範本",
+ "required_and_upload": ",按要求填寫後上傳",
+ "file": "檔案",
+ "upload_file": "上傳檔案",
+ "xls_format_files": "僅支援xlsx、xls格式的檔案",
+ "import": "匯入",
+ "change_file": "更換檔案",
+ "data_import_completed": "資料匯入完成",
+ "notes_import_completed": "備註匯入完成",
+ "imported_100_data": "成功匯入資料 {msg} 筆",
+ "return_to_view": "返回查看",
+ "continue_importing": "繼續匯入",
+ "import_20_can": "成功匯入資料 {msg} 筆,匯入失敗 {loss} 筆,可 ",
+ "download_error_report": "下載錯誤報告",
+ "modify_and_re_import": ",修改後重新匯入",
+ "data_import_failed": "資料匯入失敗",
+ "failed_100_can": "匯入失敗 {msg} 筆,可",
+ "change_password": "修改密碼",
+ "new_password": "新密碼",
+ "confirm_password": "確認密碼",
+ "old_password": "舊密碼",
+ "title": "使用者管理",
+ "upgrade_pwd": {
+ "title": "修改密碼",
+ "old_pwd": "舊密碼",
+ "new_pwd": "新密碼",
+ "confirm_pwd": "確認密碼",
+ "two_pwd_not_match": "兩次輸入的密碼不一致",
+ "pwd_format_error": "8-20位且至少一位大寫字母、小寫字母、數字、特殊字元"
+ }
+ },
+ "workspace": {
+ "relevant_content_found": "找不到相關內容",
+ "add_workspace": "新增工作區",
+ "administrator": "管理員",
+ "ordinary_member": "一般成員",
+ "people": "人",
+ "name_username_email": "搜尋姓名、使用者名稱、郵箱",
+ "add_member": "新增成員",
+ "member_type": "成員類型",
+ "workspace_name": "工作區名稱",
+ "no_user": "暫無使用者",
+ "remove": "移除",
+ "selected_2_members": "是否移除選中的 {msg} 個成員?",
+ "removed_successfully": "移除成功",
+ "workspace_de_workspace": "是否刪除工作區:{msg}?",
+ "member_feng_yibudao": "是否移除成員:{msg}?",
+ "select_member": "選擇成員",
+ "selected_2_people": "已選:{msg} 人",
+ "selected_number": "已選:{msg} 個",
+ "clear": "清空",
+ "historical_dialogue": "暫無歷史對話",
+ "rename_a_workspace": "重新命名工作區",
+ "return_to_workspace": "返回工作區",
+ "there_are": "有 ",
+ "2_dashboards": "{msg} 個儀表板",
+ "smart_data_centers": "{msg} 個智慧問數",
+ "confirm_to_delete": "資料來源刪除後,相關的問數不可繼續提問,儀表板中的圖表無法正常顯示,請謹慎操作!",
+ "id_account_to_add": "搜尋使用者名稱/帳號新增",
+ "find_user": "尋找使用者",
+ "add_successfully": "新增成功",
+ "member_management": "成員管理",
+ "permission_configuration": "權限配置",
+ "set": "設定",
+ "operate_with_caution": "刪除後,該工作區下的使用者將被移除,所有資源也將被刪除,請謹慎操作。"
+ },
+ "permission": {
+ "search_field": "搜尋欄位",
+ "search_rule_group": "搜尋規則組",
+ "add_rule_group": "新增規則組",
+ "permission_rule": "權限規則",
+ "restricted_user": "受限使用者",
+ "set_rule": "設定規則",
+ "set_user": "設定使用者",
+ "2": "{msg} 個",
+ "238_people": "{msg} 人",
+ "no_permission_rule": "暫無權限規則",
+ "set_permission_rule": "設定權限規則",
+ "basic_information": "基本資訊",
+ "rule_group_name": "規則組名稱",
+ "rule_name": "規則名稱",
+ "type": "類型",
+ "data_source": "資料來源",
+ "data_table": "資料表",
+ "select_restricted_user": "選擇受限使用者",
+ "rule_group_1": "是否刪除規則組:{msg}?",
+ "no_rule": "暫無規則",
+ "row_permission": "列權限",
+ "column_permission": "欄位權限",
+ "rule_rule_1": "是否刪除規則:{msg}?",
+ "no_content": "暫無內容",
+ "edit_column_permission": "編輯欄位權限",
+ "edit_row_permission": "編輯列權限",
+ "add_column_permission": "新增欄位權限",
+ "add_row_permission": "新增列權限",
+ "no_fields_yet": "暫無欄位",
+ "filter_eq": "等於",
+ "filter_not_eq": "不等於",
+ "filter_lt": "小於",
+ "filter_le": "小於等於",
+ "filter_gt": "大於",
+ "filter_ge": "大於等於",
+ "filter_null": "為空",
+ "filter_not_null": "不為空",
+ "filter_empty": "空字串",
+ "filter_not_empty": "非空字串",
+ "filter_include": "包含",
+ "filter_not_include": "不包含",
+ "conditional_filtering": "條件篩選",
+ "add_conditions": "新增條件",
+ "add_relationships": "新增關係",
+ "cannot_be_empty_": "過濾欄位不能為空",
+ "cannot_be_empty_de_ruler": "規則條件不能為空",
+ "filter_value_can_null": "過濾值不能為空",
+ "filter_like": "包含",
+ "filter_not_like": "不包含",
+ "filter_in": "屬於",
+ "filter_not_in": "不屬於",
+ "enter_a_question": "請輸入問題",
+ "new_conversation": "新對話",
+ "send": "傳送",
+ "stop_replying": "停止回覆",
+ "i_am_sqlbot": "你好,我是SQLBot",
+ "predict_data_etc": "我可以查詢資料、產生圖表、分析資料、預測資料等。",
+ "intelligent_data_query": "趕快開啟智慧問數吧~"
+ },
+ "embedded": {
+ "delete_10_apps": "是否刪除 {msg} 個應用?",
+ "click_to_show": "點擊顯示",
+ "click_to_hide": "點擊隱藏",
+ "your_app_secret": "確定更新 APP Secret 嗎?",
+ "proceed_with_caution": "重設後現有的 App Secret 將會失效,請謹慎操作。",
+ "password_length": "密碼長度",
+ "edit_app": "編輯應用",
+ "embedded_management": "嵌入式管理",
+ "embedded_assistant": "小助手嵌入",
+ "embedded_page": "頁面嵌入",
+ "no_application": "暫無應用",
+ "create_application": "建立應用",
+ "basic_application": "基礎應用",
+ "advanced_application": "進階應用",
+ "embed_third_party": "嵌入第三方",
+ "support_is_required": "適用於無需資料驗證的場景,只需將嵌入式程式碼內嵌到第三方的程式碼中,無需做額外的支援",
+ "data_permissions_etc": "適用於需要資料驗證的場景,需要第三方系統提供資料來源介面、使用者介面資料權限等",
+ "create_basic_application": "建立基礎應用",
+ "set_data_source": "設定資料來源",
+ "basic_information": "基本資訊",
+ "application_name": "應用名稱",
+ "application_description": "應用描述",
+ "cross_domain_settings": "跨網域設定",
+ "enableCustomModel": "使用指定模型",
+ "useModel": "使用模型",
+ "defaultModel": "預設模型",
+ "customModel": "指定模型",
+ "third_party_address": "請輸入嵌入的第三方位址,多個以分號分割",
+ "set_to_private": "設為私有",
+ "set_to_public": "設為公共",
+ "public": "公共",
+ "private": "私有",
+ "configure_interface": "配置介面",
+ "interface_url": "介面 URL",
+ "format_is_incorrect": "格式不對{msg}",
+ "domain_format_incorrect": ",http或https開頭,不能以 / 結尾,多個網域名稱以分號(半形)分隔",
+ "interface_url_incorrect": ",請填寫相對路徑,以/開頭,或者絕對路徑以http或https開頭",
+ "aes_enable": "開啟 AES 加密",
+ "aes_enable_tips": "加密欄位 (host, user, password, dataBase, schema) 均採用 AES-CBC-PKCS5Padding 加密方式",
+ "bit": "位",
+ "creating_advanced_applications": "建立進階應用",
+ "credential_acquisition_method": "憑證取得方式",
+ "table_notes": "表備註",
+ "system_credential_type": "來源系統憑證類型",
+ "credential_name": "憑證名稱",
+ "target_credential_location": "目標憑證位置",
+ "target_credential_name": "目標憑證名稱",
+ "target_credential": "目標憑證",
+ "edit_advanced_applications": "編輯進階應用",
+ "edit_basic_applications": "編輯基礎應用",
+ "delete": "是否刪除: {msg}?",
+ "code_to_embed": "複製以下程式碼進行嵌入",
+ "floating_window_mode": "浮窗模式",
+ "copy_successful": "複製成功",
+ "copy_failed": "複製失敗",
+ "open_the_query": "公共的資料來源,允許所有使用者開啟問數;私有資料來源,登入狀態的使用者可以開啟問數",
+ "add_interface_credentials": "新增介面憑證",
+ "edit_interface_credentials": "編輯介面憑證",
+ "duplicate_name": "重複名稱",
+ "duplicate_name_": "重複姓名",
+ "duplicate_account": "重複帳號",
+ "duplicate_email": "重複郵箱",
+ "repeating_parameters": "重複參數",
+ "interface_credentials": "介面憑證",
+ "no_credentials_yet": "暫無憑證",
+ "intelligent_customer_service": "SQLBot 智慧客服",
+ "enter_a_question": "請輸入問題",
+ "new_conversation": "新建對話",
+ "send": "傳送",
+ "stop_replying": "停止回答",
+ "i_am_sqlbot": "你好,我是 SQLBot",
+ "predict_data_etc": "我可以查詢資料、產生圖表、檢測資料異常、預測資料等",
+ "intelligent_data_query": "趕快開啟智慧問數吧~",
+ "origin_format_error": "格式錯誤,http或https開頭,不能以 / 結尾",
+ "display_settings": "顯示設定",
+ "header_text_color": "頭部文字顏色",
+ "app_logo": "應用 Logo",
+ "maximum_size_10mb": "建議尺寸 32 x 32,支援 JPG、PNG,大小不超過 10MB",
+ "replace": "取代",
+ "default_icon_position": "圖示預設位置",
+ "draggable_position": "可拖曳位置",
+ "up": "上",
+ "down": "下",
+ "left": "左",
+ "right": "右",
+ "welcome_description": "歡迎語描述",
+ "data_analysis_now": "我可以查詢資料、產生圖表、檢測資料異常、預測資料等趕快開啟智慧問數吧~",
+ "window_entrance_icon": "浮窗入口圖示",
+ "preview_error": "目前頁面禁止使用 Iframe 嵌入!",
+ "assistant_app": "小助手應用",
+ "auto_select_ds": "自動選擇資料來源"
+ },
+ "chat": {
+ "type": "圖表類型",
+ "chart_type": {
+ "table": "明細表",
+ "bar": "橫條圖",
+ "column": "柱狀圖",
+ "line": "折線圖",
+ "pie": "圓餅圖"
+ },
+ "hide_label": "隱藏標籤",
+ "show_label": "顯示標籤",
+ "sort_asc": "升序",
+ "sort_desc": "降序",
+ "sort_none": "不排序",
+ "show_sql": "檢視SQL",
+ "export_to": "匯出為",
+ "excel": "Excel",
+ "picture": "圖片",
+ "add_to_dashboard": "新增到儀表板",
+ "full_screen": "全螢幕",
+ "exit_full_screen": "退出全螢幕",
+ "sql_generation": "產生SQL",
+ "chart_generation": "產生圖表",
+ "inference_process": "思考過程",
+ "thinking": "思考中",
+ "data_analysis": "資料分析",
+ "data_predict": "資料預測",
+ "data_over_limit": "資料量過大,僅展示前{0}筆資料",
+ "ds_is_invalid": "資料來源無效",
+ "error": "錯誤",
+ "exec-sql-err": "執行SQL失敗",
+ "no_data": "暫無資料",
+ "loading_data": "載入中...",
+ "show_error_detail": "檢視具體資訊",
+ "thousands_separator_setting": "千分位符設定",
+ "thousands_separator_display": "套用千分位符顯示",
+ "log": {
+ "GENERATE_SQL": "產生 SQL",
+ "GENERATE_CHART": "產生圖表結構",
+ "ANALYSIS": "分析",
+ "PREDICT_DATA": "預測",
+ "GENERATE_SQL_WITH_PERMISSIONS": "拼接 SQL 列權限",
+ "CHOOSE_DATASOURCE": "匹配資料來源",
+ "GENERATE_DYNAMIC_SQL": "產生動態 SQL",
+ "CHOOSE_TABLE": "匹配資料表 (Schema)",
+ "FILTER_TERMS": "匹配術語",
+ "FILTER_SQL_EXAMPLE": "匹配 SQL 範例",
+ "FILTER_CUSTOM_PROMPT": "匹配自訂提示詞",
+ "EXECUTE_SQL": "執行 SQL",
+ "GENERATE_PICTURE": "產生圖片"
+ },
+ "log_system": "對話範本",
+ "log_question": "本次提問",
+ "log_answer": "AI 回答",
+ "log_history": "歷史記錄",
+ "find_term_title": "匹配到 {0} 個術語",
+ "find_sql_sample_title": "匹配到 {0} 個SQL範例",
+ "find_custom_prompt_title": "匹配到 {0} 個自訂提示詞",
+ "query_count_title": "查詢到 {0} 筆資料",
+ "generate_picture_success": "已產生圖片"
+ },
+ "about": {
+ "title": "關於",
+ "auth_to": "授權給",
+ "invalid_license": "License 無效",
+ "update_license": "更新 License",
+ "expiration_time": "過期時間",
+ "expirationed": "(已過期)",
+ "auth_num": "授權數量",
+ "version": "版本",
+ "version_num": "版本號",
+ "standard": "社區版",
+ "enterprise": "企業版",
+ "professional": "專業版",
+ "embedded": "嵌入式版",
+ "support": "取得技術支援",
+ "update_success": "更新成功,請重新登入",
+ "serial_no": "序號",
+ "remark": "備註",
+ "back_community": "還原至社區版",
+ "confirm_tips": "確定還原至社區版?"
+ },
+ "license": {
+ "error_tips": "是否重新整理頁面?",
+ "offline_tips": "服務已下線,請聯繫管理員重啟服務!"
+ },
+ "system": {
+ "system_settings": "系統設定",
+ "appearance_settings": "外觀設定",
+ "authentication_settings": "登入認證",
+ "platform_display_theme": "平台顯示主題",
+ "default_turquoise": "預設 (松石綠)",
+ "tech_blue": "科技藍",
+ "custom": "自訂",
+ "platform_login_settings": "平台登入設定",
+ "page_preview": "頁面預覽",
+ "restore_default": "恢復預設",
+ "website_logo": "網站 Logo",
+ "tab": "頁籤",
+ "replace_image": "取代圖片",
+ "larger_than_200kb": "頂部網站顯示的 Logo,建議尺寸 48 x 48,支援 JPG、PNG,大小不超過 200KB",
+ "login_logo": "系統 Logo",
+ "larger_than_200kb_de": "登入頁面右側 Logo,建議尺寸 204*52,支援 JPG、PNG,大小不超過 200KB",
+ "login_background_image": "登入背景圖",
+ "larger_than_5mb": "左側背景圖,向量圖建議尺寸 576*900,點陣圖建議尺寸 1152*1800;支援 JPG、PNG,大小不超過 5M",
+ "website_name": "網站名稱",
+ "on_webpage_tabs": "顯示在網頁 Tab 的平台名稱",
+ "welcome_message": "顯示歡迎語",
+ "the_product_logo": "產品 Logo 下的 歡迎語",
+ "screen_customization_supported": "預設為 SQLBot 登入介面,支援自訂設定",
+ "screen_customization_settings": "預設為 SQLBot 平台介面,支援自訂設定",
+ "platform_settings": "平台設定",
+ "help_documentation": "說明文件",
+ "show_about": "顯示關於",
+ "abort_update": "放棄更新",
+ "save_and_apply": "儲存並套用",
+ "setting_successfully": "設定成功",
+ "customize_theme_color": "自訂主題色"
+ },
+ "platform": {
+ "title": "平台對接",
+ "status_open": "已開啟",
+ "status_close": "已關閉",
+ "access_in": "接入",
+ "can_enable_it": "測試連線有效後,可開啟"
+ },
+ "authentication": {
+ "invalid": "無效",
+ "valid": "有效",
+ "cas_settings": "CAS 設定",
+ "callback_domain_name": "回調位址",
+ "callback_domain_name_error": "回調位址必須是目前的存取網域名稱",
+ "field_mapping": "使用者屬性對應",
+ "field_mapping_placeholder": "例如:{'{'}\"account\": \"saml2Account\", \"name\": \"saml2Name\", \"email\": \"email\"{'}'}",
+ "incorrect_please_re_enter": "格式錯誤,請重新填寫",
+ "in_json_format": "請輸入json格式",
+ "authorize_url": "授權位址",
+ "client_id": "用戶端 ID",
+ "client_secret": "用戶端密鑰",
+ "redirect_url": "回調位址",
+ "logout_redirect_url": "登出回調位址",
+ "logout_redirect_url_placeholder": "登出後預設跳轉至 SQLBot 登入頁面,可自訂設定登出後跳轉位址",
+ "oauth2_settings": "OAuth2 設定",
+ "scope": "授權範圍",
+ "userinfo_url": "使用者資訊位址",
+ "token_url": "權杖位址",
+ "revoke_url": "撤銷位址",
+ "oauth2_field_mapping_placeholder": "例如:{'{'}\"account\": \"oauth2Account\", \"name\": \"oauth2Name\", \"email\": \"email\"{'}'}",
+ "token_auth_method": "Token 認證方式",
+ "userinfo_auth_method": "使用者資訊認證方式",
+ "oidc_settings": "OIDC 設定",
+ "metadata_url": "中繼資料位址",
+ "realm": "領域",
+ "oidc_field_mapping_placeholder": "例如:{'{'}\"account\": \"oidcAccount\", \"name\": \"oidcName\", \"email\": \"email\"{'}'}",
+ "ldap_settings": "LDAP 設定",
+ "server_address": "伺服器位址",
+ "server_address_placeholder": "例如:ldap://ldap.example.com:389",
+ "bind_dn": "繫結 DN",
+ "bind_dn_placeholder": "例如:cn=admin,dc=example,dc=com",
+ "bind_pwd": "繫結密碼",
+ "ou": "使用者 OU",
+ "ou_placeholder": "例如:ou=users,dc=example,dc=com",
+ "user_filter": "使用者過濾器",
+ "user_filter_placeholder": "例如:uid 或 uid {0} email, 多屬性以 {1} 分割",
+ "ldap_field_mapping_placeholder": "例如:{'{'}\"account\": \"ldapAccount\", \"name\": \"ldapName\", \"email\": \"mail\"{'}'}",
+ "be_turned_on": "測試連線有效後,可開啟"
+ },
+ "login": {
+ "default_login": "預設",
+ "ldap_login": "LDAP 登入",
+ "account_login": "帳號登入",
+ "other_login": "其他登入方式",
+ "pwd_invalid_error": "密碼已過期請聯繫管理員修改或重設",
+ "pwd_exp_tips": "密碼在 {0} 天後過期,請盡快修改密碼",
+ "qr_code": "QR Code",
+ "platform_disable": "{0}設定未開啟!",
+ "input_account": "請輸入帳號",
+ "redirect_2_auth": "正在跳轉至 {0} 認證,{1} 秒...",
+ "redirect_immediately": "立即跳轉",
+ "permission_invalid": "認證無效【目前帳號權限不夠】",
+ "scan_qr_login": "掃碼登入",
+ "no_auth_error": "未認證,即將跳轉登入頁面,{0} 秒..."
+ },
+ "supplier": {
+ "alibaba_cloud_bailian": "阿里雲百煉",
+ "qianfan_model": "千帆大模型",
+ "deepseek": "DeepSeek",
+ "tencent_hunyuan": "騰訊混元",
+ "iflytek_spark": "訊飛星火",
+ "gemini": "Gemini",
+ "openai": "OpenAI",
+ "kimi": "Kimi",
+ "tencent_cloud": "騰訊雲",
+ "volcano_engine": "火山引擎",
+ "minimax": "MiniMax",
+ "generic_openai": "通用OpenAI"
+ },
+ "modelType": {
+ "llm": "大語言模型"
+ },
+ "audit": {
+ "system_log": "操作日誌",
+ "search_log": "搜尋日誌",
+ "operation_type": "操作類型",
+ "operation_details": "操作詳情",
+ "operation_user_name": "操作使用者",
+ "operation_status": "操作狀態",
+ "user_name": "操作日誌",
+ "oid_name": "工作區",
+ "ip_address": "IP 位址",
+ "create_time": "建立時間",
+ "no_log": "暫無日誌",
+ "all_236_terms": "是否匯出全部 {msg} 筆日誌?",
+ "export_hint": "是否匯出全部日誌?",
+ "export": "匯出",
+ "success": "成功",
+ "failed": "失敗",
+ "failed_info": "操作失敗詳情",
+ "opt_time": "操作時間"
+ },
+ "api_key": {
+ "info_tips": "API Key 是您存取 SQLBot API 的密鑰,具有帳戶的完全權限,請您務必妥善保管!不要以任何方式公開 API Key 到外部渠道,避免被他人利用造成安全威脅。",
+ "create": "建立",
+ "to_doc": "檢視 API",
+ "trigger_limit": "最多支援建立 {0} 個 API Key"
+ }
+}
diff --git a/frontend/src/main.ts b/frontend/src/main.ts
index f81e9fcbe..08c414c1a 100644
--- a/frontend/src/main.ts
+++ b/frontend/src/main.ts
@@ -1,3 +1,7 @@
+import 'core-js/features/object/has-own'
+// @ts-ignore: css-has-pseudo/browser lacks official type definitions
+import cssHasPseudo from 'css-has-pseudo/browser'
+
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import './style.less'
@@ -7,6 +11,26 @@ import { i18n } from './i18n'
import VueDOMPurifyHTML from 'vue-dompurify-html'
// import 'element-plus/dist/index.css'
+cssHasPseudo(document)
+
+function supportsFlexGap() {
+ const flex = document.createElement('div')
+ flex.style.display = 'flex'
+ flex.style.flexDirection = 'column'
+ flex.style.rowGap = '1px'
+
+ flex.appendChild(document.createElement('div'))
+ flex.appendChild(document.createElement('div'))
+
+ document.body.appendChild(flex)
+ const isSupported = flex.scrollHeight === 1
+ document.body.removeChild(flex)
+
+ return isSupported
+}
+
+document.documentElement.setAttribute('data-no-flex-gap', String(!supportsFlexGap()))
+
const app = createApp(App)
const pinia = createPinia()
diff --git a/frontend/src/router/dynamic.ts b/frontend/src/router/dynamic.ts
new file mode 100644
index 000000000..7555741d0
--- /dev/null
+++ b/frontend/src/router/dynamic.ts
@@ -0,0 +1,121 @@
+import LayoutDsl from '@/components/layout/LayoutDsl.vue'
+
+import Datasource from '@/views/ds/Datasource.vue'
+import SetAssistant from '@/views/system/embedded/iframe.vue'
+import { i18n } from '@/i18n'
+import { useUserStore } from '@/stores/user'
+import type { Router } from 'vue-router'
+const userStore = useUserStore()
+const t = i18n.global.t
+
+const dynamicRouterList = [
+ {
+ path: '/ds',
+ component: LayoutDsl,
+ name: 'ds-menu',
+ redirect: '/ds/index',
+ children: [
+ {
+ path: 'index',
+ name: 'ds',
+ component: Datasource,
+ meta: { title: t('menu.Data Connections'), iconActive: 'ds', iconDeActive: 'noDs' },
+ },
+ ],
+ },
+ {
+ path: '/as',
+ component: LayoutDsl,
+ name: 'as-menu',
+ redirect: '/as/index',
+ children: [
+ {
+ path: 'index',
+ name: 'as',
+ component: SetAssistant,
+ meta: {
+ title: t('embedded.assistant_app'),
+ iconActive: 'embedded',
+ iconDeActive: 'noEmbedded',
+ },
+ },
+ ],
+ meta: { title: t('embedded.assistant_app') },
+ },
+] as any[]
+
+const reduceRouters = (router: Router, invalid_router_name_list: string[]) => {
+ const tree = router.getRoutes()
+ const invalid_name_set = [] as any
+ invalid_router_name_list.forEach((router_name: string) => {
+ router.removeRoute(router_name)
+ invalid_name_set.push(router_name)
+ })
+
+ const pathsSet = new Set(invalid_router_name_list)
+
+ function processNode(node: any): boolean {
+ if (node.name && pathsSet.has(node.name)) {
+ return true
+ }
+ if (node.children) {
+ const newChildren: any[] = []
+
+ for (const child of node.children) {
+ const shouldRemove = processNode(child)
+ if (!shouldRemove) {
+ newChildren.push(child)
+ }
+ }
+
+ if (newChildren.length > 0) {
+ node.children = newChildren
+ return false
+ } else {
+ node.children = undefined
+ }
+ }
+
+ return false
+ }
+
+ let i = 0
+ while (i < tree.length) {
+ if (processNode(tree[i])) {
+ tree.splice(i, 1)
+ } else {
+ i++
+ }
+ }
+}
+
+export const generateDynamicRouters = (router: Router) => {
+ if (userStore.isAdmin || userStore.isSpaceAdmin) {
+ dynamicRouterList.forEach((item: any) => {
+ if (!item.parent) {
+ router.addRoute(item)
+ } else {
+ router.addRoute(item.parent, item)
+ const parentRoute: any = router.getRoutes().find((r: any) => r.name === item.parent)
+ if (parentRoute?.children) {
+ parentRoute.children.push(item)
+ }
+ }
+ })
+ } else {
+ const router_name_list = [] as string[]
+ const stack = [...dynamicRouterList]
+ while (stack.length) {
+ const item = stack.pop()
+ if (item.name) {
+ router_name_list.push(item.name)
+ }
+ if (item.children?.length) {
+ item.children.forEach((child: any) => {
+ stack.push(child)
+ })
+ }
+ }
+ reduceRouters(router, router_name_list)
+ }
+}
diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts
index 8ba1f21f0..b1c266752 100644
--- a/frontend/src/router/index.ts
+++ b/frontend/src/router/index.ts
@@ -5,26 +5,34 @@ import LayoutDsl from '@/components/layout/LayoutDsl.vue'
import SinglePage from '@/components/layout/SinglePage.vue'
import login from '@/views/login/index.vue'
import chat from '@/views/chat/index.vue'
-import Datasource from '@/views/ds/Datasource.vue'
import DashboardEditor from '@/views/dashboard/editor/index.vue'
import DashboardPreview from '@//views/dashboard/preview/SQPreviewSingle.vue'
import Dashboard from '@/views/dashboard/index.vue'
import Model from '@/views/system/model/Model.vue'
-import Embedded from '@/views/system/embedded/index.vue'
+// import Embedded from '@/views/system/embedded/index.vue'
+// import SetAssistant from '@/views/system/embedded/iframe.vue'
+import SystemEmbedded from '@/views/system/embedded/Page.vue'
+import Variables from '@/views/system/variables/index.vue'
+
import assistantTest from '@/views/system/embedded/Test.vue'
import assistant from '@/views/embedded/index.vue'
import EmbeddedPage from '@/views/embedded/page.vue'
+import EmbeddedCommon from '@/views/embedded/common.vue'
import Member from '@/views/system/member/index.vue'
import Professional from '@/views/system/professional/index.vue'
import Training from '@/views/system/training/index.vue'
import Prompt from '@/views/system/prompt/index.vue'
+import Audit from '@/views/system/audit/index.vue'
import Appearance from '@/views/system/appearance/index.vue'
import Parameter from '@/views/system/parameter/index.vue'
import Authentication from '@/views/system/authentication/index.vue'
+import Platform from '@/views/system/platform/index.vue'
import Permission from '@/views/system/permission/index.vue'
import User from '@/views/system/user/User.vue'
import Workspace from '@/views/system/workspace/index.vue'
import Page401 from '@/views/error/index.vue'
+import ChatPreview from '@/views/chat/preview.vue'
+
import { i18n } from '@/i18n'
import { watchRouter } from './watch'
@@ -63,9 +71,10 @@ export const routes = [
},
],
},
- {
+ /* {
path: '/ds',
component: LayoutDsl,
+ name: 'ds-menu',
redirect: '/ds/index',
children: [
{
@@ -75,7 +84,7 @@ export const routes = [
meta: { title: t('menu.Data Connections'), iconActive: 'ds', iconDeActive: 'noDs' },
},
],
- },
+ }, */
{
path: '/dashboard',
component: LayoutDsl,
@@ -95,6 +104,7 @@ export const routes = [
},
{
path: '/set',
+ name: 'set',
component: LayoutDsl,
redirect: '/set/member',
meta: { title: t('workspace.set'), iconActive: 'set', iconDeActive: 'noSet' },
@@ -111,6 +121,12 @@ export const routes = [
component: Permission,
meta: { title: t('workspace.permission_configuration') },
},
+ /* {
+ path: '/set/assistant',
+ name: 'setAssistant',
+ component: SetAssistant,
+ meta: { title: t('embedded.assistant_app') },
+ }, */
{
path: '/set/professional',
name: 'professional',
@@ -145,6 +161,7 @@ export const routes = [
},
{
path: '/system',
+ name: 'system',
component: LayoutDsl,
redirect: '/system/user',
meta: { hidden: true },
@@ -178,7 +195,7 @@ export const routes = [
{
path: 'embedded',
name: 'embedded',
- component: Embedded,
+ component: SystemEmbedded,
meta: {
title: t('embedded.embedded_management'),
iconActive: 'embedded',
@@ -203,14 +220,32 @@ export const routes = [
component: Parameter,
meta: { title: t('parameter.parameter_configuration') },
},
+ {
+ path: 'variables',
+ name: 'variables',
+ component: Variables,
+ meta: { title: t('variables.system_variables') },
+ },
{
path: 'authentication',
name: 'authentication',
component: Authentication,
meta: { title: t('system.authentication_settings') },
},
+ {
+ path: 'platform',
+ name: 'platform',
+ component: Platform,
+ meta: { title: t('platform.title') },
+ },
],
},
+ {
+ path: 'audit',
+ name: 'audit',
+ component: Audit,
+ meta: { title: t('audit.system_log'), iconActive: 'log', iconDeActive: 'noLog' },
+ },
],
},
@@ -224,11 +259,26 @@ export const routes = [
name: 'embeddedPage',
component: EmbeddedPage,
},
+ {
+ path: '/embeddedCommon',
+ name: 'embeddedCommon',
+ component: EmbeddedCommon,
+ },
{
path: '/assistantTest',
name: 'assistantTest',
component: assistantTest,
},
+ {
+ path: '/chatPreview',
+ name: 'chatPreview',
+ component: ChatPreview,
+ },
+ {
+ path: '/admin-login',
+ name: 'admin-login',
+ component: login,
+ },
{
path: '/401',
name: '401',
diff --git a/frontend/src/router/watch.ts b/frontend/src/router/watch.ts
index 7c63f1314..e862d286f 100644
--- a/frontend/src/router/watch.ts
+++ b/frontend/src/router/watch.ts
@@ -4,19 +4,23 @@ import { useAppearanceStoreWithOut } from '@/stores/appearance'
import { useUserStore } from '@/stores/user'
import { request } from '@/utils/request'
import type { Router } from 'vue-router'
+import { generateDynamicRouters } from './dynamic'
+import { toLoginPage } from '@/utils/utils'
const appearanceStore = useAppearanceStoreWithOut()
const userStore = useUserStore()
const { wsCache } = useCache()
-const whiteList = ['/login']
-const assistantWhiteList = ['/assistant', '/embeddedPage', '/401']
+const whiteList = ['/login', '/admin-login']
+const assistantWhiteList = ['/assistant', '/embeddedPage', '/embeddedCommon', '/401']
+
+const wsAdminRouterList = ['/ds/index', '/as/index']
export const watchRouter = (router: Router) => {
router.beforeEach(async (to: any, from: any, next: any) => {
await loadXpackStatic()
await appearanceStore.setAppearance()
LicenseGenerator.generateRouters(router)
if (to.path.startsWith('/login') && userStore.getUid) {
- next('/')
+ next(to?.query?.redirect || '/')
return
}
if (assistantWhiteList.includes(to.path)) {
@@ -30,17 +34,29 @@ export const watchRouter = (router: Router) => {
}
if (!token) {
// ElMessage.error('Please login first')
- next('/login')
+ next(toLoginPage(to.fullPath))
return
}
if (!userStore.getUid) {
await userStore.info()
+ generateDynamicRouters(router)
+ const isFirstDynamicPath = to?.path && ['/ds/index', '/as/index'].includes(to.path)
+ if (isFirstDynamicPath) {
+ if (userStore.isSpaceAdmin) {
+ next({ ...to, replace: true })
+ return
+ }
+ }
+ }
+ if (to.path === '/docs') {
+ location.href = to.fullPath
+ return
}
if (to.path === '/' || accessCrossPermission(to)) {
next('/chat')
return
}
- if (to.path === '/login') {
+ if (to.path === '/login' || to.path === '/admin-login') {
console.info(from)
next('/chat')
} else {
@@ -53,9 +69,14 @@ const accessCrossPermission = (to: any) => {
if (!to?.path) return false
return (
(to.path.startsWith('/system') && !userStore.isAdmin) ||
- (to.path.startsWith('/set') && !userStore.isSpaceAdmin)
+ (to.path.startsWith('/set') && !userStore.isSpaceAdmin) ||
+ (isWsAdminRouter(to) && !userStore.isSpaceAdmin)
)
}
+
+const isWsAdminRouter = (to?: any) => {
+ return wsAdminRouterList.some((item: string) => to?.path?.startsWith(item))
+}
const loadXpackStatic = () => {
if (document.getElementById('sqlbot_xpack_static')) {
return Promise.resolve()
diff --git a/frontend/src/stores/appearance.ts b/frontend/src/stores/appearance.ts
index 2093f300a..ed368a7dc 100644
--- a/frontend/src/stores/appearance.ts
+++ b/frontend/src/stores/appearance.ts
@@ -315,7 +315,7 @@ const setLinkIcon = (linkWeb?: string) => {
if (linkWeb) {
link['href'] = baseUrl + linkWeb
} else {
- link['href'] = '/LOGO-fold.svg'
+ link['href'] = `${location.pathname}LOGO-fold.svg`
}
}
}
diff --git a/frontend/src/stores/assistant.ts b/frontend/src/stores/assistant.ts
index 46b7a7893..45474fcbd 100644
--- a/frontend/src/stores/assistant.ts
+++ b/frontend/src/stores/assistant.ts
@@ -1,13 +1,10 @@
import { defineStore } from 'pinia'
import { store } from './index'
import { chatApi, ChatInfo } from '@/api/chat'
-import { useCache } from '@/utils/useCache'
-
-const { wsCache } = useCache()
-const flagKey = 'sqlbit-assistant-flag'
type Resolver
= (value: T | PromiseLike) => void
type Rejecter = (reason?: any) => void
interface PendingRequest {
+ requestId: string
resolve: Resolver
reject: Rejecter
}
@@ -15,14 +12,16 @@ interface AssistantState {
id: string
token: string
assistant: boolean
- flag: number
type: number
certificate: string
online: boolean
pageEmbedded?: boolean
history: boolean
hostOrigin: string
- requestPromiseMap: Map
+ autoDs?: boolean
+ requestPromiseMap: Map
+ peddingStatus: number //0: ready,1: pedding,2:finish
+ certificateTime: number
}
export const AssistantStore = defineStore('assistant', {
@@ -31,14 +30,16 @@ export const AssistantStore = defineStore('assistant', {
id: '',
token: '',
assistant: false,
- flag: 0,
type: 0,
certificate: '',
online: false,
pageEmbedded: false,
history: true,
hostOrigin: '',
- requestPromiseMap: new Map(),
+ autoDs: false,
+ requestPromiseMap: new Map(),
+ peddingStatus: 0,
+ certificateTime: 0,
}
},
getters: {
@@ -54,9 +55,6 @@ export const AssistantStore = defineStore('assistant', {
getAssistant(): boolean {
return this.assistant
},
- getFlag(): number {
- return this.flag
- },
getType(): number {
return this.type
},
@@ -75,47 +73,90 @@ export const AssistantStore = defineStore('assistant', {
getHostOrigin(): string {
return this.hostOrigin
},
+ getAutoDs(): boolean {
+ return !!this.autoDs
+ },
},
actions: {
- refreshCertificate() {
+ refreshCertificate(requestUrl?: string) {
+ /* if (+new Date() < this.certificateTime + 5000) {
+ return
+ } */
+
const timeout = 30000
+ let peddingList = this.requestPromiseMap.get(this.id) as PendingRequest[]
+ if (!peddingList) {
+ this.requestPromiseMap.set(this.id, [])
+ peddingList = this.requestPromiseMap.get(this.id) as PendingRequest[]
+ }
+
+ if (this.peddingStatus === 2) {
+ if (peddingList?.length) {
+ return
+ } else {
+ this.peddingStatus = 0
+ }
+ }
+
return new Promise((resolve, reject) => {
+ const currentRequestId = `${this.id}|${requestUrl}|${+new Date()}`
const timeoutId = setTimeout(() => {
- this.requestPromiseMap.delete(this.id)
- reject(new Error(`Request ${this.id} timed out after ${timeout}ms`))
+ console.error(`Request ${currentRequestId}[${requestUrl}] timed out after ${timeout}ms`)
+ resolve(null)
+ removeRequest(currentRequestId, peddingList)
+ if (timeoutId) {
+ clearTimeout(timeoutId)
+ }
+ // reject(new Error(`Request ${this.id} timed out after ${timeout}ms`))
}, timeout)
- this.requestPromiseMap.set(this.id, {
- resolve: (value: T) => {
+
+ const cleanupAndResolve = (value: any) => {
+ if (timeoutId) {
clearTimeout(timeoutId)
- this.requestPromiseMap.delete(this.id)
- resolve(value)
- },
- reject: (reason) => {
+ }
+ resolve(value)
+ removeRequest(currentRequestId, peddingList)
+ }
+
+ const cleanupAndReject = (reason: any) => {
+ if (timeoutId) {
clearTimeout(timeoutId)
- this.requestPromiseMap.delete(this.id)
- reject(reason)
- },
- })
- const readyData = {
- eventName: this.pageEmbedded ? 'sqlbot_embedded_event' : 'sqlbot_assistant_event',
- busi: 'ready',
- ready: true,
- messageId: this.id,
+ }
+ removeRequest(currentRequestId, peddingList)
+ reject(reason)
+ }
+
+ addRequest(currentRequestId, cleanupAndResolve, cleanupAndReject, peddingList)
+ if (this.peddingStatus !== 1) {
+ this.peddingStatus = 1
+ const readyData = {
+ eventName: this.pageEmbedded ? 'sqlbot_embedded_event' : 'sqlbot_assistant_event',
+ busi: 'ready',
+ ready: true,
+ messageId: this.id,
+ }
+ window.parent.postMessage(readyData, '*')
}
- window.parent.postMessage(readyData, '*')
})
},
resolveCertificate(data?: any) {
- const peddingRequest = this.requestPromiseMap.get(this.id)
- if (peddingRequest) {
- peddingRequest.resolve(data)
+ const peddingRequestList = this.requestPromiseMap.get(this.id)
+
+ if (peddingRequestList?.length) {
+ let len = peddingRequestList?.length
+ while (len--) {
+ const peddingRequest: PendingRequest = peddingRequestList[len]
+ peddingRequest.resolve(data)
+ }
}
+ this.peddingStatus = 2
},
setId(id: string) {
this.id = id
},
setCertificate(certificate: string) {
this.certificate = certificate
+ this.certificateTime = +new Date()
},
setType(type: number) {
this.type = type
@@ -126,14 +167,6 @@ export const AssistantStore = defineStore('assistant', {
setAssistant(assistant: boolean) {
this.assistant = assistant
},
- setFlag(flag: number) {
- if (wsCache.get(flagKey)) {
- this.flag = wsCache.get(flagKey)
- } else {
- this.flag = flag
- wsCache.set(flagKey, flag)
- }
- },
setPageEmbedded(embedded?: boolean) {
this.pageEmbedded = !!embedded
},
@@ -146,6 +179,9 @@ export const AssistantStore = defineStore('assistant', {
setHostOrigin(origin: string) {
this.hostOrigin = origin
},
+ setAutoDs(autoDs?: boolean) {
+ this.autoDs = !!autoDs
+ },
async setChat() {
if (!this.assistant) {
return null
@@ -155,12 +191,40 @@ export const AssistantStore = defineStore('assistant', {
return chat
},
clear() {
- wsCache.delete(flagKey)
this.$reset()
},
},
})
+const removeRequest = (requestId: string, peddingList: PendingRequest[]) => {
+ if (!peddingList) return
+ let len = peddingList.length
+ while (len--) {
+ const peddingRequest = peddingList[len]
+ if (peddingRequest?.requestId === requestId) {
+ peddingList.splice(len, 1)
+ }
+ }
+}
+
+const addRequest = (
+ requestId: string,
+ resolve: any,
+ reject: any,
+ peddingList: PendingRequest[]
+) => {
+ const currentPeddingRequest = {
+ requestId,
+ resolve: (value: any) => {
+ resolve(value)
+ },
+ reject: (reason: any) => {
+ reject(reason)
+ },
+ } as PendingRequest
+ peddingList?.push(currentPeddingRequest)
+}
+
export const useAssistantStore = () => {
return AssistantStore(store)
}
diff --git a/frontend/src/stores/chatConfig.ts b/frontend/src/stores/chatConfig.ts
new file mode 100644
index 000000000..478547826
--- /dev/null
+++ b/frontend/src/stores/chatConfig.ts
@@ -0,0 +1,80 @@
+import { defineStore } from 'pinia'
+import { store } from '@/stores/index.ts'
+import { request } from '@/utils/request.ts'
+import { formatArg } from '@/utils/utils.ts'
+
+interface ChatConfig {
+ sqlbot_name: string
+ expand_thinking_block: boolean
+ hide_thinking_block: boolean
+ limit_rows: boolean
+ show_sql: boolean
+ show_log: boolean
+}
+
+export const chatConfigStore = defineStore('chatConfigStore', {
+ state: (): ChatConfig => {
+ return {
+ sqlbot_name: 'SQLBot',
+ expand_thinking_block: false,
+ hide_thinking_block: false,
+ limit_rows: true,
+ show_sql: true,
+ show_log: true,
+ }
+ },
+ getters: {
+ getSQLBotName(): string {
+ return this.sqlbot_name
+ },
+ getExpandThinkingBlock(): boolean {
+ return this.expand_thinking_block
+ },
+ getHideThinkingBlock(): boolean {
+ return this.hide_thinking_block
+ },
+ getShowSQL(): boolean {
+ return this.show_sql
+ },
+ getShowLog(): boolean {
+ return this.show_log
+ },
+ getLimitRows(): boolean {
+ return this.limit_rows
+ },
+ },
+ actions: {
+ fetchGlobalConfig() {
+ request.get('/system/parameter/chat').then((res: any) => {
+ if (res) {
+ res.forEach((item: any) => {
+ if (item.pkey === 'chat.hide_thinking_block') {
+ this.hide_thinking_block = formatArg(item.pval)
+ }
+ if (item.pkey === 'chat.expand_thinking_block') {
+ this.expand_thinking_block = formatArg(item.pval)
+ }
+ if (item.pkey === 'chat.show_sql') {
+ this.show_sql = formatArg(item.pval)
+ }
+ if (item.pkey === 'chat.show_log') {
+ this.show_log = formatArg(item.pval)
+ }
+ if (item.pkey === 'chat.limit_rows') {
+ this.limit_rows = formatArg(item.pval)
+ }
+ if (item.pkey === 'chat.sqlbot_name') {
+ if (item.pval && item.pval.trim().length > 0) {
+ this.sqlbot_name = item.pval
+ }
+ }
+ })
+ }
+ })
+ },
+ },
+})
+
+export const useChatConfigStore = () => {
+ return chatConfigStore(store)
+}
diff --git a/frontend/src/stores/user.ts b/frontend/src/stores/user.ts
index 330431af5..b72e061d7 100644
--- a/frontend/src/stores/user.ts
+++ b/frontend/src/stores/user.ts
@@ -4,7 +4,7 @@ import { AuthApi } from '@/api/login'
import { useCache } from '@/utils/useCache'
import { i18n } from '@/i18n'
import { store } from './index'
-import { getQueryString } from '@/utils/utils'
+import { getCurrentRouter, getQueryString, getSQLBotAddr, isPlatform } from '@/utils/utils'
const { wsCache } = useCache()
@@ -98,8 +98,15 @@ export const UserStore = defineStore('user', {
window.open(res, '_self')
return res
}
- if (getQueryString('code') && getQueryString('state')?.includes('oauth2_state')) {
- const logout_url = location.origin + location.pathname + '#/login'
+ if (
+ (getQueryString('code') && getQueryString('state')?.includes('oauth2_state')) ||
+ isPlatform()
+ ) {
+ const currentPath = getCurrentRouter()
+ let logout_url = getSQLBotAddr() + '#/login'
+ if (currentPath) {
+ logout_url += `?redirect=${currentPath}`
+ }
window.location.href = logout_url
window.open(res, logout_url)
return logout_url
@@ -135,6 +142,7 @@ export const UserStore = defineStore('user', {
})
this.setLanguage(this.language)
+ this.platformInfo = wsCache.get('user.platformInfo')
},
setToken(token: string) {
wsCache.set('user.token', token)
@@ -169,6 +177,8 @@ export const UserStore = defineStore('user', {
language = 'zh-CN'
} else if (language === 'zh_CN') {
language = 'zh-CN'
+ } else if (language === 'zh_TW') {
+ language = 'zh-TW'
} else if (language === 'ko_KR') {
language = 'ko-KR'
}
diff --git a/frontend/src/style.less b/frontend/src/style.less
index 05e5b8b8c..0d1d9263b 100644
--- a/frontend/src/style.less
+++ b/frontend/src/style.less
@@ -1,3 +1,23 @@
+/* ==========================================
+ 老旧浏览器 Flex Gap 完美物理隔离补丁
+ ========================================== */
+
+/* 1. 当浏览器【不支持 flex gap】时,激活此特定命名空间 */
+:root[data-no-flex-gap='true'] .flex-gap-fallback {
+ display: flex;
+}
+
+/* 2. 仅对标记了该 class 的容器下的【相邻子元素】水平方向加间距 */
+:root[data-no-flex-gap='true'] .flex-gap-fallback > * + * {
+ margin-left: var(--gap-size, 16px); /* 默认 16px,可通过变量动态修改 */
+}
+
+/* 3. 如果需要处理垂直排列(flex-direction: column) */
+:root[data-no-flex-gap='true'] .flex-gap-fallback.flex-col > * + * {
+ margin-left: 0;
+ margin-top: var(--gap-size, 16px);
+}
+
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
@@ -25,12 +45,15 @@
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
--ed-border-radius-base: 6px !important;
--ed-border-color: #d9dcdf !important;
+}
+
+:root:root {
--ed-color-primary-light-7: #d2f1e9 !important;
--ed-border-color: #d9dcdf !important;
- --ed-border-radius-base: 6px !important;
--ed-disabled-border-color: #d9dcdf !important;
--ed-border-color-light: #dee0e3 !important;
--ed-border-color-lighter: #dee0e3 !important;
+ --ed-border-radius-base: 6px !important;
}
a {
@@ -85,7 +108,7 @@ body {
.list-item_primary {
height: 40px;
- border-radius: 4px;
+ border-radius: 6px;
padding: 8px 12px;
cursor: pointer;
display: flex;
@@ -227,7 +250,11 @@ strong {
.ed-select__popper {
padding: 0 4px !important;
.ed-select-dropdown__item {
- border-radius: 4px;
+ border-radius: 6px;
+ }
+
+ .ed-select-dropdown__list {
+ padding: 4px 0 !important;
}
}
@@ -380,16 +407,6 @@ strong {
line-height: 22px;
}
-.ed-pager li.number.number.number {
- &:hover {
- background-color: var(--ed-color-primary-60);
- }
-
- &:active {
- background-color: var(--ed-color-primary-80);
- }
-}
-
.ed-table.ed-table.ed-table {
--ed-table-border-color: #1f232926;
}
@@ -419,3 +436,45 @@ strong {
margin-top: -7px;
}
}
+
+.text-variables.text-variables,
+.string-variables.string-variables {
+ color: #3370ff;
+}
+.number-variables.number-variables,
+.int-variables.int-variables,
+.float-variables.float-variables {
+ color: #04b49c;
+}
+.datetime-variables.datetime-variables {
+ color: #3370ff;
+}
+
+.ed-tree-node__content {
+ border-radius: 6px;
+}
+
+.login-content {
+ /* 针对 Webkit 浏览器的自动填充样式重置 */
+ input:-webkit-autofill,
+ input:-webkit-autofill:hover,
+ input:-webkit-autofill:focus,
+ input:-webkit-autofill:active {
+ /* 1. 使用足够大的内阴影来覆盖背景色,把 #ffffff 替换成你输入框原本的背景色 */
+ -webkit-box-shadow: 0 0 0px 1000px #ffffff inset !important;
+
+ /* 2. 由于常规的 color 属性也会失效,需要用这个属性修改文字颜色 */
+ -webkit-text-fill-color: #333333 !important;
+
+ /* 3. 保留光标的正常颜色 */
+ caret-color: #333333;
+ }
+}
+
+.ed-popper.is-pure:has(.ed-select-dropdown) {
+ padding: 0 !important;
+
+ .ed-select-dropdown {
+ padding: 0 4px !important;
+ }
+}
diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts
index 84db2335a..b0700fd00 100644
--- a/frontend/src/utils/request.ts
+++ b/frontend/src/utils/request.ts
@@ -107,8 +107,12 @@ class HttpService {
!!(assistantStore.getType % 2) &&
assistantStore.getCertificate
) {
- if (config.method?.toLowerCase() === 'get' && /\/chat\/\d+$/.test(config.url || '')) {
- await assistantStore.refreshCertificate()
+ if (
+ /* (config.method?.toLowerCase() === 'get' && /\/chat\/\d+$/.test(config.url || '')) || */
+ /^\/chat/.test(config.url || '') ||
+ config.url?.includes('/system/assistant/ds')
+ ) {
+ await assistantStore.refreshCertificate(config.url || '')
}
config.headers['X-SQLBOT-ASSISTANT-CERTIFICATE'] = btoa(
encodeURIComponent(assistantStore.getCertificate)
@@ -317,7 +321,7 @@ class HttpService {
!!(assistantStore.getType % 2) &&
assistantStore.getCertificate
) {
- await assistantStore.refreshCertificate()
+ await assistantStore.refreshCertificate(url)
heads['X-SQLBOT-ASSISTANT-CERTIFICATE'] = btoa(
encodeURIComponent(assistantStore.getCertificate)
)
diff --git a/frontend/src/utils/useCache.ts b/frontend/src/utils/useCache.ts
index c7cda2afa..256da2142 100644
--- a/frontend/src/utils/useCache.ts
+++ b/frontend/src/utils/useCache.ts
@@ -1,13 +1,50 @@
import WebStorageCache from 'web-storage-cache'
-type CacheType = 'sessionStorage' | 'localStorage'
+type CacheType = 'localStorage' | 'sessionStorage'
+
+const getPathPrefix = () => {
+ const pathname = window.location.pathname
+ // eslint-disable-next-line no-useless-escape
+ const match = pathname.match(/^\/([^\/]+)/)
+ return match ? `${match[1]}_` : 'sqlbot_v1_'
+}
export const useCache = (type: CacheType = 'localStorage') => {
- const wsCache: WebStorageCache = new WebStorageCache({
- storage: type,
+ const originalCache = new WebStorageCache({ storage: type })
+ const prefix = getPathPrefix()
+
+ const methodsNeedKeyPrefix = new Set(['get', 'delete', 'touch', 'add', 'replace'])
+
+ const wrappedCache = new Proxy(originalCache, {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ get(target, prop, _receiver) {
+ const originalMethod = target[prop as keyof typeof target]
+
+ if (typeof originalMethod !== 'function') {
+ return originalMethod
+ }
+
+ if (methodsNeedKeyPrefix.has(prop as string)) {
+ return function (this: any, key: string, ...args: any[]) {
+ // 自动加上前缀
+ const scopedKey = `${prefix}${key}`
+ return (originalMethod as (...args: any[]) => any).apply(target, [scopedKey, ...args])
+ }
+ }
+
+ if (prop === 'set') {
+ return function (this: any, key: string, value: any, ...args: any[]) {
+ const scopedKey = `${prefix}${key}`
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
+ return (originalMethod as Function).apply(target, [scopedKey, value, ...args])
+ }
+ }
+
+ return originalMethod.bind(target)
+ },
})
return {
- wsCache,
+ wsCache: wrappedCache,
}
}
diff --git a/frontend/src/utils/utils.ts b/frontend/src/utils/utils.ts
index f31e25d66..01eb57390 100644
--- a/frontend/src/utils/utils.ts
+++ b/frontend/src/utils/utils.ts
@@ -50,7 +50,7 @@ export const getBrowserLocale = () => {
}
if (language.toLowerCase().startsWith('zh')) {
const temp = language.toLowerCase().replace('_', '-')
- return temp === 'zh' ? 'zh-CN' : temp === 'zh-cn' ? 'zh-CN' : 'tw'
+ return temp === 'zh' ? 'zh-CN' : temp === 'zh-cn' ? 'zh-CN' : 'zh-TW'
}
return language
}
@@ -103,6 +103,29 @@ export const isBtnShow = (val: string) => {
}
}
+export const toLoginPage = (fullPath: string) => {
+ if (!fullPath || fullPath === '/') {
+ return {
+ path: '/login',
+ }
+ }
+ return {
+ path: '/login',
+ query: { redirect: fullPath },
+ }
+}
+
+export const toLoginSuccess = (router: any) => {
+ const redirect = router?.currentRoute?.value?.query?.redirect
+ const redirectPath = Array.isArray(redirect) ? redirect[0] : redirect || '/chat'
+ router.push(redirectPath as string)
+}
+export const getCurrentRouter = () => {
+ const hash = location.hash
+ if (!hash) return null
+ return hash.replace('#/login?redirect=', '')
+}
+
export const setTitle = (title?: string) => {
document.title = title || 'SQLBot'
}
@@ -222,11 +245,21 @@ export const isLarkPlatform = () => {
return !!getQueryString('state') && !!getQueryString('code')
}
+export const isPlatform = () => {
+ const state = getQueryString('state')
+ const platformArray = ['wecom', 'dingtalk', 'lark']
+ return (
+ !!state &&
+ !!getQueryString('code') &&
+ platformArray.some((item: string) => state.includes(item))
+ )
+}
+
export const isPlatformClient = () => {
return !!getQueryString('client') || getQueryString('state')?.includes('client')
}
-export const checkPlatform = () => {
+/* export const checkPlatform = () => {
const flagArray = ['/casbi', 'oidcbi']
const pathname = window.location.pathname
if (
@@ -242,7 +275,7 @@ export const cleanPlatformFlag = () => {
const platformKey = 'out_auth_platform'
wsCache.delete(platformKey)
return false
-}
+} */
export function isTablet() {
const userAgent = navigator.userAgent
const tabletRegex = /iPad|Silk|Galaxy Tab|PlayBook|BlackBerry|(tablet|ipad|playbook)/i
@@ -263,3 +296,22 @@ export const getSQLBotAddr = (portEnd?: boolean) => {
}
return addr.substring(0, addr.length - 1)
}
+
+export const formatArg = (text: string) => {
+ if (!text) {
+ return false
+ }
+ const mappingArray = ['true', 'false', '1', '0']
+ const match = mappingArray.some((item: string) => {
+ return item === text.toLocaleLowerCase()
+ })
+ if (!match) {
+ return text
+ }
+ try {
+ return JSON.parse(text)
+ } catch (e: any) {
+ console.warn(e)
+ return text
+ }
+}
diff --git a/frontend/src/utils/xss.ts b/frontend/src/utils/xss.ts
new file mode 100644
index 000000000..a2504373f
--- /dev/null
+++ b/frontend/src/utils/xss.ts
@@ -0,0 +1,90 @@
+/**
+ * XSS Protection Utilities
+ * Provides functions to sanitize and escape user input to prevent XSS attacks
+ */
+
+/**
+ * Escape HTML entities to prevent XSS
+ * @param text - The text to escape
+ * @returns Escaped text safe for HTML insertion
+ */
+export function escapeHtml(text: string): string {
+ const div = document.createElement('div')
+ div.textContent = text
+ return div.innerHTML
+}
+
+/**
+ * Highlight keywords in text with XSS protection
+ * @param text - The original text
+ * @param keyword - The keyword to highlight
+ * @param highlightClass - CSS class for highlighted text
+ * @returns HTML string with highlighted keyword (XSS-safe)
+ */
+export function highlightKeyword(
+ text: string,
+ keyword: string,
+ highlightClass: string = 'highlight'
+): string {
+ if (!keyword) return escapeHtml(text)
+
+ const escapedText = escapeHtml(text)
+ const escapedKeyword = escapeHtml(keyword)
+
+ // Use case-insensitive replace
+ const regex = new RegExp(escapedKeyword, 'gi')
+ return escapedText.replace(regex, (match) => `${match}`)
+}
+
+/**
+ * Sanitize HTML content to remove potentially dangerous elements and attributes
+ * @param html - The HTML content to sanitize
+ * @returns Sanitized HTML
+ */
+export function sanitizeHtml(html: string): string {
+ // Create a temporary div to parse HTML
+ const temp = document.createElement('div')
+ temp.innerHTML = html
+
+ // List of allowed tags
+ const allowedTags = ['b', 'i', 'u', 'strong', 'em', 'span', 'p', 'br', 'a']
+
+ // List of allowed attributes
+ const allowedAttrs = ['class', 'href', 'title']
+
+ // Remove disallowed tags and attributes
+ const sanitize = (node: Node): void => {
+ if (node.nodeType === Node.ELEMENT_NODE) {
+ const element = node as Element
+
+ // Check if tag is allowed
+ if (!allowedTags.includes(element.tagName.toLowerCase())) {
+ // Replace with text content
+ const textNode = document.createTextNode(element.textContent || '')
+ element.parentNode?.replaceChild(textNode, element)
+ return
+ }
+
+ // Remove disallowed attributes
+ Array.from(element.attributes).forEach((attr) => {
+ if (!allowedAttrs.includes(attr.name.toLowerCase())) {
+ element.removeAttribute(attr.name)
+ }
+ })
+
+ // For links, ensure they don't use javascript: protocol
+ if (element.tagName.toLowerCase() === 'a') {
+ const href = element.getAttribute('href') || ''
+ if (href.toLowerCase().startsWith('javascript:')) {
+ element.removeAttribute('href')
+ }
+ }
+ }
+
+ // Recursively sanitize child nodes
+ Array.from(node.childNodes).forEach(sanitize)
+ }
+
+ sanitize(temp)
+ return temp.innerHTML
+}
diff --git a/frontend/src/views/chat/ChatCreator.vue b/frontend/src/views/chat/ChatCreator.vue
index 93c57f790..aab5ae298 100644
--- a/frontend/src/views/chat/ChatCreator.vue
+++ b/frontend/src/views/chat/ChatCreator.vue
@@ -1,5 +1,5 @@
-
-
+
+
-
-
+
+
@@ -46,6 +46,7 @@ const appearanceStore = useAppearanceStoreWithOut()
.chat-row-container {
display: flex;
flex-direction: column;
+ --gap-size: 8px;
gap: 8px;
width: 100%;
max-width: 800px;
@@ -54,6 +55,7 @@ const appearanceStore = useAppearanceStoreWithOut()
display: flex;
flex-direction: row;
align-items: flex-start;
+ --gap-size: 8px;
gap: 8px;
padding: 20px 0 0;
diff --git a/frontend/src/views/chat/ChatTokenTime.vue b/frontend/src/views/chat/ChatTokenTime.vue
new file mode 100644
index 000000000..8fdee2cc8
--- /dev/null
+++ b/frontend/src/views/chat/ChatTokenTime.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/chat/ExecutionDetails.vue b/frontend/src/views/chat/ExecutionDetails.vue
new file mode 100644
index 000000000..1b88888d3
--- /dev/null
+++ b/frontend/src/views/chat/ExecutionDetails.vue
@@ -0,0 +1,239 @@
+
+
+
+
+ {{ t('parameter.overview') }}
+
+
+
+
+
+
{{ t('parameter.tokens_required') }}
+
{{ logHistory.total_tokens }}
+
+
+
+
+
+
{{ t('parameter.time_execution') }}
+
{{ logHistory.duration }}s
+
+
+ {{ t('parameter.execution_details') }}
+
+
+
+
+
+
diff --git a/frontend/src/views/chat/QuickQuestion.vue b/frontend/src/views/chat/QuickQuestion.vue
index e5e1296d0..660b5a468 100644
--- a/frontend/src/views/chat/QuickQuestion.vue
+++ b/frontend/src/views/chat/QuickQuestion.vue
@@ -15,7 +15,7 @@ const getRecommendQuestions = () => {
const questions = '[]'
const retrieveQuestions = () => {
- getRecommendQuestions()
+ recommendQuestionRef.value.getRecommendQuestions(10, true)
recentQuestionRef.value.getRecentQuestions()
}
const quickAsk = (question: string) => {
@@ -136,7 +136,7 @@ const props = withDefaults(
.quick_question_popover {
padding: 4px !important;
.quick_question_title {
- width: 40px;
+ min-width: 40px;
height: 24px;
border-radius: 6px;
opacity: 1;
@@ -149,8 +149,8 @@ const props = withDefaults(
cursor: pointer;
margin-left: 8px;
&:hover {
- color: rgba(24, 158, 122, 0.5);
- background: rgba(28, 186, 144, 0.1);
+ color: var(--ed-color-primary-15-d, #189e7a);
+ background: #1f23291a;
}
}
.title-active {
@@ -185,6 +185,9 @@ const props = withDefaults(
top: 30px;
right: 4px;
z-index: 1;
+ &:hover {
+ background-color: #1f23291a !important;
+ }
}
.tool-btn {
diff --git a/frontend/src/views/chat/RecentQuestion.vue b/frontend/src/views/chat/RecentQuestion.vue
index 007cddfa0..5010e7408 100644
--- a/frontend/src/views/chat/RecentQuestion.vue
+++ b/frontend/src/views/chat/RecentQuestion.vue
@@ -38,7 +38,7 @@ defineExpose({