Skip to content

Commit e961de6

Browse files
committed
支持绘图功能
1 parent f6fee9f commit e961de6

File tree

9 files changed

+72
-6
lines changed

9 files changed

+72
-6
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
/public/build
66
/storage/*.key
77
/vendor
8-
/resources/views/analysis.blade.php
8+
/resources/views
99
.env
1010
.env.backup
1111
.env.production

app/Http/Controllers/ChatController.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,32 @@ public function audio(Request $request): JsonResponse
152152
return response()->json(['chat_id' => $chatId, 'message' => $userMessage]); // 将语音识别结果先返回给客户端
153153
}
154154

155+
public function image(Request $request): JsonResponse
156+
{
157+
$request->validate([
158+
'prompt' => 'required|string'
159+
]);
160+
$messages = $request->session()->get('messages', [$this->preset]);
161+
$prompt = $request->input('prompt');
162+
$userMsg = ['role' => 'user', 'content' => $prompt];
163+
$messages[] = $userMsg;
164+
$response = OpenAI::image([
165+
"prompt" => $prompt,
166+
"n" => 1,
167+
"size" => "256x256",
168+
"response_format" => "url",
169+
]);
170+
$result = json_decode($response);
171+
$image = '';
172+
if (isset($result->data[0]->url)) {
173+
$image = '![](' . $result->data[0]->url . ')';
174+
}
175+
$assistantMsg = ['role' => 'assistant', 'content' => $image];
176+
$messages[] = $assistantMsg;
177+
$request->session()->put('messages', $messages);
178+
return response()->json($assistantMsg);
179+
}
180+
155181
/**
156182
* Reset the session.
157183
*/

app/Providers/RouteServiceProvider.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,8 @@ protected function configureRateLimiting(): void
5050
RateLimiter::for('audio', function (Request $request) {
5151
return Limit::perMinute(30)->by($request->user()?->id ?: $request->ip());
5252
});
53+
RateLimiter::for('image', function (Request $request) {
54+
return Limit::perMinute(20)->by($request->user()?->id ?: $request->ip());
55+
});
5356
}
5457
}

resources/js/Components/AudioWidget.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export default {
3535
},
3636
computed: {
3737
buttonClass() {
38-
return "flex items-center justify-center px-4 py-2 border border-green-600 hover:border-green-700 text-white rounded-md text-sm md:text-base cursor-pointer";
38+
return "flex items-center justify-center px-4 py-2 border border-green-600 bg-green-500 hover:bg-green-600 text-white rounded-md text-sm md:text-base cursor-pointer";
3939
}
4040
},
4141
beforeUnmount() {

resources/js/Components/IconButton.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ export default {
1111
return {
1212
icons: {
1313
download: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-6 h-6"><path d="M19 12v7H5v-7H3v7c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-7h-2zm-6 .67l2.59-2.58L17 11.5l-5 5-5-5 1.41-1.41L11 12.67V3h2z"/><path fill="none" d="M0 0h24v24H0z"/></svg>',
14-
mic: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-6 h-6"><path d="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z"/><path d="M0 0h24v24H0z" fill="none"/></svg>',
14+
mic: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M12 15c1.66 0 2.99-1.34 2.99-3L15 6c0-1.66-1.34-3-3-3S9 4.34 9 6v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 15 6.7 12H5c0 3.42 2.72 6.23 6 6.72V22h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z"></path></svg>',
1515
play: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-6 h-6"><path d="M8 5v14l11-7z"/><path d="M0 0h24v24H0z" fill="none"/></svg>',
1616
save: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-6 h-6"><path d="M0 0h24v24H0z" fill="none"/><path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/></svg>',
17-
stop: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-6 h-6"><path d="M0 0h24v24H0z" fill="none"/><path d="M6 6h12v12H6z"/></svg>',
17+
stop: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M6 6h12v12H6V6z"></path></svg>',
1818
volume: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-6 h-6"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/><path d="M0 0h24v24H0z" fill="#none"/></svg>',
1919
},
2020
};

resources/js/Pages/Chat.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ const audioFailed = (error) => {
3737
})
3838
}
3939
40+
const image = () => {
41+
store.dispatch('imageMessage', form.prompt)
42+
}
43+
4044
</script>
4145

4246
<template>
@@ -116,6 +120,16 @@ const audioFailed = (error) => {
116120
</svg>
117121
</button>
118122
<audio-widget @audio-upload="audio" @audio-failed="audioFailed" />
123+
<button
124+
:class="{ 'flex items-center justify-center px-4 py-2 border border-green-600 bg-green-500 hover:bg-green-600 text-white rounded-md text-sm md:text-base': true }"
125+
@click="image" title="绘制图片" type="button">
126+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
127+
width="24" height="24" viewBox="0 0 24 24">
128+
<path fill="#FFFFFF"
129+
d="M4 2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-4l-4 4l-4-4H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m15 13V7l-4 4l-2-2l-6 6h12M7 5a2 2 0 0 0-2 2a2 2 0 0 0 2 2a2 2 0 0 0 2-2a2 2 0 0 0-2-2Z">
130+
</path>
131+
</svg>
132+
</button>
119133
<button
120134
class="flex items-center justify-center px-4 py-2 border border-gray-500 bg-gray-400 hover:bg-gray-500 text-white rounded-md text-sm md:text-base"
121135
@click="reset" title="清空消息" type="button">

resources/js/api/chat.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ export default {
2121
return fetch(CHAT_CONFIG.BASE_URL + '/audio', { method: 'POST', body: formData })
2222
},
2323

24+
// 发送画图消息
25+
imageMessage: (message) => {
26+
const formData = new FormData();
27+
formData.append('prompt', message);
28+
return axios.post(CHAT_CONFIG.BASE_URL + '/image', formData, { responseType: 'json' })
29+
},
30+
2431
// 清空所有消息
2532
clearMessages: () => {
2633
return axios.get(CHAT_CONFIG.BASE_URL + '/reset')

resources/js/store.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const store = createStore({
6565
eventSource.onerror = function (e) {
6666
eventSource.close();
6767
commit('deleteMessage');
68-
commit('addMessage', { 'role': 'assistant', 'content': '接收回复出错,请重试' })
68+
commit('addMessage', { 'role': 'assistant', 'content': '请求频率太高,请稍后再试' })
6969
};
7070
}).catch(error => {
7171
console.log(error);
@@ -103,7 +103,7 @@ const store = createStore({
103103
eventSource.onerror = function (e) {
104104
eventSource.close();
105105
commit('deleteMessage');
106-
commit('addMessage', { 'role': 'assistant', 'content': '接收回复出错,请重试' })
106+
commit('addMessage', { 'role': 'assistant', 'content': '请求频率太高,请稍后再试' })
107107
};
108108
}).catch(error => {
109109
if (state.messages[state.messages.length - 1].content === '正在识别语音,请稍候...') {
@@ -113,6 +113,21 @@ const store = createStore({
113113
console.log(error);
114114
});
115115
},
116+
imageMessage({ commit }, message) {
117+
commit('addMessage', { 'role': 'user', 'content': message })
118+
commit('addMessage', { 'role': 'assistant', 'content': '正在根据你提供的信息绘图,请稍候...' })
119+
ChatAPI.imageMessage(message).then(response => {
120+
commit('deleteMessage')
121+
commit('addMessage', response.data);
122+
}).catch(error => {
123+
commit('deleteMessage')
124+
if (error.response.status === 429) {
125+
commit('addMessage', { 'role': 'assistant', 'content': '请求过于频繁,请稍后再试' });
126+
} else {
127+
commit('addMessage', { 'role': 'assistant', 'content': '请求处理失败,请重试' });
128+
}
129+
});
130+
},
116131
clearMessages({ commit }) {
117132
ChatAPI.clearMessages().then(response => {
118133
commit('clearMessages');

routes/web.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
Route::post('/chat', ChatController::class . '@chat')->name('chat')->middleware('throttle:chat');
2121
Route::get('/stream', ChatController::class . '@stream')->name('stream');
2222
Route::post('/audio', ChatController::class . '@audio')->name('audio')->middleware('throttle:audio');
23+
Route::post('/image', ChatController::class . '@image')->name('image')->middleware('throttle:imgae');
2324
Route::get('/reset', ChatController::class . '@reset')->name('reset');
2425

2526
// 暂不开放用户认证功能

0 commit comments

Comments
 (0)