Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 3 additions & 46 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,9 @@
import "./App.css";
import Messages from "./components/Messages";
import ChatPage from "./pages/ChatPage";
import AdminPage from "./pages/admin";
import Scenario from "../src/pages/Scenarios";
import ScenarioForm from "./pages/admin/ScenarioForm";
import { BrowserRouter, Routes, Route } from "react-router";

import Router from "./Router";

function App() {
return (
<BrowserRouter>
<Routes>
<Route
path="/"
element={
<div className="flex flex-col h-screen">
<Scenario />
</div>
}
/>
<Route
path="/game"
element={
<div className="flex flex-col h-screen">
<ChatPage />
</div>
}
>
<Route path="/game/:sid" element={<Messages />} />
</Route>
<Route
path="/admin"
element={
<div className="flex flex-col h-screen">
<AdminPage />
</div>
}
/>
<Route
path="/admin/:id"
element={
<div className="flex flex-col h-screen">
<ScenarioForm />
</div>
}
/>
</Routes>
</BrowserRouter>
);
return <Router />;
}

export default App;
22 changes: 22 additions & 0 deletions src/Router.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { BrowserRouter, Routes, Route } from "react-router";
import Scenarios from "./pages/Scenarios";
import ChatPage from "./pages/ChatPage";
// import Messages from "./components/Messages";
import AdminPage from "./pages/admin";
import ScenarioForm from "./pages/admin/ScenarioForm";

export default function Router() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Scenarios />} />
<Route path="/game">
<Route path="/game/:sid" element={<ChatPage />} />
</Route>

<Route path="/admin" element={<AdminPage />} />
<Route path="/admin/:id" element={<ScenarioForm />} />
</Routes>
</BrowserRouter>
);
}
12 changes: 12 additions & 0 deletions src/components/GameOver.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
/* eslint-disable react/prop-types */
import { useState, useEffect } from "react";
import { Link } from "react-router";
import { useStore } from "../store/store";

export default function GameOver({ score, onRestart }) {
const [visible, setVisible] = useState(false);
const { messages, resetGame } = useStore();

console.log(messages);

useEffect(() => {
setTimeout(() => setVisible(true), 500);
Expand All @@ -20,6 +24,13 @@ export default function GameOver({ score, onRestart }) {
Your Score: <span className="font-bold text-yellow-400">{score}</span>
</p>

<p className="text-2xl mt-4">
The last word is:
<span className="font-bold text-yellow-400">
{messages[messages.length - 1].content}
</span>
</p>

<div className="mt-6 flex gap-4">
<button
className="bg-blue-500 hover:bg-blue-700 text-white px-6 py-3 rounded-xl text-lg"
Expand All @@ -30,6 +41,7 @@ export default function GameOver({ score, onRestart }) {
<Link
className="bg-gray-700 hover:bg-gray-500 text-white px-6 py-3 rounded-xl text-lg"
to="/"
onClick={resetGame}
>
Exit
</Link>
Expand Down
11 changes: 5 additions & 6 deletions src/components/Messages.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { useStore } from "../store/store";
import { useEffect, useRef} from "react";
import { useEffect, useRef } from "react";

export default function Messages() {
const { messages, scoreHistory } = useStore();


let assistantIndex = -1;
const messagesEndRef = useRef(null);
Expand All @@ -13,7 +12,7 @@ export default function Messages() {
}, [messages]);

return (
<div className="flex-1 overflow-y-auto flex flex-col pb-4">
<div className="overflow-y-auto flex flex-col pb-4">
{messages.map((message, index) => {
if (message.role === "assistant") {
assistantIndex++; // 只在 assistant 消息时递增
Expand All @@ -40,9 +39,9 @@ export default function Messages() {
: "chat-bubble-primary"
}`}
style={{
wordBreak: 'break-word',
overflowWrap: 'break-word',
hyphens: 'auto'
wordBreak: "break-word",
overflowWrap: "break-word",
hyphens: "auto",
}}
>
<div className="flex-1 overflow-hidden text-sm sm:text-base">
Expand Down
10 changes: 10 additions & 0 deletions src/components/Win.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/* eslint-disable react/prop-types */
import { useState, useEffect } from "react";
import { Link } from "react-router";
import { useStore } from "../store/store";

export default function Win({ score, onRestart }) {
const [visible, setVisible] = useState(false);
const { messages, resetGame } = useStore();

useEffect(() => {
setTimeout(() => setVisible(true), 500);
Expand All @@ -22,6 +24,13 @@ export default function Win({ score, onRestart }) {
Your Score: <span className="font-bold text-green-300">{score}</span>
</p>

<p className="text-2xl mt-4">
The last word is:
<span className="font-bold text-green-300">
{messages[messages.length - 1].content}
</span>
</p>

<div className="mt-6 flex gap-4">
<button
className="bg-blue-500 hover:bg-blue-700 text-white px-6 py-3 rounded-xl text-lg"
Expand All @@ -33,6 +42,7 @@ export default function Win({ score, onRestart }) {
<Link
to="/"
className="bg-gray-700 hover:bg-gray-500 text-white px-6 py-3 rounded-xl text-lg"
onClick={resetGame}
>
Exit
</Link>
Expand Down
36 changes: 21 additions & 15 deletions src/pages/ChatPage.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useStore } from "../store/store";
import { useEffect, useState } from "react";
import { useParams } from "react-router";
import { Link } from "react-router";
import { useParams, useNavigate, Link } from "react-router";
import Messages from "../components/Messages";
import GameOver from "../components/GameOver";
import Win from "../components/Win";
Expand All @@ -15,6 +14,7 @@ export default function ChatPage() {
score,
fetchScenario,
scoreHistory,
resetGame,
} = useStore();
const params = useParams();
const currentId = parseInt(params.sid);
Expand Down Expand Up @@ -56,35 +56,40 @@ export default function ChatPage() {
}

function onRestart() {
resetGame();
setIsWin(null);
fetchScenario(currentId);
}

return (
<div className="flex-1 flex flex-col min-h-screen bg-gray-50">
<div className="flex flex-col h-dvh bg-gray-50">
<Description />
<nav className="sticky top-0 bg-white shadow-sm py-4 px-6 w-full z-50">
<nav className="bg-white shadow-sm py-4 px-6 w-full z-50">
<div className="max-w-4xl mx-auto flex items-center justify-between">
<h1 className="text-xl font-bold text-gray-800">
MTLove Score: <span className="text-blue-600">{score}</span>
</h1>
<button
className="btn"
onClick={() => document.getElementById("my_modal_2").showModal()}
>
Challenge Description
</button>
<Link to="/">
<button className="btn btn-neutral">Home</button>
</Link>

<div className="flex justify-end gap-2">
<button
className="btn"
onClick={() => document.getElementById("my_modal_2").showModal()}
>
Challenge Description
</button>

<Link to="/" className="btn btn-neutral" onClick={resetGame}>
Home
</Link>
</div>
</div>
</nav>

<div className="flex-1 w-full max-w-4xl mx-auto px-4 overflow-y-auto">
<div className="flex-1 w-full max-w-4xl mx-auto pb-4 overflow-y-auto">
<Messages />
</div>

<div className="sticky bottom-0 bg-white border-t border-gray-200 py-4 z-10">
<div className="bg-white border-t border-gray-200 py-4 z-10">
<div className="max-w-4xl mx-auto px-4 flex gap-3">
<textarea
placeholder="Type your message here..."
Expand All @@ -104,3 +109,4 @@ export default function ChatPage() {
</div>
);
}
// pt-28 md:p-0
58 changes: 33 additions & 25 deletions src/store/store.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { fetchDb } from './fetch';
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { fetchDb } from "./fetch";

const API_KEY = import.meta.env.VITE_API_KEY;

export const useStore = create(
persist(
(set, get) => ({
resetGame: () =>
set({
score: 0,
scoreHistory: [0],
userInput: "",
messages: [],
}),

// 获取当前的 scenario
fetchScenario: async (sid) => {
try {
Expand All @@ -16,22 +24,22 @@ export const useStore = create(
score: 0,
systemPrompt: scenarioData.system,
messages: [
{ role: 'system', content: scenarioData.system },
{ role: 'assistant', content: scenarioData.start },
{ role: "system", content: scenarioData.system },
{ role: "assistant", content: scenarioData.start },
],
});
} catch (error) {
console.error('Failed to fetch scenario:', error);
console.error("Failed to fetch scenario:", error);
}
},

userInput: '',
userInput: "",
setUserInput: (input) => set({ userInput: input }),

token: '',
token: "",
setToken: (newToken) => set({ token: newToken }),

systemPrompt: '',
systemPrompt: "",
setSystemPrompt: (prompt) => set({ systemPrompt: prompt }),

messages: [],
Expand Down Expand Up @@ -59,31 +67,31 @@ export const useStore = create(
} = get();
if (!userInput) return;

addMessage({ role: 'user', content: userInput }); // 添加用户对话
setUserInput(''); // 清空输入框
addMessage({ role: "user", content: userInput }); // 添加用户对话
setUserInput(""); // 清空输入框

// console.log(api);

const requestBody = {
model: 'gemini-2.0-flash-exp',
messages: [...messages, { role: 'user', content: userInput }],
model: "gemini-2.0-flash-exp",
messages: [...messages, { role: "user", content: userInput }],
response_format: {
type: 'json_schema',
type: "json_schema",
json_schema: {
name: 'compliance_result',
name: "compliance_result",
schema: {
type: 'object',
type: "object",
properties: {
text: {
type: 'string',
description: 'Response text.',
type: "string",
description: "Response text.",
},
score: {
type: 'number',
description: 'Score of the response.',
type: "number",
description: "Score of the response.",
},
},
required: ['text', 'score'],
required: ["text", "score"],
additionalProperties: false,
},
strict: true,
Expand All @@ -96,9 +104,9 @@ export const useStore = create(
const response = await fetch(
`https://generativelanguage.googleapis.com/v1beta/openai/chat/completions`,
{
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify(requestBody),
Expand All @@ -114,9 +122,9 @@ export const useStore = create(
const curScore = jsonObj.score;
setScore(curScore);
setScoreHistory(curScore);
addMessage({ role: 'assistant', content: jsonObj.text });
addMessage({ role: "assistant", content: jsonObj.text });
},
}),
{ name: 'mtlove' }
{ name: "mtlove" }
)
);
9 changes: 6 additions & 3 deletions vite.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})
// server: {
// host: true,
// },
});