first commit

This commit is contained in:
2025-12-29 16:55:59 +08:00
commit 12e699ccb8
13 changed files with 19551 additions and 0 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
REACT_APP_DEEPSEEK_API_KEY=sk-34616a36a2cf46d3ba7c1f54828b514a

23
.gitignore vendored Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

47
package.json Normal file
View 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
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

38
public/index.html Normal file
View 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
View 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
View 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
View 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
View 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
View 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()],
};