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