对于这个题目,我是很抗拒的,想了怎么写之后,大概有一个思路,准备使用React Natvie做一个与AI 大模型对话的App,为什么是React Native,因为我对Flutter 太过于熟悉了,以至于我觉得使用 flutter来写一个实在没有什么挑战,而我又对ReactNative基本没有怎么使用过,不来点挑战点的,似乎不能体现出我装逼的潜质,也恰好算作最佳实践指北吧。
废话不多说,直接上手开干,我们要做的一个App是和ChatGPT这样的大模型对话,不仅可以进行文本对话,还应该可以让他给我们生成图片,而且为了通用,我们不仅需要与ChatGPT对还,还要求可以和Gemini对话,甚至其他的大模型。可能说起来有点复杂,其实我们只需要对接一套api,多亏了这个库:https://github.com/songquanpeng/one-api,他可以将其他大模型的API统一为ChatGPT方式来访问。
如图所示,你只需要管理渠道即可:
见下图所示,我因为有Gemini 的API,所以我给添加进来了,注意这里是需要一点点黑科技的,one-api 最好部署在某些不可描述的“万恶”的区域,这样以便他可以顺利和一些很激动人心的大模型进行对话,当然你说我就想使用 混元大模型,那就直接在腾讯云上买一台服务器使用 Docker 部署就好了。
当然,以上都不是重点,但是么有以上,我们客户端的代码将会比较麻烦,会需要去适配多个模型,但是本质上,不都是一样吗,这也就应了那句话,你以为的岁月静好,不过是有人在替你负重前行罢了,所以,如果你认为这个项目帮助你了,你可以表示一下对吧。
首先我们分析一下功能
当然,我们选择React Native,用于跨平台移动应用开发,这样一套代码可以搞定android和ios,后端one-api直接按照文档,使用docker 进行安装即可,没有什么难度。本地数据存储,我们使用 async-storage ,网络请求框架,我们这次晚点有意思的,使用 anstack.com/query 。
为什么UI的选择单独拿出来呢,因为颜值即正义,对吗,所以,我选择 reactnativeelements
他提供的demo可以直接看下,另外,因为他配置了 expo 的模板方式给我们初始化一个项目,那是相当的滋润啊。
npx create-expo-app --template @rneui/template
上述方式,直接给你生成一个项目,如下:这是我生成的项目:
而且是一个typescript版本的,也是省了很多配置的事情。
ok,我们动手写第一个页面,第一个页面普遍都是一个对话框页面,首先之后大概张这个样子:
这里当然就给得比较简单了,对话没有区分谁是谁说的,甚至markdown展示,以及复制/删除/暂停对话的能力我们统统没有实现,这目前还不是主要的,但是我们做了插入附件的功能,其代码如下:
import React, { useState } from "react";
import { View, FlatList } from "react-native";
import { ListItem, makeStyles } from "@rneui/themed";
import InputPanel from "../components/InputPanel"; // 确保正确导入 InputPanel 组件
const ChatScreen = () => {
const styles = useStyles();
const [messages, setMessages] = useState([
// 测试数据,实际开发中应从后端获取
{ id: "1", text: "Hello there!" },
{ id: "11", text: "give me more message" },
]);
type ItemType = (typeof messages)[0];
const renderItem = ({ item }: { item: ItemType }) => (
// Code inside the function
<ListItem bottomDivider>
<ListItem.Content>
<ListItem.Title>{item.text}</ListItem.Title>
</ListItem.Content>
</ListItem>
);
return (
<View style={styles.container}>
<FlatList
data={messages}
renderItem={renderItem}
keyExtractor={(item) => item.id}
style={styles.list}
/>
<InputPanel />
</View>
);
};
const useStyles = makeStyles((theme) => ({
container: {
flex: 1,
backgroundColor: theme.colors.background,
},
list: {
flex: 1,
},
}));
export default ChatScreen;
其中,InputPanel的代码如下:
import React, { useState } from "react";
import { View } from "react-native";
import { Input, Button, Icon, makeStyles, Text } from "@rneui/themed";
import Attachment from "./Attachment";
const InputPanel = () => {
const [text, setText] = useState("");
type AttachmentType = {
uri: string;
type: string;
};
// 构造几个 Attachment 数据
const [attachments, setAttachments] = useState<AttachmentType[]>([
{
uri: "<https://picsum.photos/200/400>",
type: "image",
},
{
uri: "<https://picsum.photos/200/500>",
type: "file",
},
]);
const styles = useStyles();
const handleSend = () => {
// 发送文本逻辑
console.log("Sending Text: ", text);
setText("");
};
const handleAttach = () => {
// 附件添加逻辑
console.log("Attach file");
};
return (
<View style={styles.container}>
<View style={styles.attachments}>
{attachments.map((item) => (
<Attachment
key={item.uri}
uri={item.uri}
type={item.type}
onRemove={(uri) =>
setAttachments(attachments.filter((item) => item.uri !== uri))
}
/>
))}
</View>
<Input
placeholder="Type a message..."
value={text}
multiline
onChangeText={setText}
inputContainerStyle={{
borderBottomWidth: 0,
}}
errorStyle={{
display: "none",
}}
leftIcon={
<Icon name="attachment" type="material" onPress={handleAttach} />
}
rightIcon={
<Icon
name="send"
type="material"
iconStyle={{ alignSelf: "baseline" }}
onPress={handleSend}
/>
}
/>
</View>
);
};
const useStyles = makeStyles((theme) => ({
container: {
flexDirection: "column",
alignItems: "center",
backgroundColor: theme.colors.background,
borderWidth: 1,
borderColor: theme.colors.greyOutline,
borderRadius: 5,
margin: theme.spacing.md,
},
attachments: {
flexDirection: "row",
width: "100%",
justifyContent: "flex-start",
flexWrap: "wrap",
},
}));
export default InputPanel;
以上就是本次对于react native 实现一个与ChatGPT这种大模型对话应用的UI部分,接下来就是逻辑部分了。
思考一按,我恩要在对话框中问一个问题,然后请求模型得到响应,我们可能需要写一个模型请求的封装:
import useSettingsStore from "../store/settingsStore";
export const fetchOpenAiCompletion = async (message: string) => {
const baseURL = useSettingsStore.getState().baseURL;
const apiKey = useSettingsStore.getState().apiKey;
console.log("fetchOpenAiCompletion", message);
console.log("baseURL", baseURL, "apiKey", apiKey);
const response = await fetch(`${baseURL}/v1/chat/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: message }],
temperature: 0.7,
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
};
这里,我们请求模型是需要配置一下 apikey 和 baseUrl 的,因此我们最好搞一个设置页面来配置一下这些参数,然后配合全局状态缓存,来存储这些设置。
import AsyncStorage from "@react-native-async-storage/async-storage";
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
const useSettingsStore = create(persist(
(set) => ({
apiKey: '',
baseURL: '',
themeMode: '',
setApiKey: (apiKey) => set({ apiKey }),
setBaseURL: (baseURL) => set({ baseURL }),
setThemeMode: (themeMode) => set({ themeMode }),
}),
{
name: 'settings-storage', // unique name
storage: createJSONStorage(() => AsyncStorage),
}
));
export default useSettingsStore;
ok,一切实际上准备的七七八八了,当然还有会话列表的功能,我们可以这么处理:
import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";
import { Session, Message } from "./sessionTypes";
import AsyncStorage from "@react-native-async-storage/async-storage";
interface SessionState {
sessions: Session[];
createSession: (session: Session) => void;
addMessageToSession: (sessionId: string, message: Message) => void;
}
const useSessionStore = create<SessionState, any>(
persist(
(set) => ({
sessions: [],
createSession: (session: Session) =>
set((state) => ({ sessions: [...state.sessions, session] })),
addMessageToSession: (sessionId: string, message: Message) =>
set((state) => ({
sessions: state.sessions.map((session) =>
session.id === sessionId
? { ...session, messages: [...session.messages, message] }
: session
),
})),
}),
{
name: "session-storage",
storage: createJSONStorage(() => AsyncStorage),
}
)
);
export default useSessionStore;
而且,还实现了缓存。
测试一下,我们的模型是否打通,ok,看起来问题不大。所以,整个 react-native 的初步阶段就算是完结了,当然这个App 还需要大量的打磨,才可以拿出来用,如果有需要,可以私信我加入,一起搞事。
本文探索了一下 react-native 的开发,使用 expo 直接进行开发,这样,我们不需要太多的环境配置就可以上手,注意最新的 expo ,即 50 版本可以直接文件路由的方式,这意味着熟悉 next.js 这套的人可以轻松上手 react-native ,几乎无任何门槛。
在 UI 方面,我选择了 react-native-element ,这个让我们不用担心界面太丑
在全局状态上,我们选择了 zustand,他相对 redux 会简单很多,配合中间件,结合 async storeage,可以非常方便实现一个代缓存的全局状态。
在与服务端数据通讯方面,我们使用 tanstack query ,未我们省下了相当多的麻烦状态维护的麻烦。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。