- 设置页新增「日志分享」与「已分享日志」区块(默认实例设置内) - 补齐 helpers/logs.js 的 logshare 调用(上传/分析/删除/记录) - 日志分享设置支持分享服务、AI 来源、自动上传、多文件、不留存、进度等选项 - 已分享日志支持查看列表与删除 - 补 en-US / zh-CN 文案 - i18n 白名单补充 LogShare AI
This commit is contained in:
1 parent
8d61826c5f
commit
853147b04d
7 files changed
+584
-2
No files matched your search
@@ -6,6 +6,8 @@ import { ref, watch } from 'vue'
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
|
||||
import CrashAnalysisAISettings from './CrashAnalysisAISettings.vue'
|
||||
import LogShareSettings from './LogShareSettings.vue'
|
||||
import SharedLogsSettings from './SharedLogsSettings.vue'
|
||||
import SettingsRow from './SettingsRow.vue'
|
||||
import SettingsSection from './SettingsSection.vue'
|
||||
|
||||
@@ -391,5 +393,7 @@ watch(
|
||||
</SettingsSection>
|
||||
|
||||
<CrashAnalysisAISettings />
|
||||
<LogShareSettings />
|
||||
<SharedLogsSettings />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,354 @@
|
||||
<script setup lang="ts">
|
||||
import { Combobox, defineMessages, injectNotificationManager, Toggle, useVIntl } from '@modrinth/ui'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
import { type AIState, getAIState, sharedAIState } from '@/helpers/ai'
|
||||
import {
|
||||
get_crash_analysis_ai_settings,
|
||||
get_log_share_settings,
|
||||
update_crash_analysis_ai_settings,
|
||||
update_log_share_settings,
|
||||
} from '@/helpers/logs'
|
||||
|
||||
import SettingsRow from './SettingsRow.vue'
|
||||
import SettingsSection from './SettingsSection.vue'
|
||||
|
||||
type CrashAISettings = {
|
||||
enabled: boolean
|
||||
provider_id: string
|
||||
model_id: string
|
||||
ai_source: 'logshare' | 'custom'
|
||||
}
|
||||
|
||||
type LogShareSettings = {
|
||||
share_provider: 'logshare' | 'mclogs'
|
||||
ai_source: string
|
||||
auto_upload: boolean
|
||||
multi_file: boolean
|
||||
no_storage: boolean
|
||||
show_progress: boolean
|
||||
}
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { handleError } = injectNotificationManager()
|
||||
const settings = ref<LogShareSettings>({
|
||||
share_provider: 'logshare',
|
||||
ai_source: 'logshare',
|
||||
auto_upload: true,
|
||||
multi_file: true,
|
||||
no_storage: false,
|
||||
show_progress: true,
|
||||
})
|
||||
const aiSettings = ref<CrashAISettings>({
|
||||
enabled: false,
|
||||
provider_id: '',
|
||||
model_id: '',
|
||||
ai_source: 'logshare',
|
||||
})
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'app.log-share.settings.title', defaultMessage: 'Log sharing & AI analysis' },
|
||||
description: {
|
||||
id: 'app.log-share.settings.description',
|
||||
defaultMessage:
|
||||
'Share crash diagnostics with LogShare for a structured summary and optional AI analysis. LogShare is preferred; mclo.gs is used as a fallback.',
|
||||
},
|
||||
shareProvider: {
|
||||
id: 'app.log-share.settings.share-provider',
|
||||
defaultMessage: 'Log sharing service',
|
||||
},
|
||||
shareProviderDescription: {
|
||||
id: 'app.log-share.settings.share-provider-description',
|
||||
defaultMessage: 'Where the diagnostic link is uploaded when you share it.',
|
||||
},
|
||||
aiSource: { id: 'app.log-share.settings.ai-source', defaultMessage: 'AI analysis source' },
|
||||
aiSourceLogShare: {
|
||||
id: 'app.log-share.settings.ai-source-logshare',
|
||||
defaultMessage: 'LogShare AI',
|
||||
},
|
||||
aiSourceCustom: {
|
||||
id: 'app.log-share.settings.ai-source-custom',
|
||||
defaultMessage: 'Custom AI provider',
|
||||
},
|
||||
aiSourceDescription: {
|
||||
id: 'app.log-share.settings.ai-source-description',
|
||||
defaultMessage:
|
||||
'Both sources send a sanitized and shortened crash context. AI output may be inaccurate.',
|
||||
},
|
||||
enabled: {
|
||||
id: 'app.crash-analysis.ai.settings.enabled',
|
||||
defaultMessage: 'Enable AI explanations',
|
||||
},
|
||||
enabledDescription: {
|
||||
id: 'app.crash-analysis.ai.settings.enabled-description',
|
||||
defaultMessage: 'Local rule-based crash analysis remains available without AI.',
|
||||
},
|
||||
provider: { id: 'app.crash-analysis.ai.settings.provider', defaultMessage: 'AI provider' },
|
||||
model: { id: 'app.crash-analysis.ai.settings.model', defaultMessage: 'Text model' },
|
||||
noProviders: {
|
||||
id: 'app.crash-analysis.ai.settings.no-providers',
|
||||
defaultMessage: 'Enable a provider and a text model above before using AI crash explanations.',
|
||||
},
|
||||
autoUpload: {
|
||||
id: 'app.log-share.settings.auto-upload',
|
||||
defaultMessage: 'Upload to get a structured summary automatically',
|
||||
},
|
||||
autoUploadDescription: {
|
||||
id: 'app.log-share.settings.auto-upload-description',
|
||||
defaultMessage: 'Only effective in LogShare AI mode.',
|
||||
},
|
||||
multiFile: { id: 'app.log-share.settings.multi-file', defaultMessage: 'Multi-file upload' },
|
||||
multiFileDescription: {
|
||||
id: 'app.log-share.settings.multi-file-description',
|
||||
defaultMessage: 'Upload latest.log, crash reports, hs_err and launcher logs together.',
|
||||
},
|
||||
noStorage: {
|
||||
id: 'app.log-share.settings.no-storage',
|
||||
defaultMessage: 'Analyze without storing logs',
|
||||
},
|
||||
noStorageDescription: {
|
||||
id: 'app.log-share.settings.no-storage-description',
|
||||
defaultMessage:
|
||||
'Submit content directly for analysis and skip the stored upload. Disables the automatic summary.',
|
||||
},
|
||||
showProgress: {
|
||||
id: 'app.log-share.settings.show-progress',
|
||||
defaultMessage: 'Show AI analysis progress',
|
||||
},
|
||||
showProgressDescription: {
|
||||
id: 'app.log-share.settings.show-progress-description',
|
||||
defaultMessage: 'Display thinking, tool and limit status while LogShare AI is working.',
|
||||
},
|
||||
})
|
||||
|
||||
const emptyState: AIState = { settings: { enabled: false }, catalog_source: '', providers: [] }
|
||||
const aiState = computed(() => sharedAIState.value ?? emptyState)
|
||||
const providers = computed(() =>
|
||||
aiState.value.providers.filter(
|
||||
(provider) => provider.enabled && provider.models.some((model) => model.enabled),
|
||||
),
|
||||
)
|
||||
const providerOptions = computed(() =>
|
||||
providers.value.map((provider) => ({
|
||||
value: provider.provider_id,
|
||||
label: provider.custom_name || provider.provider_id,
|
||||
})),
|
||||
)
|
||||
const modelOptions = computed(
|
||||
() =>
|
||||
providers.value
|
||||
.find((provider) => provider.provider_id === aiSettings.value.provider_id)
|
||||
?.models.filter((model) => model.enabled)
|
||||
.map((model) => ({ value: model.id, label: model.name || model.id })) ?? [],
|
||||
)
|
||||
|
||||
const isCustom = computed(() => aiSettings.value.ai_source === 'custom')
|
||||
const noStorageEnabled = computed(() => aiSettings.value.ai_source === 'logshare')
|
||||
|
||||
const shareProviderOptions = [
|
||||
{ value: 'logshare', label: 'LogShare' },
|
||||
{ value: 'mclogs', label: 'mclo.gs' },
|
||||
]
|
||||
const aiSourceOptions = [
|
||||
{ value: 'logshare', label: formatMessage(messages.aiSourceLogShare) },
|
||||
{ value: 'custom', label: formatMessage(messages.aiSourceCustom) },
|
||||
]
|
||||
|
||||
async function saveShare(next: LogShareSettings): Promise<void> {
|
||||
saving.value = true
|
||||
try {
|
||||
await update_log_share_settings(next)
|
||||
settings.value = next
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAI(next: CrashAISettings): Promise<void> {
|
||||
saving.value = true
|
||||
try {
|
||||
await update_crash_analysis_ai_settings(next)
|
||||
aiSettings.value = next
|
||||
settings.value = { ...settings.value, ai_source: next.ai_source }
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function updateShare(patch: Partial<LogShareSettings>): void {
|
||||
void saveShare({ ...settings.value, ...patch })
|
||||
}
|
||||
|
||||
function selectShareProvider(value: unknown): void {
|
||||
const provider = value === 'mclogs' ? 'mclogs' : 'logshare'
|
||||
updateShare({ share_provider: provider, ai_source: aiSettings.value.ai_source })
|
||||
}
|
||||
|
||||
function selectAiSource(value: unknown): void {
|
||||
const source = value === 'custom' ? 'custom' : 'logshare'
|
||||
void saveAI({ ...aiSettings.value, ai_source: source })
|
||||
}
|
||||
|
||||
function updateEnabled(enabled: boolean): void {
|
||||
void saveAI({ ...aiSettings.value, enabled })
|
||||
}
|
||||
|
||||
function updateProvider(providerId: string): void {
|
||||
const provider = providers.value.find((item) => item.provider_id === providerId)
|
||||
const modelId = provider?.models.find((model) => model.enabled)?.id ?? ''
|
||||
void saveAI({ ...aiSettings.value, provider_id: providerId, model_id: modelId })
|
||||
}
|
||||
|
||||
function updateModel(modelId: string): void {
|
||||
void saveAI({ ...aiSettings.value, model_id: modelId })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const [nextShare, nextAI] = await Promise.all([
|
||||
get_log_share_settings(),
|
||||
get_crash_analysis_ai_settings(),
|
||||
getAIState(),
|
||||
])
|
||||
settings.value = { ...nextShare, ai_source: nextAI.ai_source }
|
||||
aiSettings.value = nextAI
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SettingsSection
|
||||
v-if="!loading"
|
||||
id="settings-target-log-share"
|
||||
:title="formatMessage(messages.title)"
|
||||
:description="formatMessage(messages.description)"
|
||||
>
|
||||
<SettingsRow>
|
||||
<template #label>{{ formatMessage(messages.shareProvider) }}</template>
|
||||
<template #description>{{ formatMessage(messages.shareProviderDescription) }}</template>
|
||||
<template #control>
|
||||
<Combobox
|
||||
id="log-share-provider"
|
||||
:model-value="settings.share_provider"
|
||||
:options="shareProviderOptions"
|
||||
:disabled="saving"
|
||||
@update:model-value="selectShareProvider($event)"
|
||||
/>
|
||||
</template>
|
||||
</SettingsRow>
|
||||
<SettingsRow>
|
||||
<template #label>{{ formatMessage(messages.aiSource) }}</template>
|
||||
<template #description>{{ formatMessage(messages.aiSourceDescription) }}</template>
|
||||
<template #control>
|
||||
<Combobox
|
||||
id="log-share-ai-source"
|
||||
:model-value="aiSettings.ai_source"
|
||||
:options="aiSourceOptions"
|
||||
:disabled="saving"
|
||||
@update:model-value="selectAiSource($event)"
|
||||
/>
|
||||
</template>
|
||||
</SettingsRow>
|
||||
<template v-if="isCustom">
|
||||
<SettingsRow>
|
||||
<template #label>{{ formatMessage(messages.enabled) }}</template>
|
||||
<template #description>{{ formatMessage(messages.enabledDescription) }}</template>
|
||||
<template #control>
|
||||
<Toggle
|
||||
id="crash-analysis-ai-enabled"
|
||||
:model-value="aiSettings.enabled"
|
||||
:disabled="saving || !aiState.settings.enabled || !providers.length"
|
||||
@update:model-value="updateEnabled(!!$event)"
|
||||
/>
|
||||
</template>
|
||||
</SettingsRow>
|
||||
<template v-if="providers.length">
|
||||
<SettingsRow>
|
||||
<template #label>{{ formatMessage(messages.provider) }}</template>
|
||||
<template #control>
|
||||
<Combobox
|
||||
id="crash-analysis-ai-provider"
|
||||
:model-value="aiSettings.provider_id"
|
||||
:options="providerOptions"
|
||||
:disabled="saving"
|
||||
@update:model-value="updateProvider(String($event))"
|
||||
/>
|
||||
</template>
|
||||
</SettingsRow>
|
||||
<SettingsRow>
|
||||
<template #label>{{ formatMessage(messages.model) }}</template>
|
||||
<template #control>
|
||||
<Combobox
|
||||
id="crash-analysis-ai-model"
|
||||
:model-value="aiSettings.model_id"
|
||||
:options="modelOptions"
|
||||
:disabled="saving || !aiSettings.provider_id"
|
||||
@update:model-value="updateModel(String($event))"
|
||||
/>
|
||||
</template>
|
||||
</SettingsRow>
|
||||
</template>
|
||||
<p v-else class="m-0 p-4 text-sm text-[var(--color-text-tertiary)]">
|
||||
{{ formatMessage(messages.noProviders) }}
|
||||
</p>
|
||||
</template>
|
||||
<SettingsRow>
|
||||
<template #label>{{ formatMessage(messages.autoUpload) }}</template>
|
||||
<template #description>{{ formatMessage(messages.autoUploadDescription) }}</template>
|
||||
<template #control>
|
||||
<Toggle
|
||||
id="log-share-auto-upload"
|
||||
:model-value="settings.auto_upload"
|
||||
:disabled="saving || !noStorageEnabled || settings.no_storage"
|
||||
@update:model-value="updateShare({ auto_upload: !!$event })"
|
||||
/>
|
||||
</template>
|
||||
</SettingsRow>
|
||||
<SettingsRow>
|
||||
<template #label>{{ formatMessage(messages.multiFile) }}</template>
|
||||
<template #description>{{ formatMessage(messages.multiFileDescription) }}</template>
|
||||
<template #control>
|
||||
<Toggle
|
||||
id="log-share-multi-file"
|
||||
:model-value="settings.multi_file"
|
||||
:disabled="saving"
|
||||
@update:model-value="updateShare({ multi_file: !!$event })"
|
||||
/>
|
||||
</template>
|
||||
</SettingsRow>
|
||||
<SettingsRow>
|
||||
<template #label>{{ formatMessage(messages.noStorage) }}</template>
|
||||
<template #description>{{ formatMessage(messages.noStorageDescription) }}</template>
|
||||
<template #control>
|
||||
<Toggle
|
||||
id="log-share-no-storage"
|
||||
:model-value="settings.no_storage"
|
||||
:disabled="saving || !noStorageEnabled"
|
||||
@update:model-value="updateShare({ no_storage: !!$event })"
|
||||
/>
|
||||
</template>
|
||||
</SettingsRow>
|
||||
<SettingsRow>
|
||||
<template #label>{{ formatMessage(messages.showProgress) }}</template>
|
||||
<template #description>{{ formatMessage(messages.showProgressDescription) }}</template>
|
||||
<template #control>
|
||||
<Toggle
|
||||
id="log-share-show-progress"
|
||||
:model-value="settings.show_progress"
|
||||
:disabled="saving"
|
||||
@update:model-value="updateShare({ show_progress: !!$event })"
|
||||
/>
|
||||
</template>
|
||||
</SettingsRow>
|
||||
</SettingsSection>
|
||||
</template>
|
||||
@@ -0,0 +1,129 @@
|
||||
<script setup lang="ts">
|
||||
import { Button, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
import { delete_shared_log, list_shared_logs } from '@/helpers/logs'
|
||||
|
||||
import SettingsRow from './SettingsRow.vue'
|
||||
import SettingsSection from './SettingsSection.vue'
|
||||
|
||||
type SharedLog = {
|
||||
id: string
|
||||
url: string
|
||||
raw: string
|
||||
token: string
|
||||
provider: string
|
||||
instance_id: string | null
|
||||
instance_name: string | null
|
||||
truncated: boolean
|
||||
created_at: number
|
||||
}
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { addNotification, handleError } = injectNotificationManager()
|
||||
const logs = ref<SharedLog[]>([])
|
||||
const loading = ref(true)
|
||||
const deletingId = ref<string | null>(null)
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'app.log-share.shared-logs.title', defaultMessage: 'Shared logs' },
|
||||
description: {
|
||||
id: 'app.log-share.shared-logs.description',
|
||||
defaultMessage:
|
||||
'Every diagnostic you have shared is listed here. Delete an entry to remove it from your history and request its removal from the sharing service.',
|
||||
},
|
||||
empty: { id: 'app.log-share.shared-logs.empty', defaultMessage: 'No shared logs yet.' },
|
||||
delete: { id: 'app.log-share.shared-logs.delete', defaultMessage: 'Delete' },
|
||||
deleting: { id: 'app.log-share.shared-logs.deleting', defaultMessage: 'Deleting…' },
|
||||
deleted: {
|
||||
id: 'app.log-share.shared-logs.deleted',
|
||||
defaultMessage: 'Shared log removed.',
|
||||
},
|
||||
deleteFailed: {
|
||||
id: 'app.log-share.shared-logs.delete-failed',
|
||||
defaultMessage: 'Failed to delete the shared log.',
|
||||
},
|
||||
truncated: { id: 'app.log-share.shared-logs.truncated', defaultMessage: 'Truncated' },
|
||||
})
|
||||
|
||||
async function refresh(): Promise<void> {
|
||||
try {
|
||||
logs.value = await list_shared_logs()
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(log: SharedLog): Promise<void> {
|
||||
if (deletingId.value) return
|
||||
deletingId.value = log.id
|
||||
try {
|
||||
await delete_shared_log(log.id, log.token)
|
||||
logs.value = logs.value.filter((item) => item.id !== log.id)
|
||||
addNotification({ title: formatMessage(messages.deleted), type: 'success' })
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
addNotification({ title: formatMessage(messages.deleteFailed), type: 'error' })
|
||||
} finally {
|
||||
deletingId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(timestamp: number): string {
|
||||
return new Date(timestamp * 1000).toLocaleString()
|
||||
}
|
||||
|
||||
onMounted(refresh)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SettingsSection
|
||||
id="settings-target-shared-logs"
|
||||
:title="formatMessage(messages.title)"
|
||||
:description="formatMessage(messages.description)"
|
||||
>
|
||||
<SettingsRow stacked>
|
||||
<template #control>
|
||||
<div class="flex w-full min-w-0 flex-col gap-2">
|
||||
<p v-if="loading" class="m-0 p-2 text-sm text-[var(--color-text-tertiary)]">…</p>
|
||||
<p v-else-if="!logs.length" class="m-0 p-2 text-sm text-[var(--color-text-tertiary)]">
|
||||
{{ formatMessage(messages.empty) }}
|
||||
</p>
|
||||
<ul v-else class="m-0 flex max-h-72 list-none flex-col gap-1 overflow-y-auto p-2">
|
||||
<li
|
||||
v-for="log in logs"
|
||||
:key="log.id"
|
||||
class="flex items-center gap-3 rounded-lg bg-surface-2 px-3 py-2"
|
||||
>
|
||||
<a
|
||||
:href="log.url || `https://logshare.cn/${log.id}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="min-w-0 flex-1 truncate text-[var(--color-text-default)] underline"
|
||||
>
|
||||
{{ log.url || log.id }}
|
||||
</a>
|
||||
<span class="shrink-0 text-xs text-[var(--color-text-tertiary)]">
|
||||
{{ log.provider }}
|
||||
<template v-if="log.truncated">
|
||||
· {{ formatMessage(messages.truncated) }}
|
||||
</template>
|
||||
<template v-if="log.instance_name"> · {{ log.instance_name }} </template>
|
||||
· {{ formatDate(log.created_at) }}
|
||||
</span>
|
||||
<Button type="outlined" :disabled="deletingId === log.id" @click="remove(log)"
|
||||
>{{
|
||||
deletingId === log.id
|
||||
? formatMessage(messages.deleting)
|
||||
: formatMessage(messages.delete)
|
||||
}}
|
||||
</Button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</SettingsRow>
|
||||
</SettingsSection>
|
||||
</template>
|
||||
@@ -95,6 +95,50 @@ export async function undo_added_mod(instanceId, filename, expectedHash) {
|
||||
return await invoke('plugin:logs|logs_undo_added_mod', { instanceId, filename, expectedHash })
|
||||
}
|
||||
|
||||
export async function get_log_share_settings() {
|
||||
return await invoke('plugin:logs|logs_get_log_share_settings')
|
||||
}
|
||||
|
||||
export async function update_log_share_settings(settings) {
|
||||
return await invoke('plugin:logs|logs_update_log_share_settings', { settings })
|
||||
}
|
||||
|
||||
export async function logshare_upload_crash(instanceId) {
|
||||
return await invoke('plugin:logs|logs_logshare_upload_crash', { instanceId })
|
||||
}
|
||||
|
||||
export async function logshare_get_insights(id) {
|
||||
return await invoke('plugin:logs|logs_logshare_get_insights', { id })
|
||||
}
|
||||
|
||||
export async function logshare_analyse_crash_direct(instanceId) {
|
||||
return await invoke('plugin:logs|logs_logshare_analyse_crash_direct', { instanceId })
|
||||
}
|
||||
|
||||
export async function logshare_ai_analyze_stored(instanceId, id) {
|
||||
return await invoke('plugin:logs|logs_logshare_ai_analyze_stored', { instanceId, id })
|
||||
}
|
||||
|
||||
export async function logshare_ai_analyze_direct(instanceId) {
|
||||
return await invoke('plugin:logs|logs_logshare_ai_analyze_direct', { instanceId })
|
||||
}
|
||||
|
||||
export async function logshare_delete(id, token) {
|
||||
return await invoke('plugin:logs|logs_logshare_delete', { id, token })
|
||||
}
|
||||
|
||||
export async function list_shared_logs() {
|
||||
return await invoke('plugin:logs|logs_list_shared_logs')
|
||||
}
|
||||
|
||||
export async function record_shared_log(log) {
|
||||
return await invoke('plugin:logs|logs_record_shared_log', { log })
|
||||
}
|
||||
|
||||
export async function delete_shared_log(id, token) {
|
||||
return await invoke('plugin:logs|logs_delete_shared_log', { id, token })
|
||||
}
|
||||
|
||||
/// Export the censored files and local analysis from an instance's latest run.
|
||||
export async function export_crash_context(instanceId, instanceName) {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
||||
|
||||
@@ -11600,4 +11600,29 @@
|
||||
"app.library.context-menu.unpin-instance": {"message": "Unpin instance"},
|
||||
"app.library.group.new": {"message": "New group"},
|
||||
"app.library.selection.new-group": {"message": "New group"},
|
||||
"app.library.selection.remove-from-group": {"message": "Remove from group"}}
|
||||
"app.library.selection.remove-from-group": {"message": "Remove from group"},
|
||||
"app.log-share.settings.title": {"message": "Log sharing & AI analysis"},
|
||||
"app.log-share.settings.description": {"message": "Share crash diagnostics with LogShare for a structured summary and optional AI analysis. LogShare is preferred; mclo.gs is used as a fallback."},
|
||||
"app.log-share.settings.share-provider": {"message": "Log sharing service"},
|
||||
"app.log-share.settings.share-provider-description": {"message": "Where the diagnostic link is uploaded when you share it."},
|
||||
"app.log-share.settings.ai-source": {"message": "AI analysis source"},
|
||||
"app.log-share.settings.ai-source-logshare": {"message": "LogShare AI"},
|
||||
"app.log-share.settings.ai-source-custom": {"message": "Custom AI provider"},
|
||||
"app.log-share.settings.ai-source-description": {"message": "Both sources send a sanitized and shortened crash context. AI output may be inaccurate."},
|
||||
"app.log-share.settings.auto-upload": {"message": "Upload to get a structured summary automatically"},
|
||||
"app.log-share.settings.auto-upload-description": {"message": "Only effective in LogShare AI mode."},
|
||||
"app.log-share.settings.multi-file": {"message": "Multi-file upload"},
|
||||
"app.log-share.settings.multi-file-description": {"message": "Upload latest.log, crash reports, hs_err and launcher logs together."},
|
||||
"app.log-share.settings.no-storage": {"message": "Analyze without storing logs"},
|
||||
"app.log-share.settings.no-storage-description": {"message": "Submit content directly for analysis and skip the stored upload. Disables the automatic summary."},
|
||||
"app.log-share.settings.show-progress": {"message": "Show AI analysis progress"},
|
||||
"app.log-share.settings.show-progress-description": {"message": "Display thinking, tool and limit status while LogShare AI is working."},
|
||||
"app.log-share.shared-logs.title": {"message": "Shared logs"},
|
||||
"app.log-share.shared-logs.description": {"message": "Every diagnostic you have shared is listed here. Delete an entry to remove it from your history and request its removal from the sharing service."},
|
||||
"app.log-share.shared-logs.empty": {"message": "No shared logs yet."},
|
||||
"app.log-share.shared-logs.delete": {"message": "Delete"},
|
||||
"app.log-share.shared-logs.deleting": {"message": "Deleting…"},
|
||||
"app.log-share.shared-logs.deleted": {"message": "Shared log removed."},
|
||||
"app.log-share.shared-logs.delete-failed": {"message": "Failed to delete the shared log."},
|
||||
"app.log-share.shared-logs.truncated": {"message": "Truncated"}
|
||||
}
|
||||
@@ -11431,4 +11431,29 @@
|
||||
"app.library.context-menu.unpin-instance": {"message": "取消置顶"},
|
||||
"app.library.group.new": {"message": "新建分组"},
|
||||
"app.library.selection.new-group": {"message": "新建分组"},
|
||||
"app.library.selection.remove-from-group": {"message": "移出分组"}}
|
||||
"app.library.selection.remove-from-group": {"message": "移出分组"},
|
||||
"app.log-share.settings.title": {"message": "日志分享与 AI 分析"},
|
||||
"app.log-share.settings.description": {"message": "将崩溃诊断分享到 LogShare,获取结构化摘要与可选的 AI 分析。优先使用 LogShare,不可用时回退到 mclo.gs。"},
|
||||
"app.log-share.settings.share-provider": {"message": "日志分享服务"},
|
||||
"app.log-share.settings.share-provider-description": {"message": "分享诊断链接时上传到的目标服务。"},
|
||||
"app.log-share.settings.ai-source": {"message": "AI 分析来源"},
|
||||
"app.log-share.settings.ai-source-logshare": {"message": "LogShare AI"},
|
||||
"app.log-share.settings.ai-source-custom": {"message": "自定义 AI 服务商"},
|
||||
"app.log-share.settings.ai-source-description": {"message": "LogShare AI 在云端分析;自定义则使用下方配置的服务商。"},
|
||||
"app.log-share.settings.auto-upload": {"message": "自动上传以获取结构化摘要"},
|
||||
"app.log-share.settings.auto-upload-description": {"message": "仅在 LogShare 模式下生效。"},
|
||||
"app.log-share.settings.multi-file": {"message": "多文件上传"},
|
||||
"app.log-share.settings.multi-file-description": {"message": "同时上传 latest.log、崩溃报告、hs_err 以及启动器日志。"},
|
||||
"app.log-share.settings.no-storage": {"message": "不落盘直接分析"},
|
||||
"app.log-share.settings.no-storage-description": {"message": "直接提交内容进行分析并跳过存储上传。会关闭自动摘要。"},
|
||||
"app.log-share.settings.show-progress": {"message": "显示 AI 分析进度"},
|
||||
"app.log-share.settings.show-progress-description": {"message": "展示 LogAgent 运行期间的思考、工具及上限状态。"},
|
||||
"app.log-share.shared-logs.title": {"message": "已分享日志"},
|
||||
"app.log-share.shared-logs.description": {"message": "你分享过的所有诊断都记录在这里。删除条目会将其从记录中移除,并向分享服务请求删除。"},
|
||||
"app.log-share.shared-logs.empty": {"message": "暂无已分享的日志。"},
|
||||
"app.log-share.shared-logs.delete": {"message": "删除"},
|
||||
"app.log-share.shared-logs.deleting": {"message": "删除中…"},
|
||||
"app.log-share.shared-logs.deleted": {"message": "已删除共享日志。"},
|
||||
"app.log-share.shared-logs.delete-failed": {"message": "删除已分享日志失败。"},
|
||||
"app.log-share.shared-logs.truncated": {"message": "已截断"}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ const allowedUntranslatedMessages = new Set([
|
||||
'Cleanroom',
|
||||
'LiteLoader',
|
||||
'LogShare.CN',
|
||||
'LogShare AI',
|
||||
'NeoForge',
|
||||
'OptiFine',
|
||||
'Quilt',
|
||||
|
||||
Reference in new issue
Block a user