diff --git a/android-webview-app/app/src/main/java/com/cyjai/agent/MainActivity.kt b/android-webview-app/app/src/main/java/com/cyjai/agent/MainActivity.kt index 4b41066..198ee97 100644 --- a/android-webview-app/app/src/main/java/com/cyjai/agent/MainActivity.kt +++ b/android-webview-app/app/src/main/java/com/cyjai/agent/MainActivity.kt @@ -10,12 +10,12 @@ import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Environment +import android.provider.DocumentsContract import android.util.Log import android.view.ViewGroup import android.view.View import android.content.Context import android.webkit.CookieManager -import android.webkit.DownloadListener import android.webkit.JavascriptInterface import android.webkit.PermissionRequest import android.webkit.ValueCallback @@ -185,10 +185,6 @@ class MainActivity : AppCompatActivity() { } } - webView.setDownloadListener(DownloadListener { url, _, contentDisposition, _, _ -> - startSystemDownload(url, suggestFileName(contentDisposition, url)) - }) - webView.webChromeClient = object : WebChromeClient() { override fun onPermissionRequest(request: PermissionRequest) { // 按需放行媒体权限(例如文件上传触发的媒体访问) @@ -203,13 +199,12 @@ class MainActivity : AppCompatActivity() { this@MainActivity.filePathCallback?.onReceiveValue(null) this@MainActivity.filePathCallback = filePathCallback - // 统一使用 ACTION_GET_CONTENT,避免不同 ROM 对 Photo Picker / 相册的兼容差异 - // 导致返回的 URI/文件对象在上传时失效。根据 acceptTypes 设置过滤类型, - // 图片选择仍只显示图片文件,但走文件管理器而不是系统相册。 + // 使用 ACTION_OPEN_DOCUMENT 走系统文件管理器(DocumentsUI), + // 避免 ACTION_GET_CONTENT 在某些 ROM 上唤起相册/Photo Picker 导致返回 URI 失效。 val acceptTypes = fileChooserParams?.acceptTypes?.filter { it.isNotBlank() }?.takeIf { it.isNotEmpty() } ?: arrayListOf("*/*") val allowMultiple = fileChooserParams?.mode == FileChooserParams.MODE_OPEN_MULTIPLE - val intent = Intent(Intent.ACTION_GET_CONTENT).apply { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = acceptTypes.first() if (allowMultiple) { diff --git a/package-lock.json b/package-lock.json index 447f4ea..5fd6786 100644 --- a/package-lock.json +++ b/package-lock.json @@ -770,6 +770,7 @@ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "license": "MIT", + "optional": true, "dependencies": { "@floating-ui/utils": "^0.2.11" } @@ -779,6 +780,7 @@ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", "license": "MIT", + "optional": true, "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" @@ -788,7 +790,8 @@ "version": "0.2.11", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", @@ -6964,7 +6967,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/static/src/components/chat/ShowFileCard.vue b/static/src/components/chat/ShowFileCard.vue index e9bee6f..b3b5299 100644 --- a/static/src/components/chat/ShowFileCard.vue +++ b/static/src/components/chat/ShowFileCard.vue @@ -75,6 +75,14 @@ :title="displayName" > + + + +
+
+ + PDF 文件(点击预览) +
@@ -94,6 +102,9 @@ const props = defineProps<{ preview?: string; }>(); +// ---- 环境检测 ---- +const isAndroidApp = typeof window !== 'undefined' && Boolean((window as any).AndroidPdfBridge); + // ---- 状态 ---- const loading = ref(false); const error = ref(''); @@ -108,9 +119,15 @@ const displayName = computed(() => props.name || props.path.split('/').pop() || const canPreview = computed(() => { if (props.preview === 'off') return false; + // Android App 内 PDF 不走 iframe 内嵌预览(WebView 不支持),改用原生 PdfPreviewActivity + if (isAndroidApp && fileType.value === 'pdf') return false; return ['text', 'code', 'json', 'csv', 'markdown', 'image', 'pdf'].includes(fileType.value); }); +const canAndroidPdfPreview = computed(() => { + return isAndroidApp && fileType.value === 'pdf'; +}); + const canCopy = computed(() => ['text', 'code', 'json', 'csv', 'markdown'].includes(fileType.value)); const metaText = computed(() => { @@ -275,6 +292,13 @@ function openFullImage() { if (contentUrl.value) window.open(contentUrl.value, '_blank'); } +function previewPdf() { + const bridge = (window as any).AndroidPdfBridge; + if (bridge && typeof bridge.previewPdf === 'function') { + bridge.previewPdf(contentUrl.value); + } +} + function onImageError() { error.value = '图片加载失败'; } @@ -482,4 +506,31 @@ onMounted(() => { border: none; display: block; } + +.sfc-android-pdf-preview { + border-top: 1px solid var(--border-default); + background: var(--surface-base); +} + +.sfc-android-pdf-placeholder { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + padding: 24px; + color: var(--text-secondary); + background: var(--surface-raised); + cursor: pointer; + transition: background 0.15s; +} + +.sfc-android-pdf-placeholder:hover { + background: var(--surface-muted); +} + +.sfc-android-pdf-placeholder .sfc-file-icon { + width: 20px; + height: 20px; + background-color: var(--text-secondary); +} diff --git a/static/src/components/personalization/PersonalizationDrawer.vue b/static/src/components/personalization/PersonalizationDrawer.vue index 1b71b60..164c1d4 100644 --- a/static/src/components/personalization/PersonalizationDrawer.vue +++ b/static/src/components/personalization/PersonalizationDrawer.vue @@ -450,6 +450,19 @@ +
+ + 上传诊断日志 + 复现问题后点击,把当前环境信息上传给开发者排查 + + +
{ window.location.href = url; }; +const uploadDiagnosticLog = async () => { + try { + const w = window as any; + const params = new URLSearchParams(window.location.search); + const screenInfo = + typeof window !== 'undefined' + ? { + width: window.innerWidth, + height: window.innerHeight, + dpr: window.devicePixelRatio + } + : null; + const bridgeProbe = (name: string) => { + const b = w?.[name]; + if (!b) return { exists: false }; + const methods: Record = {}; + [ + 'getAppVersionCode', + 'getAppVersionName', + 'onThemeChanged', + 'previewPdf', + 'isPdfPreviewSupported', + 'downloadFile', + 'isSupported', + 'isModelReady', + 'isModelPartial', + 'startRecording', + 'stopRecording' + ].forEach((m) => { + try { + methods[m] = typeof b[m] === 'function'; + } catch { + methods[m] = 'error'; + } + }); + let versionCode = ''; + let versionName = ''; + try { + versionCode = + typeof b.getAppVersionCode === 'function' ? String(b.getAppVersionCode() || '') : ''; + versionName = + typeof b.getAppVersionName === 'function' ? String(b.getAppVersionName() || '') : ''; + } catch {} + return { exists: true, methods, versionCode, versionName }; + }; + const payload = { + source: 'diagnostic_log', + url: window.location.href, + userAgent: navigator.userAgent, + screen: screenInfo, + urlParams: { + app_vc: params.get('app_vc') || '', + app_vn: params.get('app_vn') || '', + app_shell: params.get('app_shell') || '' + }, + bridges: { + AndroidThemeBridge: bridgeProbe('AndroidThemeBridge'), + AndroidPdfBridge: bridgeProbe('AndroidPdfBridge'), + AndroidDownloadBridge: bridgeProbe('AndroidDownloadBridge'), + AndroidVoiceBridge: bridgeProbe('AndroidVoiceBridge') + }, + appCurrentVersionCode: appCurrentVersionCode.value, + appCurrentVersionName: appCurrentVersionName.value, + appUpdateInfo: appUpdateInfo.value, + appUpdateError: appUpdateError.value, + appUpdateCheckedAt: appUpdateCheckedAt.value, + timestamp: new Date().toISOString() + }; + const resp = await fetch('/api/client_debug_log', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + if (!resp.ok) throw new Error(`status ${resp.status}`); + alert('诊断日志已上传'); + } catch (e: any) { + alert('上传诊断日志失败:' + (e?.message || String(e))); + } +}; + onMounted(async () => { hydrateAppVersionFromBridge(); try {