Skip to content

Commit 2f0fe3e

Browse files
committed
feat: 添加多语言支持链接和滚动功能到聊天界面
1 parent 9aa5e60 commit 2f0fe3e

14 files changed

Lines changed: 1544 additions & 189 deletions

File tree

.github/workflows/release.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,50 @@ jobs:
7979
prerelease: false
8080
args: ${{ matrix.args }}
8181

82+
build-android:
83+
runs-on: ubuntu-latest
84+
steps:
85+
- name: Checkout repository
86+
uses: actions/checkout@v4
87+
88+
- name: Setup Node.js
89+
uses: actions/setup-node@v4
90+
with:
91+
node-version: "20"
92+
93+
- name: Install pnpm
94+
uses: pnpm/action-setup@v3
95+
with:
96+
version: 9
97+
98+
- name: Install dependencies
99+
run: pnpm install
100+
101+
- name: Build reader assets
102+
run: pnpm --filter @readany/app-expo run build:reader
103+
104+
- name: Setup Expo
105+
uses: expo/expo-github-action@v8
106+
with:
107+
expo-version: latest
108+
eas-version: latest
109+
token: ${{ secrets.EXPO_TOKEN }}
110+
111+
- name: Build Android APK (Local)
112+
run: |
113+
cd packages/app-expo
114+
# Use --local to build on the runner itself and save EAS build credits
115+
npx eas-cli build --platform android --profile preview --local --non-interactive --output=../../ReadAny.apk
116+
117+
- name: Upload to Release
118+
uses: softprops/action-gh-release@v1
119+
if: startsWith(github.ref, 'refs/tags/')
120+
with:
121+
files: ReadAny.apk
122+
tag_name: ${{ github.ref_name }}
123+
env:
124+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
125+
82126
create-updater-json:
83127
needs: build
84128
runs-on: ubuntu-latest

packages/app-expo/app.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@
2121
"foregroundImage": "./assets/adaptive-icon.png",
2222
"backgroundColor": "#ffffff"
2323
},
24-
"package": "com.readany.app"
24+
"package": "com.readany.app",
25+
"permissions": [
26+
"android.permission.CAMERA",
27+
"android.permission.RECORD_AUDIO"
28+
]
2529
},
2630
"plugins": [
2731
"expo-font",
@@ -35,6 +39,11 @@
3539
}
3640
]
3741
],
38-
"scheme": "readany"
42+
"scheme": "readany",
43+
"extra": {
44+
"eas": {
45+
"projectId": "e9c65825-d965-4d58-a3af-46406ee8a9ae"
46+
}
47+
}
3948
}
4049
}

packages/app-expo/assets/reader/reader.html

Lines changed: 49 additions & 47 deletions
Large diffs are not rendered by default.

packages/app-expo/eas.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"cli": {
3+
"version": ">= 14.1.0"
4+
},
5+
"build": {
6+
"development": {
7+
"developmentClient": true,
8+
"distribution": "internal"
9+
},
10+
"preview": {
11+
"android": {
12+
"buildType": "apk"
13+
}
14+
},
15+
"production": {}
16+
}
17+
}

packages/app-expo/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212
"lint": "biome check ."
1313
},
1414
"dependencies": {
15+
"@aws-sdk/client-s3": "^3.712.0",
16+
"@huggingface/transformers": "^3.8.1",
17+
"@langchain/anthropic": "^1.3.21",
18+
"@langchain/core": "^1.1.29",
19+
"@langchain/deepseek": "^1.0.15",
20+
"@langchain/google-genai": "^2.1.21",
21+
"@langchain/langgraph": "^1.2.0",
22+
"@langchain/openai": "^1.2.11",
1523
"@react-navigation/bottom-tabs": "^7.15.5",
1624
"@react-navigation/native": "^7.1.33",
1725
"@react-navigation/native-stack": "^7.14.4",

packages/app-expo/src/screens/BookChatScreen.tsx

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
KeyboardAvoidingView,
1616
Platform,
1717
Pressable,
18+
ScrollView,
1819
StyleSheet,
1920
Text,
2021
TouchableOpacity,
@@ -399,60 +400,66 @@ export function BookChatScreen({ route, navigation }: Props) {
399400
<XIcon size={16} color={colors.foreground} />
400401
</TouchableOpacity>
401402
</View>
402-
{bookThreads.length === 0 ? (
403-
<View style={s.sidebarEmpty}>
404-
<Text style={s.sidebarEmptyText}>{t("chat.noConversations", "暂无对话")}</Text>
405-
</View>
406-
) : (
407-
groupedThreads.map(({ key, label, threads }) => {
408-
if (threads.length === 0) return null;
409-
return (
410-
<View key={key}>
411-
<Text style={s.sectionLabel}>{label}</Text>
412-
{threads.map((thread) => {
413-
const isActive = thread.id === activeThreadId;
414-
const lastMsg =
415-
thread.messages.length > 0
416-
? thread.messages[thread.messages.length - 1]
417-
: null;
418-
const preview = lastMsg?.content?.slice(0, 60) || "";
419-
return (
420-
<TouchableOpacity
421-
key={thread.id}
422-
style={[s.threadItem, isActive && s.threadItemActive]}
423-
onPress={() => handleSelectThread(thread.id)}
424-
activeOpacity={0.7}
425-
>
426-
<View style={s.threadContent}>
427-
<View style={s.threadTitleRow}>
428-
<Text
429-
style={[s.threadTitle, isActive && s.threadTitleActive]}
430-
numberOfLines={1}
431-
>
432-
{thread.title || t("chat.newChat", "新对话")}
433-
</Text>
434-
<Text style={s.threadTime}>{formatTime(thread.updatedAt)}</Text>
435-
</View>
436-
{preview ? (
437-
<Text style={s.threadPreview} numberOfLines={1}>
438-
{preview}
439-
</Text>
440-
) : null}
441-
</View>
403+
<ScrollView
404+
style={{ flex: 1 }}
405+
contentContainerStyle={{ paddingBottom: 20 }}
406+
showsVerticalScrollIndicator={false}
407+
>
408+
{bookThreads.length === 0 ? (
409+
<View style={s.sidebarEmpty}>
410+
<Text style={s.sidebarEmptyText}>{t("chat.noConversations", "暂无对话")}</Text>
411+
</View>
412+
) : (
413+
groupedThreads.map(({ key, label, threads }) => {
414+
if (threads.length === 0) return null;
415+
return (
416+
<View key={key}>
417+
<Text style={s.sectionLabel}>{label}</Text>
418+
{threads.map((thread) => {
419+
const isActive = thread.id === activeThreadId;
420+
const lastMsg =
421+
thread.messages.length > 0
422+
? thread.messages[thread.messages.length - 1]
423+
: null;
424+
const preview = lastMsg?.content?.slice(0, 60) || "";
425+
return (
442426
<TouchableOpacity
443-
style={s.threadDeleteBtn}
444-
onPress={() => removeThread(thread.id)}
445-
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
427+
key={thread.id}
428+
style={[s.threadItem, isActive && s.threadItemActive]}
429+
onPress={() => handleSelectThread(thread.id)}
430+
activeOpacity={0.7}
446431
>
447-
<Trash2Icon size={12} color={colors.mutedForeground} />
432+
<View style={s.threadContent}>
433+
<View style={s.threadTitleRow}>
434+
<Text
435+
style={[s.threadTitle, isActive && s.threadTitleActive]}
436+
numberOfLines={1}
437+
>
438+
{thread.title || t("chat.newChat", "新对话")}
439+
</Text>
440+
<Text style={s.threadTime}>{formatTime(thread.updatedAt)}</Text>
441+
</View>
442+
{preview ? (
443+
<Text style={s.threadPreview} numberOfLines={1}>
444+
{preview}
445+
</Text>
446+
) : null}
447+
</View>
448+
<TouchableOpacity
449+
style={s.threadDeleteBtn}
450+
onPress={() => removeThread(thread.id)}
451+
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
452+
>
453+
<Trash2Icon size={12} color={colors.mutedForeground} />
454+
</TouchableOpacity>
448455
</TouchableOpacity>
449-
</TouchableOpacity>
450-
);
451-
})}
452-
</View>
453-
);
454-
})
455-
)}
456+
);
457+
})}
458+
</View>
459+
);
460+
})
461+
)}
462+
</ScrollView>
456463
</Animated.View>
457464
</View>
458465
)}

packages/app-expo/src/screens/ChatScreen.tsx

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
KeyboardAvoidingView,
1616
Platform,
1717
Pressable,
18+
ScrollView,
1819
StyleSheet,
1920
Text,
2021
TouchableOpacity,
@@ -315,60 +316,66 @@ export function ChatScreen() {
315316
<XIcon size={16} color={colors.foreground} />
316317
</TouchableOpacity>
317318
</View>
318-
{generalThreads.length === 0 ? (
319-
<View style={s.sidebarEmpty}>
320-
<Text style={s.sidebarEmptyText}>{t("chat.noConversations", "暂无对话")}</Text>
321-
</View>
322-
) : (
323-
groupedThreads.map(({ key, label, threads }) => {
324-
if (threads.length === 0) return null;
325-
return (
326-
<View key={key}>
327-
<Text style={s.sectionLabel}>{label}</Text>
328-
{threads.map((thread) => {
329-
const isActive = thread.id === generalActiveThreadId;
330-
const lastMsg =
331-
thread.messages.length > 0
332-
? thread.messages[thread.messages.length - 1]
333-
: null;
334-
const preview = lastMsg?.content?.slice(0, 60) || "";
335-
return (
336-
<TouchableOpacity
337-
key={thread.id}
338-
style={[s.threadItem, isActive && s.threadItemActive]}
339-
onPress={() => handleSelectThread(thread.id)}
340-
activeOpacity={0.7}
341-
>
342-
<View style={s.threadContent}>
343-
<View style={s.threadTitleRow}>
344-
<Text
345-
style={[s.threadTitle, isActive && s.threadTitleActive]}
346-
numberOfLines={1}
347-
>
348-
{thread.title || t("chat.newChat", "新对话")}
349-
</Text>
350-
<Text style={s.threadTime}>{formatTime(thread.updatedAt)}</Text>
351-
</View>
352-
{preview ? (
353-
<Text style={s.threadPreview} numberOfLines={1}>
354-
{preview}
355-
</Text>
356-
) : null}
357-
</View>
319+
<ScrollView
320+
style={{ flex: 1 }}
321+
contentContainerStyle={{ paddingBottom: 20 }}
322+
showsVerticalScrollIndicator={false}
323+
>
324+
{generalThreads.length === 0 ? (
325+
<View style={s.sidebarEmpty}>
326+
<Text style={s.sidebarEmptyText}>{t("chat.noConversations", "暂无对话")}</Text>
327+
</View>
328+
) : (
329+
groupedThreads.map(({ key, label, threads }) => {
330+
if (threads.length === 0) return null;
331+
return (
332+
<View key={key}>
333+
<Text style={s.sectionLabel}>{label}</Text>
334+
{threads.map((thread) => {
335+
const isActive = thread.id === generalActiveThreadId;
336+
const lastMsg =
337+
thread.messages.length > 0
338+
? thread.messages[thread.messages.length - 1]
339+
: null;
340+
const preview = lastMsg?.content?.slice(0, 60) || "";
341+
return (
358342
<TouchableOpacity
359-
style={s.threadDeleteBtn}
360-
onPress={() => removeThread(thread.id)}
361-
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
343+
key={thread.id}
344+
style={[s.threadItem, isActive && s.threadItemActive]}
345+
onPress={() => handleSelectThread(thread.id)}
346+
activeOpacity={0.7}
362347
>
363-
<Trash2Icon size={12} color={colors.mutedForeground} />
348+
<View style={s.threadContent}>
349+
<View style={s.threadTitleRow}>
350+
<Text
351+
style={[s.threadTitle, isActive && s.threadTitleActive]}
352+
numberOfLines={1}
353+
>
354+
{thread.title || t("chat.newChat", "新对话")}
355+
</Text>
356+
<Text style={s.threadTime}>{formatTime(thread.updatedAt)}</Text>
357+
</View>
358+
{preview ? (
359+
<Text style={s.threadPreview} numberOfLines={1}>
360+
{preview}
361+
</Text>
362+
) : null}
363+
</View>
364+
<TouchableOpacity
365+
style={s.threadDeleteBtn}
366+
onPress={() => removeThread(thread.id)}
367+
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
368+
>
369+
<Trash2Icon size={12} color={colors.mutedForeground} />
370+
</TouchableOpacity>
364371
</TouchableOpacity>
365-
</TouchableOpacity>
366-
);
367-
})}
368-
</View>
369-
);
370-
})
371-
)}
372+
);
373+
})}
374+
</View>
375+
);
376+
})
377+
)}
378+
</ScrollView>
372379
</Animated.View>
373380
</View>
374381
)}

packages/app-expo/src/screens/ProfileScreen.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ function MiniHeatmap({ dailyStats }: { dailyStats: DailyStats[] }) {
255255
export function ProfileScreen() {
256256
const colors = useColors();
257257
const s = makeStyles(colors);
258-
const { t } = useTranslation();
258+
const { t, i18n } = useTranslation();
259259
const nav = useNavigation<Nav>();
260260
const [overall, setOverall] = useState<OverallStats | null>(null);
261261
const [dailyStats, setDailyStats] = useState<DailyStats[]>([]);
@@ -329,7 +329,7 @@ export function ProfileScreen() {
329329
{
330330
icon: HelpCircleIcon,
331331
label: t("about.supportCenter", "帮助中心"),
332-
url: "https://codedogqby.github.io/ReadAny/support/",
332+
url: `https://codedogqby.github.io/ReadAny/${i18n.language === "zh" ? "zh/" : ""}support/`,
333333
},
334334
{ icon: InfoIcon, label: t("settings.about", "关于"), route: "About" as const },
335335
],

packages/app/src/components/layout/Sidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const NAV_ITEMS: NavItem[] = [
4343
];
4444

4545
export function HomeSidebar() {
46-
const { t } = useTranslation();
46+
const { t, i18n } = useTranslation();
4747
const { activeTabId, setActiveTab, addTab } = useAppStore();
4848
const { filter, setFilter, allTags, activeTag, setActiveTag, addTag, removeTag, renameTag } =
4949
useLibraryStore();
@@ -382,7 +382,7 @@ export function HomeSidebar() {
382382
<span className="text-sm">{t("stats.title")}</span>
383383
</button>
384384
<a
385-
href="https://codedogqby.github.io/ReadAny/ support/"
385+
href={`https://codedogqby.github.io/ReadAny/${i18n.language === "zh" ? "zh/" : ""}support/`}
386386
target="_blank"
387387
rel="noopener noreferrer"
388388
className="flex w-full items-center gap-2 rounded-md p-1 py-1 text-left text-muted-foreground text-sm hover:bg-muted hover:text-foreground"

0 commit comments

Comments
 (0)