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"
>
+
+
+
+
@@ -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 {