main
commit
1415a18b5c
18 changed files with 6242 additions and 0 deletions
@ -0,0 +1,2 @@ |
|||||||
|
node_modules |
||||||
|
*.env |
||||||
@ -0,0 +1,87 @@ |
|||||||
|
import OpenAI from "openai"; |
||||||
|
import express from "express"; |
||||||
|
import cors from "cors"; |
||||||
|
import { system_prompt } from "./system_prompt.js"; |
||||||
|
|
||||||
|
import { z } from "zod"; |
||||||
|
import { zodResponseFormat } from "openai/helpers/zod"; |
||||||
|
|
||||||
|
import { config } from "dotenv"; |
||||||
|
config(); // Load environment variables from .env file
|
||||||
|
|
||||||
|
|
||||||
|
const Output = { |
||||||
|
"type": "object", |
||||||
|
"properties": { |
||||||
|
"prompt": { |
||||||
|
"type": "string", |
||||||
|
"description": "The generated image prompt based on the user's input and the system's guidance.", |
||||||
|
}, |
||||||
|
"output_text": { |
||||||
|
"type": "string", |
||||||
|
"description": "The final output text generated by the model, without image prompt", |
||||||
|
} |
||||||
|
}, |
||||||
|
"additionalProperties": false, |
||||||
|
"required": [ |
||||||
|
"prompt", "output_text" |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
const client = new OpenAI({ |
||||||
|
apiKey: process.env.OPENAI_API_KEY,
|
||||||
|
}); |
||||||
|
|
||||||
|
// const response = await client.responses.create({
|
||||||
|
// model: "gpt-4.1",
|
||||||
|
// input: "Write a one-sentence bedtime story about a unicorn."
|
||||||
|
// });
|
||||||
|
|
||||||
|
// console.log(response.output_text);
|
||||||
|
|
||||||
|
|
||||||
|
const app = express(); |
||||||
|
const port = process.env.PORT || 3000; |
||||||
|
app.use(express.json()); |
||||||
|
app.use(cors()); |
||||||
|
|
||||||
|
app.post("/generate", async (req, res) => { |
||||||
|
const { input } = req.body; |
||||||
|
|
||||||
|
try { |
||||||
|
const response = await client.responses.create({ |
||||||
|
model: "gpt-4.1", |
||||||
|
input: [ |
||||||
|
{ |
||||||
|
role: "system", |
||||||
|
content: [ |
||||||
|
{ |
||||||
|
type:'input_text', |
||||||
|
text: system_prompt, |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
...input |
||||||
|
], |
||||||
|
text:{ |
||||||
|
format:{ |
||||||
|
type:'json_schema', |
||||||
|
name:"output_prompt", |
||||||
|
schema: Output, |
||||||
|
} |
||||||
|
}
|
||||||
|
}); |
||||||
|
|
||||||
|
console.log("Generated response:", response); |
||||||
|
|
||||||
|
res.json(JSON.parse(response.output_text)); |
||||||
|
} catch (error) { |
||||||
|
console.error("Error generating response:", error); |
||||||
|
res.status(500).json({ error: "Failed to generate response" }); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
app.listen(port, () => { |
||||||
|
console.log(`Server is running on http://localhost:${port}`); |
||||||
|
}); |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,23 @@ |
|||||||
|
{ |
||||||
|
"name": "source-web-24070-thegreattipsy", |
||||||
|
"version": "1.0.0", |
||||||
|
"description": "", |
||||||
|
"main": "index.js", |
||||||
|
"type": "module", |
||||||
|
"scripts": { |
||||||
|
"test": "echo \"Error: no test specified\" && exit 1" |
||||||
|
}, |
||||||
|
"repository": { |
||||||
|
"type": "git", |
||||||
|
"url": "https://git.ultracombos.com/24070-TheGreatTipsy/Source-Web-24070-TheGreatTipsy.git" |
||||||
|
}, |
||||||
|
"author": "", |
||||||
|
"license": "ISC", |
||||||
|
"dependencies": { |
||||||
|
"cors": "^2.8.5", |
||||||
|
"dotenv": "^16.5.0", |
||||||
|
"express": "^5.1.0", |
||||||
|
"openai": "^5.1.0", |
||||||
|
"zod": "^3.25.50" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,59 @@ |
|||||||
|
export const system_prompt = `你是一位具有同理心的 AI 助理,透過溫柔的中文對話,引導使用者回想並表達一段內心的遺憾或未竟之事。
|
||||||
|
你的任務是協助使用者逐步揭開這段記憶的情緒層次,並在每一階段輸出一句 英文圖像生成 Prompt,讓這段過往漸漸具象為一幅畫面。 |
||||||
|
|
||||||
|
📐 五個 Prompt 階段: |
||||||
|
純粹抽象:聚焦在使用者的情緒感受(如:空虛、靜止、壓抑) |
||||||
|
|
||||||
|
模糊意象:引入模糊場景、氣氛或人際暗示 |
||||||
|
|
||||||
|
未發生的畫面:勾勒「當時可能會發生的情景」 |
||||||
|
|
||||||
|
象徵性行動:加入口白、動作、遺憾的表徵 |
||||||
|
|
||||||
|
具體記憶畫面:描繪清楚、富有情感的視覺記憶場景 |
||||||
|
|
||||||
|
🎨 每段 Prompt 輸出格式: |
||||||
|
每次使用者回答後,你都要用英文輸出一句簡短的 圖像生成 Prompt(1~2 句),要能反映該階段的情緒與畫面感 |
||||||
|
|
||||||
|
每句 Prompt 要疊加前一層內容,逐步變得更具象 |
||||||
|
|
||||||
|
不主動使用人名或地名,除非使用者自己提到 |
||||||
|
|
||||||
|
保持詩意、意象化,避免寫實或指令式語言 |
||||||
|
|
||||||
|
🌱 第五段後的收尾流程: |
||||||
|
完成第五段 Prompt 後,請引導使用者對這段記憶進行情緒整理。你可以用以下中文問題其中之一,讓他/她重新理解這段遺憾,甚至願意釋懷: |
||||||
|
|
||||||
|
「如果可以回到那一刻,你想說什麼?對誰說?」 |
||||||
|
|
||||||
|
「這段記憶,現在看起來有不同的感覺了嗎?」 |
||||||
|
|
||||||
|
「你願意讓這段遺憾,安靜地待在心裡的某個角落嗎?」 |
||||||
|
|
||||||
|
「如果這是一封信,你現在想讓它被誰讀到?」 |
||||||
|
|
||||||
|
💬 最終請以一句繁體中文的結尾語,溫柔地結束這段對話。結尾語要具詩意、安撫性,以下為風格範例: |
||||||
|
「也許那件事從未發生,但它早已成為你故事的一部分。」 |
||||||
|
|
||||||
|
「有些話雖沒說出口,卻一直被你記得。」 |
||||||
|
|
||||||
|
「當時沒能完成的,也許現在能被理解。」 |
||||||
|
|
||||||
|
「你願意,就讓這段記憶,在心裡找到一個柔軟的位置。」 |
||||||
|
|
||||||
|
✅ 示意流程範例: |
||||||
|
使用者回答(中文):我後來沒參加畢旅,因為媽媽住院,我想留下來陪她。 |
||||||
|
|
||||||
|
Prompt 1(英文):“A still space, filled with silent longing.” |
||||||
|
|
||||||
|
Prompt 2:“The air carries warmth and weight, like quiet devotion.” |
||||||
|
|
||||||
|
Prompt 3:“Somewhere far, waves and laughter shimmer in the distance.” |
||||||
|
|
||||||
|
Prompt 4:“At the edge of sunset, a note is held but never passed.” |
||||||
|
|
||||||
|
Prompt 5:“A boy sits beside a sleeping figure, imagining the summer he never had.” |
||||||
|
|
||||||
|
中文引導:「如果當時的你能對媽媽說一句話,你會說什麼?」 |
||||||
|
|
||||||
|
結尾語(中文):「也許那個夏天沒來,但你用愛留住了它的模樣。」`;
|
||||||
@ -0,0 +1,24 @@ |
|||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
pnpm-debug.log* |
||||||
|
lerna-debug.log* |
||||||
|
|
||||||
|
node_modules |
||||||
|
dist |
||||||
|
dist-ssr |
||||||
|
*.local |
||||||
|
|
||||||
|
# Editor directories and files |
||||||
|
.vscode/* |
||||||
|
!.vscode/extensions.json |
||||||
|
.idea |
||||||
|
.DS_Store |
||||||
|
*.suo |
||||||
|
*.ntvs* |
||||||
|
*.njsproj |
||||||
|
*.sln |
||||||
|
*.sw? |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
# React + Vite |
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. |
||||||
|
|
||||||
|
Currently, two official plugins are available: |
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh |
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh |
||||||
|
|
||||||
|
## Expanding the ESLint configuration |
||||||
|
|
||||||
|
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
import js from '@eslint/js' |
||||||
|
import globals from 'globals' |
||||||
|
import reactHooks from 'eslint-plugin-react-hooks' |
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh' |
||||||
|
|
||||||
|
export default [ |
||||||
|
{ ignores: ['dist'] }, |
||||||
|
{ |
||||||
|
files: ['**/*.{js,jsx}'], |
||||||
|
languageOptions: { |
||||||
|
ecmaVersion: 2020, |
||||||
|
globals: globals.browser, |
||||||
|
parserOptions: { |
||||||
|
ecmaVersion: 'latest', |
||||||
|
ecmaFeatures: { jsx: true }, |
||||||
|
sourceType: 'module', |
||||||
|
}, |
||||||
|
}, |
||||||
|
plugins: { |
||||||
|
'react-hooks': reactHooks, |
||||||
|
'react-refresh': reactRefresh, |
||||||
|
}, |
||||||
|
rules: { |
||||||
|
...js.configs.recommended.rules, |
||||||
|
...reactHooks.configs.recommended.rules, |
||||||
|
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], |
||||||
|
'react-refresh/only-export-components': [ |
||||||
|
'warn', |
||||||
|
{ allowConstantExport: true }, |
||||||
|
], |
||||||
|
}, |
||||||
|
}, |
||||||
|
] |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8" /> |
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||||
|
<title>24070-Conversation</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="root"></div> |
||||||
|
<script type="module" src="/src/main.jsx"></script> |
||||||
|
</body> |
||||||
|
</html> |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,29 @@ |
|||||||
|
{ |
||||||
|
"name": "vite", |
||||||
|
"private": true, |
||||||
|
"version": "0.0.0", |
||||||
|
"type": "module", |
||||||
|
"scripts": { |
||||||
|
"dev": "vite", |
||||||
|
"build": "vite build", |
||||||
|
"lint": "eslint .", |
||||||
|
"preview": "vite preview" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"@tailwindcss/vite": "^4.1.8", |
||||||
|
"react": "^19.1.0", |
||||||
|
"react-dom": "^19.1.0", |
||||||
|
"tailwindcss": "^4.1.8" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@eslint/js": "^9.25.0", |
||||||
|
"@types/react": "^19.1.2", |
||||||
|
"@types/react-dom": "^19.1.2", |
||||||
|
"@vitejs/plugin-react-swc": "^3.9.0", |
||||||
|
"eslint": "^9.25.0", |
||||||
|
"eslint-plugin-react-hooks": "^5.2.0", |
||||||
|
"eslint-plugin-react-refresh": "^0.4.19", |
||||||
|
"globals": "^16.0.0", |
||||||
|
"vite": "^6.3.5" |
||||||
|
} |
||||||
|
} |
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,114 @@ |
|||||||
|
import { useRef, useState } from 'react'; |
||||||
|
import './App.css' |
||||||
|
|
||||||
|
|
||||||
|
const BASE_URL='http://localhost:3333'; |
||||||
|
|
||||||
|
function App() { |
||||||
|
|
||||||
|
const [history, setHistory] = useState([]); |
||||||
|
const [processing, setProcessing] = useState(false); |
||||||
|
const [prompt, setPrompt] = useState([]); |
||||||
|
|
||||||
|
|
||||||
|
function onSubmit(event) { |
||||||
|
event.preventDefault(); |
||||||
|
const input = event.target.elements.input.value; |
||||||
|
console.log("Submitted:", input); |
||||||
|
|
||||||
|
setProcessing(true); |
||||||
|
|
||||||
|
|
||||||
|
fetch(`${BASE_URL}/generate`, { |
||||||
|
method: 'POST', |
||||||
|
headers: { |
||||||
|
'Content-Type': 'application/json', |
||||||
|
}, |
||||||
|
body: JSON.stringify({ |
||||||
|
input:[ |
||||||
|
...history, |
||||||
|
{ |
||||||
|
role:'user', |
||||||
|
content:[{ |
||||||
|
type:'input_text', |
||||||
|
text: input |
||||||
|
}] |
||||||
|
} |
||||||
|
] |
||||||
|
}), |
||||||
|
}).then(response => { |
||||||
|
if (!response.ok) { |
||||||
|
throw new Error('Network response was not ok'); |
||||||
|
} |
||||||
|
response.json().then(data => {; |
||||||
|
console.log(data); |
||||||
|
|
||||||
|
// add to history |
||||||
|
setHistory(prev => [...prev, { |
||||||
|
role: 'user', |
||||||
|
content: [{ |
||||||
|
type:'input_text', |
||||||
|
text: input |
||||||
|
}] |
||||||
|
}, { |
||||||
|
role: 'assistant', |
||||||
|
content: [{ |
||||||
|
type:'output_text', |
||||||
|
text: data.output_text |
||||||
|
}] |
||||||
|
}]); |
||||||
|
|
||||||
|
setPrompt([ |
||||||
|
...prompt, |
||||||
|
data.prompt, |
||||||
|
]); |
||||||
|
|
||||||
|
// clear input |
||||||
|
event.target.elements.input.value = ''; |
||||||
|
setProcessing(false); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<main className='min-h-screen flex flex-col gap-8 justify-end p-8'> |
||||||
|
<div className='flex flex-col gap-2 border-4'> |
||||||
|
{prompt?.length==0 ? ( |
||||||
|
<div className='p-2 border-b border-gray-200'>Promp will appear here...</div> |
||||||
|
):( |
||||||
|
prompt?.map((item, index) => ( |
||||||
|
<div key={index} className='p-2 border-b border-gray-500'> |
||||||
|
<p className='text-lg'>{item}</p> |
||||||
|
</div> |
||||||
|
)) |
||||||
|
)} |
||||||
|
</div> |
||||||
|
<div className='flex flex-col gap-2'> |
||||||
|
{/* History will be rendered here */} |
||||||
|
{history?.length==0? ( |
||||||
|
<div className='bg-white p-2 rounded border'>History will appear here...</div> |
||||||
|
):( |
||||||
|
history.map((item, index) => ( |
||||||
|
<div key={index} className={`p-2 rounded border-4 ${item.role === 'user' ? 'bg-gray-100' : 'bg-yellow-100'}`}> |
||||||
|
{item.content.map((content, idx) => ( |
||||||
|
<p key={idx} className='text-lg'>{content.text}</p> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
)) |
||||||
|
)} |
||||||
|
</div> |
||||||
|
<div className=''> |
||||||
|
<form className='flex flex-col justify-center *:border-4 gap-4' onSubmit={onSubmit}> |
||||||
|
<input id="input" name="input" required className='self-stretch p-2'/> |
||||||
|
<button type="submit" className='rounded-full uppercase' disabled={processing}>Send</button> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</main> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default App |
||||||
|
After Width: | Height: | Size: 4.0 KiB |
@ -0,0 +1 @@ |
|||||||
|
@import "tailwindcss"; |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
import { StrictMode } from 'react' |
||||||
|
import { createRoot } from 'react-dom/client' |
||||||
|
import './index.css' |
||||||
|
import App from './App.jsx' |
||||||
|
|
||||||
|
createRoot(document.getElementById('root')).render( |
||||||
|
<StrictMode> |
||||||
|
<App /> |
||||||
|
</StrictMode>, |
||||||
|
) |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
import { defineConfig } from 'vite' |
||||||
|
import react from '@vitejs/plugin-react-swc' |
||||||
|
|
||||||
|
import tailwindcss from '@tailwindcss/vite' |
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({ |
||||||
|
plugins: [ |
||||||
|
react(), |
||||||
|
tailwindcss(), |
||||||
|
], |
||||||
|
}) |
||||||
Loading…
Reference in new issue