Skip to content

Commit

Permalink
feat: 小玩法,汉语新解
Browse files Browse the repository at this point in the history
  • Loading branch information
KwokKwok committed Sep 12, 2024
1 parent f19f3dc commit d039db2
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 17 deletions.
24 changes: 15 additions & 9 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,29 @@ import {
Routes,
Route,
Navigate,
BrowserRouter as Router,
HashRouter as Router,
} from 'react-router-dom';
import ImageGenerate from './pages/image-generate';
import { useIsImageMode } from './store/storage';
import HumorExplainer from './pages/humor-explainer';

function App() {
function App () {
const [isImageMode] = useIsImageMode();
return (
<div className="h-dvh w-full flex flex-col selection:bg-primary selection:text-white pb-2 text-sm">
<Router>
<HeaderAndPopup />
{isImageMode ? <ImageGenerate /> : <Chat />}
{/* <Routes>
<Route path="/" element={<Navigate to="/chat" replace />} />
<Route path="/chat" element={<Chat />} />
<Route path="/image" element={<ImageGenerate />} />
</Routes> */}
<Routes>
<Route
path="/"
element={
<>
<HeaderAndPopup />
{isImageMode ? <ImageGenerate /> : <Chat />}
</>
}
/>
<Route path="/humor-explainer" element={<HumorExplainer />} />
</Routes>
</Router>
</div>
);
Expand Down
107 changes: 107 additions & 0 deletions src/pages/humor-explainer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { useLastGptResponse } from '@src/utils/chat';
import { useState } from 'react';
import { useEffect } from 'react';
import { useSiloChat } from '@src/utils/chat';
import HumorExplainer from '@src/services/prompt/humor-explainer.txt?raw';
import InputControl from '@src/components/InputControl';

export default function () {
const { loading, onSubmit, onStop } = useSiloChat(`${HumorExplainer}`);

const [word, setWord] = useState('周四');

useEffect(() => {
onStop(true);
if (word) {
setTimeout(() => {
onSubmit(word);
}, 16);
}
}, [word]);

const modelResponses = useLastGptResponse();
const [activeIndex, setActiveIndex] = useState(0);
const optionLength = modelResponses.length;
const handleKeyDown = event => {
setActiveIndex(prev => {
let target = prev;
if (event.key === 'ArrowLeft') {
target--;
} else if (event.key === 'ArrowRight') {
target++;
}
target = Math.max(0, Math.min(target, optionLength - 1));
return target;
});
};

useEffect(() => {
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [setActiveIndex, optionLength]);

const content = modelResponses[activeIndex].message;
console.log(content);
const formattedContent = content.slice(
content.indexOf('<'),
content.lastIndexOf('>')
);

if (!modelResponses.length) return null;
return (
<div className="flex-1 py-4 flex flex-col h-full w-full pb-[8px]">
<div className="flex-1 h-0 overflow-auto pb-4 relative text-sm leading-6 flex items-center justify-center">
{modelResponses[activeIndex] && (
<div
className="max-w-96 mx-auto rounded overflow-hidden text-center"
dangerouslySetInnerHTML={{
__html: formattedContent || content,
}}
></div>
)}
</div>

<div className=" mt-[8px] flex-shrink-0 px-4 items-center flex">
<div className=" flex items-center relative flex-shrink-0 ">
<div
style={{
transform: `translateX(${activeIndex * (32 + 8)}px)`,
}}
className="absolute left-0 top-0 h-[32px] w-[32px] transform transition-transform duration-300 opacity-75 outline-primary outline outline-[2px] rounded-[4px]"
></div>
{modelResponses.map((response, index) => (
<div
key={index}
className={`cursor-pointer mr-[8px] p-[4px] transition-transform duration-300 select-none ${
activeIndex === index
? ' overflow-hidden shadow-lg scale-105'
: 'scale-100'
}`}
onClick={() => setActiveIndex(index)}
>
<img
src={response.icon}
alt={response.model}
className={
'w-[24px] h-[24px] rounded-[4px] ' +
(response.loading ? 'animate-pulse' : '')
}
/>
</div>
))}
</div>
<div className="flex-1 relative flex-shrink-0 ml-2">
<InputControl
placeholder="换个词吧"
enter
onStop={onStop}
onSubmit={setWord}
loading={loading}
/>
</div>
</div>
</div>
);
}
42 changes: 42 additions & 0 deletions src/services/prompt/humor-explainer.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 角色:
你是新汉语老师,你年轻,批判现实,思考深刻,语言风趣"。你的行文风格和"Oscar Wilde" "鲁迅" "林语堂"等大师高度一致,你擅长一针见血的表达隐喻,你对现实的批判讽刺幽默。

- 作者:云中江树,李继刚

## 任务:
用户将给你一个词汇,请将其进行全新角度的解释,你会用一个特殊视角来解释它:
用一句话表达你的词汇解释,抓住用户输入词汇的本质,使用辛辣的讽刺、一针见血的指出本质,使用包含隐喻的金句。
例如:“委婉”: "刺向他人时, 决定在剑刃上撒上止痛药。"

## 输出结果:

1. <strong>请直接输出词语卡片的Html 代码,我将直接渲染它</strong>
2. 不需要额外的解释,不要以任何标记语言包裹,包括 markdown

- 整体设计合理使用留白,整体排版要有呼吸感
- 设计原则:干净 简洁 纯色 典雅
- 配色:下面的色系中随机选择一个[
"柔和粉彩系",
"深邃宝石系",
"清新自然系",
"高雅灰度系",
"复古怀旧系",
"明亮活力系",
"冷淡极简系",
"海洋湖泊系",
"秋季丰收系",
"莫兰迪色系"
]
- 卡片样式:
(字体 . ("KaiTi, SimKai" "Arial, sans-serif"))
(颜色 . ((背景 "#FAFAFA") (标题 "#333") (副标题 "#555") (正文 "#333")))
(尺寸 . ((卡片宽度 "auto") (卡片高度 "auto, >宽度") (内边距 "20px")))
(布局 . (竖版 弹性布局 居中对齐))))
- 卡片元素:
(标题 "汉语新解")
(分隔线)
(词语 用户输入)
(拼音)
(英文翻译)
(日文翻译)
(解释:(按现代诗排版))
39 changes: 32 additions & 7 deletions src/utils/chat.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useEffect } from "react";
import { useActiveModels } from "../store/app";
import { getModelIcon } from "./models";
import { ERROR_PREFIX } from "./types";
import { useRefresh } from "./use";
import { useMultiRows, useRefresh } from "./use";
import { streamChat } from "./utils";

/**
Expand All @@ -25,7 +27,7 @@ export function getUserMessages () {
}


function _streamChat (chat, newMessage) {
function _streamChat (chat, newMessage, systemPrompt) {
const { chatId, content } = newMessage;
chat.controller.current = new AbortController();
chat.loading = true;
Expand All @@ -50,6 +52,9 @@ function _streamChat (chat, newMessage) {
return [...arr, { role: 'user', content: userMessage }, { role: 'assistant', content: aiMessage }].filter(item => item.role != 'assistant' || !item.content.startsWith(ERROR_PREFIX))
}, [])
chatMessage.push({ role: 'user', content })
if (systemPrompt) {
chatMessage.unshift({ role: 'system', content: systemPrompt })
}

// 可以先展示用户消息
chat.messages[chatId] = ''
Expand All @@ -74,6 +79,23 @@ export function useActiveChatsMessages () {
return messages
}

/**
* 获取最后的响应,用于划词解释
*/
export function useLastGptResponse () {
const [rows] = useMultiRows();
const sortedActiveModels = (rows[0] || []).flatMap((item, index) =>
rows[1]?.[index] ? [item, rows[1][index]] : [item]
);
const chatId = Object.keys(userMessages)[0];
return sortedActiveModels.map(model => ({
model,
message: allChats[model]?.messages[chatId] || '',
loading: allChats[model]?.loading || false,
icon: getModelIcon(model),
}))
}

/**
* 获取用户与模型完整的消息交互(用户消息和模型消息是分开存放的)
* @param {string} model 模型 id
Expand All @@ -94,7 +116,7 @@ export function useSingleChat (model) {
return allChats[model] || {};
}

export function useSiloChat () {
export function useSiloChat (systemPrompt) {
const { activeModels } = useActiveModels();
const refresh = useRefresh(48);
const activeChats = activeModels.map(item => {
Expand All @@ -120,14 +142,17 @@ export function useSiloChat () {
return allChats[item];
})
const loading = activeChats.some(chat => chat.loading);
if (!loading) {
refresh.stop();
}
useEffect(() => {
if (!loading) {
refresh.refresh();
refresh.stop();
}
}, [loading])
const onSubmit = (message) => {
refresh.start();
const newMessage = _addUserMessage(message);
activeChats.forEach(chat => {
_streamChat(chat, newMessage);
_streamChat(chat, newMessage, systemPrompt);
})
}
const onStop = (clear) => {
Expand Down
2 changes: 1 addition & 1 deletion vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': '/src'
'@src': '/src'
}
}
})

0 comments on commit d039db2

Please sign in to comment.