From 8177c602df5584fbdc544db511d7c7a9aeb82047 Mon Sep 17 00:00:00 2001 From: Dishit Date: Thu, 14 May 2026 10:07:08 +0530 Subject: [PATCH 1/2] feat(chat): enable partial text selection and per-code-block copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace TouchableOpacity bubble wrapper with View so native text selection gestures reach inner text nodes - Swap @ronradtke/react-native-markdown-display for react-native-enriched-markdown (native Fabric renderer) to enable cross-paragraph text selection - Split markdown rendering: code fences extracted and rendered as a custom CodeBlock component with language label and Copy button; prose segments rendered by EnrichedMarkdownText with selectable=true - Remove Copy and Regenerate from long-press action sheet; restore full action sheet (Copy all, Regenerate, Edit, Generate Image) via the ••• meta row button - Fix phi model detection regex to use word-boundary + digit pattern so dolphin models are no longer incorrectly blocked - Add terminal icon in chat header to open existing DebugLogsScreen with touch and structure logs for diagnosing gesture issues Co-authored-by: Dishit Karia " --- ios/Podfile.lock | 36 ++ package-lock.json | 519 +++++++++--------- package.json | 1 + .../components/ActionMenuSheet.tsx | 6 +- .../ChatMessage/components/MessageContent.tsx | 21 +- src/components/ChatMessage/index.tsx | 17 +- src/components/MarkdownText.tsx | 294 +++++----- .../ChatScreen/ChatScreenComponents.tsx | 6 +- src/screens/ChatScreen/index.tsx | 4 + src/screens/ModelsScreen/utils.ts | 3 +- 10 files changed, 492 insertions(+), 415 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7cedf2d7..4c740519 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -10,6 +10,7 @@ PODS: - hermes-engine (0.14.0): - hermes-engine/Pre-built (= 0.14.0) - hermes-engine/Pre-built (0.14.0) + - iosMath (0.9.4) - llama-rn (0.12.0-rc.9): - boost - DoubleConversion @@ -2797,6 +2798,35 @@ PODS: - React-perflogger (= 0.83.1) - React-utils (= 0.83.1) - SocketRocket + - ReactNativeEnrichedMarkdown (0.5.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - iosMath (~> 0.9) + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga - RNCAsyncStorage (2.2.0): - boost - DoubleConversion @@ -3368,6 +3398,7 @@ DEPENDENCIES: - ReactAppDependencyProvider (from `build/generated/ios/ReactAppDependencyProvider`) - ReactCodegen (from `build/generated/ios/ReactCodegen`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - ReactNativeEnrichedMarkdown (from `../node_modules/react-native-enriched-markdown`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - RNDeviceInfo (from `../node_modules/react-native-device-info`) - RNFS (from `../node_modules/react-native-fs`) @@ -3386,6 +3417,7 @@ DEPENDENCIES: SPEC REPOS: trunk: + - iosMath - lottie-ios - SocketRocket - SSZipArchive @@ -3566,6 +3598,8 @@ EXTERNAL SOURCES: :path: build/generated/ios/ReactCodegen ReactCommon: :path: "../node_modules/react-native/ReactCommon" + ReactNativeEnrichedMarkdown: + :path: "../node_modules/react-native-enriched-markdown" RNCAsyncStorage: :path: "../node_modules/@react-native-async-storage/async-storage" RNDeviceInfo: @@ -3604,6 +3638,7 @@ SPEC CHECKSUMS: fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: 8c6be38f94b3bf8b864981980e64e55f08e467ec + iosMath: f7a6cbadf9d836d2149c2a84c435b1effc244cba llama-rn: 796fa53f37f89e2c77cd6c462ad1172ee96d4c80 lottie-ios: a881093fab623c467d3bce374367755c272bdd59 lottie-react-native: 691b8363e8c591fb78a78254ff2517258891456b @@ -3684,6 +3719,7 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 0eb286cc274abb059ee601b862ebddac2e681d01 ReactCodegen: 3d48510bcef445f6403c0004047d4d9cbb915435 ReactCommon: ac934cb340aee91282ecd6f273a26d24d4c55cae + ReactNativeEnrichedMarkdown: bacee50e468b43a7bd133a28e3c777c29af914c4 RNCAsyncStorage: 29f0230e1a25f36c20b05f65e2eb8958d6526e82 RNDeviceInfo: 36d7f232bfe7c9b5c494cb7793230424ed32c388 RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8 diff --git a/package-lock.json b/package-lock.json index 8571ba17..949f35f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "react": "19.2.0", "react-native": "0.83.1", "react-native-device-info": "^15.0.1", + "react-native-enriched-markdown": "^0.5.0", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "^2.30.0", "react-native-haptic-feedback": "^2.3.3", @@ -2252,229 +2253,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@expo/config-plugins": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-2.0.4.tgz", - "integrity": "sha512-JGt/X2tFr7H8KBQrKfbGo9hmCubQraMxq5sj3bqDdKmDOLcE1a/EDCP9g0U4GHsa425J8VDIkQUHYz3h3ndEXQ==", - "license": "MIT", - "dependencies": { - "@expo/config-types": "^41.0.0", - "@expo/json-file": "8.2.30", - "@expo/plist": "0.0.13", - "debug": "^4.3.1", - "find-up": "~5.0.0", - "fs-extra": "9.0.0", - "getenv": "^1.0.0", - "glob": "7.1.6", - "resolve-from": "^5.0.0", - "slash": "^3.0.0", - "xcode": "^3.0.1", - "xml2js": "^0.4.23" - } - }, - "node_modules/@expo/config-plugins/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@expo/config-plugins/node_modules/fs-extra": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", - "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@expo/config-plugins/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@expo/config-plugins/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@expo/config-plugins/node_modules/jsonfile/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@expo/config-plugins/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@expo/config-plugins/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@expo/config-plugins/node_modules/universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@expo/config-types": { - "version": "41.0.0", - "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-41.0.0.tgz", - "integrity": "sha512-Ax0pHuY5OQaSrzplOkT9DdpdmNzaVDnq9VySb4Ujq7UJ4U4jriLy8u93W98zunOXpcu0iiKubPsqD6lCiq0pig==", - "license": "MIT" - }, - "node_modules/@expo/json-file": { - "version": "8.2.30", - "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-8.2.30.tgz", - "integrity": "sha512-vrgGyPEXBoFI5NY70IegusCSoSVIFV3T3ry4tjJg1MFQKTUlR7E0r+8g8XR6qC705rc2PawaZQjqXMAVtV6s2A==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "~7.10.4", - "fs-extra": "9.0.0", - "json5": "^1.0.1", - "write-file-atomic": "^2.3.0" - } - }, - "node_modules/@expo/json-file/node_modules/@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@expo/json-file/node_modules/fs-extra": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", - "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@expo/json-file/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/@expo/json-file/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@expo/json-file/node_modules/jsonfile/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@expo/json-file/node_modules/universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@expo/json-file/node_modules/write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "node_modules/@expo/plist": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.0.13.tgz", - "integrity": "sha512-zGPSq9OrCn7lWvwLLHLpHUUq2E40KptUFXn53xyZXPViI0k9lbApcR9KlonQZ95C+ELsf0BQ3gRficwK92Ivcw==", - "license": "MIT", - "dependencies": { - "base64-js": "^1.2.3", - "xmlbuilder": "^14.0.0", - "xmldom": "~0.5.0" - } - }, "node_modules/@floating-ui/core": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", @@ -3536,6 +3314,224 @@ "react-native": ">= 0.60.2" } }, + "node_modules/@react-native-voice/voice/node_modules/@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@react-native-voice/voice/node_modules/@expo/config-plugins": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-2.0.4.tgz", + "integrity": "sha512-JGt/X2tFr7H8KBQrKfbGo9hmCubQraMxq5sj3bqDdKmDOLcE1a/EDCP9g0U4GHsa425J8VDIkQUHYz3h3ndEXQ==", + "license": "MIT", + "dependencies": { + "@expo/config-types": "^41.0.0", + "@expo/json-file": "8.2.30", + "@expo/plist": "0.0.13", + "debug": "^4.3.1", + "find-up": "~5.0.0", + "fs-extra": "9.0.0", + "getenv": "^1.0.0", + "glob": "7.1.6", + "resolve-from": "^5.0.0", + "slash": "^3.0.0", + "xcode": "^3.0.1", + "xml2js": "^0.4.23" + } + }, + "node_modules/@react-native-voice/voice/node_modules/@expo/config-types": { + "version": "41.0.0", + "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-41.0.0.tgz", + "integrity": "sha512-Ax0pHuY5OQaSrzplOkT9DdpdmNzaVDnq9VySb4Ujq7UJ4U4jriLy8u93W98zunOXpcu0iiKubPsqD6lCiq0pig==", + "license": "MIT" + }, + "node_modules/@react-native-voice/voice/node_modules/@expo/json-file": { + "version": "8.2.30", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-8.2.30.tgz", + "integrity": "sha512-vrgGyPEXBoFI5NY70IegusCSoSVIFV3T3ry4tjJg1MFQKTUlR7E0r+8g8XR6qC705rc2PawaZQjqXMAVtV6s2A==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "~7.10.4", + "fs-extra": "9.0.0", + "json5": "^1.0.1", + "write-file-atomic": "^2.3.0" + } + }, + "node_modules/@react-native-voice/voice/node_modules/@expo/plist": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.0.13.tgz", + "integrity": "sha512-zGPSq9OrCn7lWvwLLHLpHUUq2E40KptUFXn53xyZXPViI0k9lbApcR9KlonQZ95C+ELsf0BQ3gRficwK92Ivcw==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.2.3", + "xmlbuilder": "^14.0.0", + "xmldom": "~0.5.0" + } + }, + "node_modules/@react-native-voice/voice/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@react-native-voice/voice/node_modules/fs-extra": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", + "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-voice/voice/node_modules/getenv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/getenv/-/getenv-1.0.0.tgz", + "integrity": "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-voice/voice/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@react-native-voice/voice/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/@react-native-voice/voice/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@react-native-voice/voice/node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@react-native-voice/voice/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@react-native-voice/voice/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-voice/voice/node_modules/universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@react-native-voice/voice/node_modules/write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "node_modules/@react-native-voice/voice/node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@react-native-voice/voice/node_modules/xml2js/node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@react-native-voice/voice/node_modules/xmlbuilder": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-14.0.0.tgz", + "integrity": "sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg==", + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.83.1", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.83.1.tgz", @@ -7799,15 +7795,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/getenv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/getenv/-/getenv-1.0.0.tgz", - "integrity": "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -11857,15 +11844,6 @@ "node": ">=10.4.0" } }, - "node_modules/plist/node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "license": "MIT", - "engines": { - "node": ">=8.0" - } - }, "node_modules/popmotion": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.3.tgz", @@ -12233,6 +12211,31 @@ "react-native": "*" } }, + "node_modules/react-native-enriched-markdown": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/react-native-enriched-markdown/-/react-native-enriched-markdown-0.5.0.tgz", + "integrity": "sha512-CYihNlcg8IjsrVbmypDjFHsDF6Z6vYO0yraw6CkR30SDsg99J6fcBrOkyWOItO8PIDD21jw8yD1eYP/0LBOCdA==", + "license": "MIT", + "workspaces": [ + "apps/example", + "apps/macos-example", + "apps/web-example" + ], + "peerDependencies": { + "@expo/config-plugins": ">=50.0.0", + "katex": ">=0.16.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + }, + "katex": { + "optional": true + } + } + }, "node_modules/react-native-fit-image": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/react-native-fit-image/-/react-native-fit-image-1.5.5.tgz", @@ -13044,9 +13047,9 @@ "license": "MIT" }, "node_modules/sax": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", - "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" @@ -14607,32 +14610,10 @@ "node": ">=10.0.0" } }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, "node_modules/xmlbuilder": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-14.0.0.tgz", - "integrity": "sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg==", + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", "license": "MIT", "engines": { "node": ">=8.0" diff --git a/package.json b/package.json index 5c1ffe0a..ac8ac085 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "react": "19.2.0", "react-native": "0.83.1", "react-native-device-info": "^15.0.1", + "react-native-enriched-markdown": "^0.5.0", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "^2.30.0", "react-native-haptic-feedback": "^2.3.3", diff --git a/src/components/ChatMessage/components/ActionMenuSheet.tsx b/src/components/ChatMessage/components/ActionMenuSheet.tsx index 1f380fe2..4b56c0dd 100644 --- a/src/components/ChatMessage/components/ActionMenuSheet.tsx +++ b/src/components/ChatMessage/components/ActionMenuSheet.tsx @@ -10,12 +10,11 @@ interface ActionMenuSheetProps { onClose: () => void; isUser: boolean; canEdit: boolean; - canRetry: boolean; canGenerateImage: boolean; styles: any; onCopy: () => void; onEdit: () => void; - onRetry: () => void; + onRetry?: () => void; onGenerateImage: () => void; } @@ -24,7 +23,6 @@ export function ActionMenuSheet({ onClose, isUser, canEdit, - canRetry, canGenerateImage, styles, onCopy, @@ -64,7 +62,7 @@ export function ActionMenuSheet({ )} - {canRetry && ( + {!!onRetry && ( ) { + useEffect(() => { + logger.log( + `[Structure] MessageContent mounted — role=${isUser ? 'user' : 'assistant'} isThinking=${isThinking} isStreaming=${isStreaming} contentLen=${content?.length ?? 0} hasThinking=${!!parsedContent.thinking} hasResponse=${!!parsedContent.response}` + ); + }, []); + if (isThinking) { return ( @@ -71,7 +78,17 @@ export function MessageContent({ ); } return ( - + { + logger.log('[Touch] assistant bubble View — onStartShouldSetResponder fired (returning false so children can claim)'); + return false; + }} + onMoveShouldSetResponder={() => { + logger.log('[Touch] assistant bubble View — onMoveShouldSetResponder fired'); + return false; + }} + > {parsedContent.response} {isStreaming && } diff --git a/src/components/ChatMessage/index.tsx b/src/components/ChatMessage/index.tsx index d80310b7..0316a517 100644 --- a/src/components/ChatMessage/index.tsx +++ b/src/components/ChatMessage/index.tsx @@ -230,12 +230,6 @@ export const ChatMessage: React.FC = ({ setIsEditing(false); }; - const handleLongPress = () => { - if (!showActions || isStreaming) return; - triggerHaptic('impactMedium'); - setShowActionMenu(true); - }; - const handleGenerateImage = () => { const source = isUser ? message.content : parsedContent.response; onGenerateImage?.(source.trim().slice(0, 500)); @@ -252,15 +246,12 @@ export const ChatMessage: React.FC = ({ onToggle={() => setShowThinking(!showThinking)} styles={styles} colors={colors} />; } const messageBody = ( - {hasAttachments && ( @@ -296,24 +287,22 @@ export const ChatMessage: React.FC = ({ {showGenerationDetails && !isUser && message.generationMeta && ( )} - + ); return ( <> {animateEntry ? {messageBody} : messageBody} - setShowActionMenu(false)} isUser={isUser} canEdit={!!onEdit} - canRetry={!!onRetry} canGenerateImage={canGenerateImage && !!onGenerateImage} styles={styles} onCopy={handleCopy} onEdit={handleEdit} - onRetry={handleRetry} + onRetry={!!onRetry ? handleRetry : undefined} onGenerateImage={handleGenerateImage} /> { + Clipboard.setString(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }, [code]); -/** Custom link rule that constrains the Pressable wrapper width */ -function createLinkRule(onPress: (url: string) => void) { - return (node: any, renderChildren: any, _parent: any) => ( - onPress(node.attributes?.href ?? '')} - > - {renderChildren} - + return ( + + + {language} + + + + {copied ? 'Copied' : 'Copy'} + + + + + {code} + + ); } +const codeBlockStyles = StyleSheet.create({ + container: { + borderRadius: 6, + borderWidth: 1, + marginVertical: SPACING.sm, + overflow: 'hidden', + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: SPACING.md, + paddingVertical: 6, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: 'rgba(128,128,128,0.2)', + }, + lang: { + fontSize: 11, + fontFamily: FONTS.mono, + textTransform: 'uppercase', + }, + copyBtn: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + copyLabel: { + fontSize: 11, + }, + code: { + fontSize: 12, + lineHeight: 18, + padding: SPACING.md, + }, +}); + +type Segment = + | { type: 'markdown'; content: string } + | { type: 'code'; language: string; code: string }; + +function splitSegments(text: string): Segment[] { + const segments: Segment[] = []; + const fence = /^```([^\n]*)\n([\s\S]*?)^```/gm; + let lastIndex = 0; + let match: RegExpExecArray | null; + + while ((match = fence.exec(text)) !== null) { + if (match.index > lastIndex) { + const md = text.slice(lastIndex, match.index).trim(); + if (md) segments.push({ type: 'markdown', content: md }); + } + segments.push({ type: 'code', language: match[1].trim(), code: match[2] }); + lastIndex = match.index + match[0].length; + } + + if (lastIndex < text.length) { + const md = text.slice(lastIndex).trim(); + if (md) segments.push({ type: 'markdown', content: md }); + } + + return segments; +} + interface MarkdownTextProps { children: string; dimmed?: boolean; @@ -39,162 +108,139 @@ interface MarkdownTextProps { export function MarkdownText({ children, dimmed }: MarkdownTextProps) { const { colors } = useTheme(); - const markdownStyles = useMemo( - () => createMarkdownStyles(colors, dimmed), - [colors, dimmed], - ); - const handleLinkPress = useCallback((url: string) => { - Linking.openURL(url); - return false; - }, []); + const markdownStyle = useMemo(() => createMarkdownStyle(colors, dimmed), [colors, dimmed]); - const processed = useMemo(() => preprocessMarkdown(children), [children]); - const rules = useMemo(() => ({ link: createLinkRule(handleLinkPress) }), [handleLinkPress]); + const segments = useMemo(() => splitSegments(preprocessMarkdown(children)), [children]); + + const handleLinkPress = useCallback((event: { url: string }) => { + logger.log(`[MarkdownText] link pressed: ${event.url}`); + Linking.openURL(event.url); + }, []); return ( - - {processed} - + + {segments.map((seg, i) => + seg.type === 'code' ? ( + + ) : ( + + ) + )} + ); } -function createMarkdownStyles(colors: ThemeColors, dimmed?: boolean) { +function createMarkdownStyle(colors: ThemeColors, dimmed?: boolean) { const textColor = dimmed ? colors.textSecondary : colors.text; return { - body: { - ...TYPOGRAPHY.body, + paragraph: { + fontSize: TYPOGRAPHY.body.fontSize, + fontFamily: TYPOGRAPHY.body.fontFamily, color: textColor, lineHeight: 20, - flexShrink: 1, + marginBottom: SPACING.sm, }, - heading1: { - ...TYPOGRAPHY.h2, - fontWeight: '600' as const, + h1: { + fontSize: 20, + fontFamily: FONTS.mono, color: textColor, marginTop: SPACING.sm, marginBottom: SPACING.xs, }, - heading2: { - ...TYPOGRAPHY.h2, + h2: { + fontSize: TYPOGRAPHY.h2.fontSize, + fontFamily: FONTS.mono, color: textColor, marginTop: SPACING.sm, marginBottom: SPACING.xs, }, - heading3: { - ...TYPOGRAPHY.h3, - fontWeight: '600' as const, + h3: { + fontSize: TYPOGRAPHY.h3.fontSize, + fontFamily: FONTS.mono, + color: textColor, + marginTop: SPACING.xs, + marginBottom: 2, + }, + h4: { + fontSize: TYPOGRAPHY.h3.fontSize, + fontFamily: FONTS.mono, + color: textColor, + marginTop: SPACING.xs, + marginBottom: 2, + }, + h5: { + fontSize: 12, + fontFamily: FONTS.mono, color: textColor, marginTop: SPACING.xs, marginBottom: 2, }, - heading4: { - ...TYPOGRAPHY.h3, + h6: { + fontSize: 11, + fontFamily: FONTS.mono, color: textColor, marginTop: SPACING.xs, marginBottom: 2, }, strong: { - fontWeight: '700' as const, + fontWeight: 'bold' as const, }, em: { fontStyle: 'italic' as const, }, - s: { - textDecorationLine: 'line-through' as const, - }, - code_inline: { + code: { fontFamily: FONTS.mono, fontSize: 13, - backgroundColor: colors.surfaceLight, color: colors.primary, - paddingHorizontal: 4, - paddingVertical: 1, - borderRadius: 3, - // Override default border - borderWidth: 0, - }, - fence: { - fontFamily: FONTS.mono, - fontSize: 12, backgroundColor: colors.surfaceLight, - color: textColor, - borderRadius: 6, - padding: SPACING.md, - marginVertical: SPACING.sm, - borderWidth: 0, }, - code_block: { + codeBlock: { fontFamily: FONTS.mono, fontSize: 12, - backgroundColor: colors.surfaceLight, color: textColor, + backgroundColor: colors.surfaceLight, + borderColor: colors.border, + borderWidth: 1, borderRadius: 6, padding: SPACING.md, - marginVertical: SPACING.sm, - borderWidth: 0, + marginBottom: SPACING.sm, }, blockquote: { - borderLeftWidth: 3, - borderLeftColor: colors.primary, - paddingLeft: SPACING.md, - marginLeft: 0, - marginVertical: SPACING.sm, + borderColor: colors.primary, + borderWidth: 3, backgroundColor: colors.surfaceLight, - borderRadius: 0, - paddingVertical: SPACING.xs, + marginBottom: SPACING.sm, }, - bullet_list: { - marginVertical: SPACING.xs, + list: { + fontSize: TYPOGRAPHY.body.fontSize, + fontFamily: TYPOGRAPHY.body.fontFamily, + color: textColor, + lineHeight: 20, + marginBottom: SPACING.xs, }, - ordered_list: { - marginVertical: SPACING.xs, + link: { + color: colors.primary, + underline: true, }, - list_item: { - marginVertical: 4, + thematicBreak: { + color: colors.border, + height: 1, + marginTop: SPACING.md, + marginBottom: SPACING.md, }, - // Tables table: { - borderWidth: 1, borderColor: colors.border, + borderWidth: 1, borderRadius: 4, - marginVertical: SPACING.sm, - }, - thead: { - backgroundColor: colors.surfaceLight, - }, - th: { - padding: SPACING.sm, - borderWidth: 0.5, - borderColor: colors.border, - fontWeight: '600' as const, - }, - td: { - padding: SPACING.sm, - borderWidth: 0.5, - borderColor: colors.border, - }, - tr: { - borderBottomWidth: 0.5, - borderColor: colors.border, - }, - hr: { - backgroundColor: colors.border, - height: 1, - marginVertical: SPACING.md, - }, - link: { - color: colors.primary, - textDecorationLine: 'underline' as const, - }, - paragraph: { - marginTop: 0, marginBottom: SPACING.sm, }, - // Image (unlikely in LLM text but handle gracefully) - image: { - borderRadius: 6, - }, }; } diff --git a/src/screens/ChatScreen/ChatScreenComponents.tsx b/src/screens/ChatScreen/ChatScreenComponents.tsx index 080027aa..f3c184fb 100644 --- a/src/screens/ChatScreen/ChatScreenComponents.tsx +++ b/src/screens/ChatScreen/ChatScreenComponents.tsx @@ -113,8 +113,9 @@ export const ChatHeader: React.FC<{ setShowModelSelector: (v: boolean) => void; setShowSettingsPanel: (v: boolean) => void; setShowProjectSelector: (v: boolean) => void; + setShowDebugLogs: (v: boolean) => void; isRemote?: boolean; -}> = ({ styles, colors, activeConversation, activeModel, activeModelName, activeImageModel, activeProject, navigation, setShowModelSelector, setShowSettingsPanel, setShowProjectSelector, isRemote }) => ( +}> = ({ styles, colors, activeConversation, activeModel, activeModelName, activeImageModel, activeProject, navigation, setShowModelSelector, setShowSettingsPanel, setShowProjectSelector, setShowDebugLogs, isRemote }) => ( navigation.goBack()}> @@ -149,6 +150,9 @@ export const ChatHeader: React.FC<{ + setShowDebugLogs(true)} testID="debug-logs-icon"> + + setShowSettingsPanel(true)} testID="chat-settings-icon"> diff --git a/src/screens/ChatScreen/index.tsx b/src/screens/ChatScreen/index.tsx index 27c5475b..bb349479 100644 --- a/src/screens/ChatScreen/index.tsx +++ b/src/screens/ChatScreen/index.tsx @@ -16,6 +16,7 @@ import { MessageRenderer } from './MessageRenderer'; import { NoModelScreen, LoadingScreen, ChatHeader } from './ChatScreenComponents'; import { ChatModalSection } from './ChatModalSection'; import { ChatMessageArea } from './ChatMessageArea'; +import { DebugLogsScreen } from '../../components/DebugLogsScreen'; function countConversationImages(conv: Conversation | undefined): number { return (conv?.messages || []).reduce((n: number, m: Message) => @@ -31,6 +32,7 @@ export const ChatScreen: React.FC = () => { const pendingNextRef = useRef(null); const [sharePromptVisible, setSharePromptVisible] = useState(false); + const [showDebugLogs, setShowDebugLogs] = useState(false); useEffect(() => subscribeSharePrompt(() => setSharePromptVisible(true)), []); // Only ONE AttachStep mounted at a time to avoid waypoint dots/lines. // chatSpotlight controls which index is active (3, 12, 15, or 16). @@ -184,6 +186,7 @@ export const ChatScreen: React.FC = () => { setShowModelSelector={chat.setShowModelSelector} setShowSettingsPanel={chat.setShowSettingsPanel} setShowProjectSelector={chat.setShowProjectSelector} + setShowDebugLogs={setShowDebugLogs} isRemote={chat.activeModelInfo?.isRemote} /> { {alertEl} setSharePromptVisible(false)} /> + {showDebugLogs && setShowDebugLogs(false)} />} ); }; diff --git a/src/screens/ModelsScreen/utils.ts b/src/screens/ModelsScreen/utils.ts index 226b7e48..40d5f815 100644 --- a/src/screens/ModelsScreen/utils.ts +++ b/src/screens/ModelsScreen/utils.ts @@ -71,7 +71,8 @@ export function getModelType(model: ModelInfo): ModelTypeFilter { export function isPhiModel(modelName: string, modelId: string): boolean { const name = modelName.toLowerCase(); const id = modelId.toLowerCase(); - return name.includes('phi') || id.includes('phi'); + const phiPattern = /\bphi[-_]?\d/i; + return phiPattern.test(name) || phiPattern.test(id); } export function getTextModelCompatibility( From f0d48e82633db9366d5d38a8b3d2bd69ae0e1759 Mon Sep 17 00:00:00 2001 From: Dishit Date: Thu, 14 May 2026 10:13:00 +0530 Subject: [PATCH 2/2] fix lints --- src/components/ChatMessage/components/MessageContent.tsx | 1 + src/components/ChatMessage/index.tsx | 2 +- src/components/MarkdownText.tsx | 3 +-- src/screens/ModelsScreen/utils.ts | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/ChatMessage/components/MessageContent.tsx b/src/components/ChatMessage/components/MessageContent.tsx index 9c73cf55..6c5e3ad3 100644 --- a/src/components/ChatMessage/components/MessageContent.tsx +++ b/src/components/ChatMessage/components/MessageContent.tsx @@ -32,6 +32,7 @@ export function MessageContent({ logger.log( `[Structure] MessageContent mounted — role=${isUser ? 'user' : 'assistant'} isThinking=${isThinking} isStreaming=${isStreaming} contentLen=${content?.length ?? 0} hasThinking=${!!parsedContent.thinking} hasResponse=${!!parsedContent.response}` ); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); if (isThinking) { diff --git a/src/components/ChatMessage/index.tsx b/src/components/ChatMessage/index.tsx index 0316a517..3ebf9838 100644 --- a/src/components/ChatMessage/index.tsx +++ b/src/components/ChatMessage/index.tsx @@ -302,7 +302,7 @@ export const ChatMessage: React.FC = ({ styles={styles} onCopy={handleCopy} onEdit={handleEdit} - onRetry={!!onRetry ? handleRetry : undefined} + onRetry={onRetry ? handleRetry : undefined} onGenerateImage={handleGenerateImage} /> - + {language} @@ -52,7 +52,6 @@ const codeBlockStyles = StyleSheet.create({ paddingHorizontal: SPACING.md, paddingVertical: 6, borderBottomWidth: StyleSheet.hairlineWidth, - borderBottomColor: 'rgba(128,128,128,0.2)', }, lang: { fontSize: 11, diff --git a/src/screens/ModelsScreen/utils.ts b/src/screens/ModelsScreen/utils.ts index 40d5f815..226b7e48 100644 --- a/src/screens/ModelsScreen/utils.ts +++ b/src/screens/ModelsScreen/utils.ts @@ -71,8 +71,7 @@ export function getModelType(model: ModelInfo): ModelTypeFilter { export function isPhiModel(modelName: string, modelId: string): boolean { const name = modelName.toLowerCase(); const id = modelId.toLowerCase(); - const phiPattern = /\bphi[-_]?\d/i; - return phiPattern.test(name) || phiPattern.test(id); + return name.includes('phi') || id.includes('phi'); } export function getTextModelCompatibility(