Skip to content

Commit 43aed94

Browse files
committed
feat: midjourney init
0 parents  commit 43aed94

29 files changed

+1715
-0
lines changed

.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?

.vscode/extensions.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
3+
}

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Vue 3 + TypeScript + Vite
2+
3+
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
4+
5+
## Recommended IDE Setup
6+
7+
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
8+
9+
## Type Support For `.vue` Imports in TS
10+
11+
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
12+
13+
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
14+
15+
1. Disable the built-in TypeScript Extension
16+
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
17+
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
18+
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.

index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Vite + Vue + TS</title>
8+
</head>
9+
<body>
10+
<div id="app"></div>
11+
<script type="module" src="/src/main.ts"></script>
12+
</body>
13+
</html>

package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "midjourney-ui-vue3",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "vue-tsc && vite build",
9+
"preview": "vite preview"
10+
},
11+
"dependencies": {
12+
"clipboard": "^2.0.11",
13+
"uuid": "^9.0.0",
14+
"vue": "^3.2.47"
15+
},
16+
"devDependencies": {
17+
"@types/uuid": "^9.0.1",
18+
"@vitejs/plugin-vue": "^4.1.0",
19+
"autoprefixer": "^10.4.14",
20+
"postcss": "^8.4.24",
21+
"tailwindcss": "^3.3.2",
22+
"typescript": "^5.0.2",
23+
"vite": "^4.3.9",
24+
"vue-tsc": "^1.4.2"
25+
}
26+
}

postcss.config.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// module.exports = {
2+
// plugins: {
3+
// tailwindcss: {},
4+
// autoprefixer: {},
5+
// },
6+
// }
7+
8+
export default {
9+
plugins: {
10+
tailwindcss: {},
11+
autoprefixer: {},
12+
},
13+
}

public/vite.svg

Lines changed: 1 addition & 0 deletions
Loading

src/App.vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script setup lang="ts">
2+
import DrawPanel from './DrawPanel.vue';
3+
</script>
4+
5+
<template>
6+
<div class="h-full">
7+
<DrawPanel />
8+
</div>
9+
</template>
10+
11+
<style scoped>
12+
</style>

src/DrawPanel.vue

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
<script setup lang="ts">
2+
import { reactive, ref, onMounted } from 'vue';
3+
import { Message } from './interfaces/message'
4+
import MessageItem from './Message.vue'
5+
import { getMessagesCache, setMessagesCache } from './utils/storage'
6+
import * as uuid from 'uuid'
7+
import { ImagineAPI, UpscaleAPI } from './api/midjourney'
8+
9+
const contentWrap = ref<HTMLDivElement>()
10+
const data = reactive<{
11+
messages: Message[],
12+
prompt: string
13+
}>({
14+
prompt: '',
15+
messages: getMessagesCache() as Message[]
16+
})
17+
18+
const onClickSend = () => {
19+
sendPrompt()
20+
}
21+
22+
const onKeyDown = (event: Event) => {
23+
event.stopPropagation()
24+
event.preventDefault()
25+
sendPrompt()
26+
}
27+
28+
const sendPrompt = () => {
29+
data.prompt = data.prompt.trim()
30+
if (!data.prompt) return
31+
const msgObj: Message = {
32+
prompt: data.prompt,
33+
createTime: Date.now(),
34+
uri: "",
35+
uuid: uuid.v1()
36+
}
37+
console.log(msgObj.uuid);
38+
39+
data.messages.push(msgObj)
40+
data.prompt = ''
41+
createImagine(msgObj)
42+
scrollToBottom()
43+
setMessagesCache(data.messages)
44+
45+
}
46+
47+
const findOneAndUpdate = (uuid: string, msg: Partial<Message>) => {
48+
if (!uuid) {
49+
return
50+
}
51+
const index = data.messages.findIndex(item => item.uuid === uuid)
52+
if (index === -1) {
53+
return
54+
}
55+
const target: Message = data.messages[index]
56+
Object.keys(msg).forEach((key: string) => {
57+
const value = msg[key as keyof Message]
58+
target[key] = value
59+
})
60+
}
61+
62+
const createImagine = (msg: Message) => {
63+
const { uuid } = msg
64+
if (!uuid) return
65+
ImagineAPI(JSON.stringify({ prompt: msg.prompt }), (resData) => {
66+
findOneAndUpdate(uuid, {
67+
...resData,
68+
msgId: resData.id,
69+
msgHash: resData.hash
70+
})
71+
if (resData && resData.progress === 'done') {
72+
findOneAndUpdate(uuid, {
73+
done: true,
74+
msgId: resData.id,
75+
msgHash: resData.hash
76+
})
77+
}
78+
})
79+
setMessagesCache(data.messages)
80+
}
81+
82+
const onUpscale = (msg: Message) => {
83+
const { index, msgId, msgHash, prompt } = msg
84+
console.log('upscale: ', msg)
85+
if (!msgHash || !msgId || !index || !prompt) {
86+
return
87+
}
88+
89+
const id = uuid.v1()
90+
const newMsg: Message = {
91+
uuid: id,
92+
prompt,
93+
index,
94+
createTime: Date.now(),
95+
generateText: `生成第 ${index} 张图片(Upscale)`,
96+
}
97+
98+
data.messages.push(newMsg)
99+
setMessagesCache(data.messages)
100+
scrollToBottom()
101+
102+
UpscaleAPI({
103+
index,
104+
msgHash,
105+
msgId,
106+
prompt
107+
}, (resData) => {
108+
findOneAndUpdate(id, {
109+
...resData,
110+
msgId: resData.id,
111+
msgHash: resData.hash
112+
})
113+
if (resData && resData.progress === 'done') {
114+
findOneAndUpdate(id, {
115+
done: true,
116+
msgId: resData.id,
117+
msgHash: resData.hash
118+
})
119+
}
120+
setMessagesCache(data.messages)
121+
})
122+
}
123+
124+
const scrollToBottom = () => {
125+
setTimeout(() => {
126+
if (contentWrap.value) {
127+
contentWrap.value.scrollTop = contentWrap.value.scrollHeight
128+
}
129+
})
130+
}
131+
132+
onMounted(() => {
133+
scrollToBottom()
134+
})
135+
136+
</script>
137+
138+
<template>
139+
<div class="h-full py-4">
140+
<div class="relative h-full max-w-[980px] m-auto bg-gray-600 text-white px-10 py-4 rounded-xl">
141+
<div id="contentWrap" ref="contentWrap" class="h-full pb-[120px] overflow-auto m-auto">
142+
<div class="border-b-2 border-purple-400" v-for="(item, index) in data.messages" :key="index">
143+
<MessageItem :message="item" @on-upscale="onUpscale" />
144+
</div>
145+
</div>
146+
<div class="absolute left-10 right-10 bottom-6 flex-row items-center justify-between px-2 py-2 bg-gray-100 border-gray-400 rounded-[4px]">
147+
<!-- 快捷/帮助区域 -->
148+
<div class="flex w-full pb-1 text-sm">
149+
<p class="underline text-orange-400 pr-1 cursor-pointer">垫图</p>
150+
<p class="underline text-orange-400 pr-1 cursor-pointer">帮我想一个</p>
151+
<p class="flex-1"></p>
152+
<p class="underline text-orange-400 pr-1 cursor-pointer">微信交流群</p>
153+
<p class="underline text-orange-400 cursor-pointer">使用说明</p>
154+
</div>
155+
<!-- 输入框 -->
156+
<div class="w-full bg-white flex items-center">
157+
<textarea
158+
v-model="data.prompt"
159+
class="flex-1 px-4 py-2 rounded-[8px] outline-none text-sm text-gray-500"
160+
autofocus
161+
placeholder="输入图片描述,例如'可爱的橘黄色的猫咪, 迪士尼风格'、'海边,机器人,小女孩,吉卜力风格'等"
162+
type="textarea"
163+
:rows="2"
164+
@keydown.enter.prevent.stop="onKeyDown"
165+
/>
166+
<img @click="onClickSend" class="mx-4 w-[36px] h-[36px] cursor-pointer" src="/src/assets/send.png" alt="">
167+
</div>
168+
</div>
169+
</div>
170+
</div>
171+
</template>

src/Message.vue

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<script setup lang="ts">
2+
import { Message } from './interfaces/message';
3+
import { copyString } from './utils/clipboard';
4+
import Tag from './components/Tag.vue';
5+
6+
const props = defineProps({
7+
message: {
8+
type: Object as () => Message
9+
}
10+
})
11+
12+
const emits = defineEmits(['on-upscale'])
13+
14+
const clickToCopy = (str: string) => {
15+
copyString(str)
16+
}
17+
18+
const getTimeStr = (timestamp: number | string) => {
19+
return new Date(timestamp).toLocaleString()
20+
}
21+
22+
const onClickUV = (index: number) => {
23+
emits('on-upscale', {
24+
...props.message,
25+
index
26+
})
27+
}
28+
</script>
29+
30+
<template>
31+
<div class="flex max-w-[500px] py-4">
32+
<div class="w-[50px] h-[50px] mr-4 rounded-full bg-gray-300"></div>
33+
<div class="flex-col flex-1">
34+
<div v-if="message?.generateText">{{ message?.generateText }}</div>
35+
<div class="flex flex-row w-full items-start">
36+
<div class="flex-1 leading-relaxed font-bold break-all mr-1">{{ message?.prompt }}</div>
37+
<div @click.stop="clickToCopy(message?.prompt!)" class="text-sm px-2 py-1 border-orange-400 bg-orange-400 cursor-pointer text-white border rounded">复制咒语</div>
38+
</div>
39+
<div class="text-sm text-slate-300 pt-1">状态:{{ message?.done ? '已完成' : '进行中' }}</div>
40+
<div class="text-sm text-slate-300">时间:{{ message?.createTime ? getTimeStr(message?.createTime) : '2023/6/2 下午6:06:32' }}</div>
41+
<div v-if="!message?.uri" class="flex flex-col items-center mt-2 p-4 bg-[#999] w-[140px] h-[140px] rounded-lg justify-center">
42+
<!-- <img class="block w-[100px]" src="/src/assets/image.png" alt=""> -->
43+
<img class="block w-[50px]" src="/src/assets/image.png" alt="">
44+
<p class="pt-4 text-sm">正在排队生成中</p>
45+
</div>
46+
<div v-else class="flex-col w-full mt-2">
47+
<img class="block" :src="message.uri" alt="">
48+
</div>
49+
<!-- upscale area -->
50+
<div v-if="message?.done && !message.index">
51+
<Tag text="U1" @click="onClickUV(1)" />
52+
<Tag text="U2" @click="onClickUV(2)" />
53+
<Tag text="U3" @click="onClickUV(3)" />
54+
<Tag text="U4" @click="onClickUV(4)" />
55+
</div>
56+
</div>
57+
</div>
58+
</template>

0 commit comments

Comments
 (0)