first commit
This commit is contained in:
1
.env
Normal file
1
.env
Normal file
@@ -0,0 +1 @@
|
||||
REACT_APP_DEEPSEEK_API_KEY=sk-34616a36a2cf46d3ba7c1f54828b514a
|
||||
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
70
README.md
Normal file
70
README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||
|
||||
The page will reload when you make changes.\
|
||||
You may also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
||||
|
||||
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
||||
|
||||
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `npm run build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
||||
19115
package-lock.json
generated
Normal file
19115
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
package.json
Normal file
47
package.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "new-app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@heroui/react": "^2.8.7",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.1",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"framer-motion": "^12.23.26",
|
||||
"openai": "^6.15.0",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.19",
|
||||
"postcss": "^8.4.38",
|
||||
"tailwindcss": "^3.4.1"
|
||||
}
|
||||
}
|
||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
38
public/index.html
Normal file
38
public/index.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
181
src/App.js
Normal file
181
src/App.js
Normal file
@@ -0,0 +1,181 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
Divider,
|
||||
ScrollShadow,
|
||||
Spinner,
|
||||
Textarea,
|
||||
} from "@heroui/react";
|
||||
import { requestBotReply } from "./dsAPI";
|
||||
|
||||
export default function App() {
|
||||
const [messages, setMessages] = useState([
|
||||
{
|
||||
id: 0,
|
||||
role: "assistant",
|
||||
content: "你好,有什么可以帮你?",
|
||||
},
|
||||
]);
|
||||
|
||||
const [inputText, setInputText] = useState("");
|
||||
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const [isBotTyping, setIsBotTyping] = useState(false);
|
||||
|
||||
const msgIdRef = useRef(1);
|
||||
const bottomRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
}, [messages, isBotTyping]);
|
||||
|
||||
async function handleSend() {
|
||||
const text = inputText.trim();
|
||||
if (!text || isSending) return;
|
||||
|
||||
const userMessage = {
|
||||
id: msgIdRef.current++,
|
||||
role: "user",
|
||||
content: text,
|
||||
};
|
||||
|
||||
setMessages((prev) => [...prev, userMessage]);
|
||||
setInputText("");
|
||||
setIsSending(true);
|
||||
setIsBotTyping(true);
|
||||
|
||||
// 给模型的上下文
|
||||
const historyForModel = [
|
||||
{ role: "system", content: "你是一个简洁的助手,用中文回答。" },
|
||||
...messages.map(({ role, content }) => ({ role, content })),
|
||||
{ role: "user", content: text },
|
||||
];
|
||||
|
||||
let reply = "";
|
||||
try {
|
||||
reply = await requestBotReply(historyForModel);
|
||||
} catch (e) {
|
||||
reply = "出错了,请稍后再试。";
|
||||
}
|
||||
|
||||
const botMessage = {
|
||||
id: msgIdRef.current++,
|
||||
role: "assistant",
|
||||
content: reply,
|
||||
};
|
||||
|
||||
setMessages((prev) => [...prev, botMessage]);
|
||||
setIsBotTyping(false);
|
||||
setIsSending(false);
|
||||
}
|
||||
|
||||
function handleKeyDown(e) {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSend();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
// min-h-screen 最小高度 = 整个屏幕高度
|
||||
// bg-gradient-to-b 背景是 从上到下的渐变
|
||||
// from-background 渐变起点颜色
|
||||
// to-content2/50 渐变终点颜色 /50 = 50% 透明度
|
||||
// p-4 控制四周内距
|
||||
<div className="min-h-screen bg-gradient-to-b from-background to-content2/50 p-4">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<Card className="shadow-xl">
|
||||
{/* 顶部 */}
|
||||
<CardHeader className="flex items-center gap-3">
|
||||
<Avatar name="Bot" />
|
||||
<div className="flex flex-col">
|
||||
<span className="font-semibold">Chatbot Demo</span>
|
||||
<span className="text-xs text-default-500">
|
||||
</span>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* 聊天区域 */}
|
||||
<CardBody className="p-0">
|
||||
<ScrollShadow className="h-[520px] px-4 py-4">
|
||||
<div className="flex flex-col gap-3">
|
||||
{messages.map((m) => {
|
||||
const isUser = m.role === "user";
|
||||
return (
|
||||
<div
|
||||
key={m.id}
|
||||
className={`flex ${isUser ? "justify-end" : "justify-start"}`}
|
||||
>
|
||||
<div
|
||||
className={`flex max-w-[80%] items-end gap-2 ${isUser ? "flex-row-reverse" : ""}`}
|
||||
>
|
||||
<Avatar
|
||||
size="sm"
|
||||
name={isUser ? "You" : "Bot"}
|
||||
/>
|
||||
<div
|
||||
className={`rounded-2xl px-4 py-3 text-sm leading-relaxed ${isUser
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-content2 text-foreground"
|
||||
}`}
|
||||
>
|
||||
{m.content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{isBotTyping && (
|
||||
<div className="flex justify-start">
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar size="sm" name="Bot" />
|
||||
<div className="flex items-center gap-2 rounded-2xl bg-content2 px-4 py-2 text-sm text-default-600">
|
||||
<Spinner size="sm" />
|
||||
正在回复…
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={bottomRef} />
|
||||
</div>
|
||||
</ScrollShadow>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* 输入区 */}
|
||||
<div className="p-4">
|
||||
<div className="flex items-end gap-3">
|
||||
<Textarea
|
||||
value={inputText}
|
||||
onValueChange={setInputText}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="输入消息,Enter 发送,Shift+Enter 换行"
|
||||
minRows={2}
|
||||
maxRows={6}
|
||||
isDisabled={isSending}
|
||||
variant="bordered"
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button
|
||||
color="primary"
|
||||
onPress={handleSend}
|
||||
isDisabled={isSending || !inputText.trim()}
|
||||
>
|
||||
{isSending ? "发送中…" : "发送"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
src/dsAPI.js
Normal file
21
src/dsAPI.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import OpenAI from "openai";
|
||||
|
||||
const openai = new OpenAI({
|
||||
baseURL: 'https://api.deepseek.com',
|
||||
apiKey: process.env.REACT_APP_DEEPSEEK_API_KEY,
|
||||
dangerouslyAllowBrowser: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* 向大模型请求回复
|
||||
* @param {Array} messages - 聊天历史 [{ role, content }]
|
||||
* @returns {Promise<string>} assistant 回复文本
|
||||
*/
|
||||
export async function requestBotReply(messages) {
|
||||
const completion = await openai.chat.completions.create({
|
||||
model: "deepseek-chat",
|
||||
messages,
|
||||
});
|
||||
|
||||
return completion.choices[0].message.content;
|
||||
}
|
||||
19
src/index.css
Normal file
19
src/index.css
Normal file
@@ -0,0 +1,19 @@
|
||||
/* src/index.css 才是 CSS 文件,放 Tailwind 指令 */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* 保留 React 默认的全局样式 */
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
16
src/index.js
Normal file
16
src/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
|
||||
import { HeroUIProvider } from "@heroui/react";
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<HeroUIProvider>
|
||||
<App />
|
||||
</HeroUIProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
14
tailwind.config.js
Normal file
14
tailwind.config.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
const { heroui } = require("@heroui/react");
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,jsx,ts,tsx,html,css}",
|
||||
"./node_modules/@heroui/react/**/*.{js,jsx,ts,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [heroui()],
|
||||
};
|
||||
Reference in New Issue
Block a user