mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-05-05 09:24:29 +08:00
acomplish re-generating for mobile chat page
This commit is contained in:
@@ -194,7 +194,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, input ChatInput, c *gin.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
if userVo.Power < input.ChatModel.Power {
|
if userVo.Power < input.ChatModel.Power {
|
||||||
return fmt.Errorf("您当前剩余算力 %d 已不足以支付当前模型的单次对话需要消耗的算力 %d,[立即购买](/member)。", userVo.Power, input.ChatModel.Power)
|
return fmt.Errorf("您的算力不足,请购买算力。")
|
||||||
}
|
}
|
||||||
|
|
||||||
if userVo.ExpiredTime > 0 && userVo.ExpiredTime <= time.Now().Unix() {
|
if userVo.ExpiredTime > 0 && userVo.ExpiredTime <= time.Now().Unix() {
|
||||||
|
|||||||
@@ -8,24 +8,40 @@
|
|||||||
<div class="triangle"></div>
|
<div class="triangle"></div>
|
||||||
<div class="content-box" ref="contentRef">
|
<div class="content-box" ref="contentRef">
|
||||||
<div
|
<div
|
||||||
|
v-if="content"
|
||||||
:data-clipboard-text="orgContent"
|
:data-clipboard-text="orgContent"
|
||||||
class="content content-mobile"
|
class="content content-mobile"
|
||||||
v-html="content"
|
v-html="content"
|
||||||
v-if="content"
|
|
||||||
></div>
|
></div>
|
||||||
|
<div v-else-if="error">
|
||||||
|
<div class="content content-mobile !text-red-500">{{ error }}</div>
|
||||||
|
</div>
|
||||||
<div class="content content-mobile flex justify-start items-center" v-else>
|
<div class="content content-mobile flex justify-start items-center" v-else>
|
||||||
<span class="mr-2">AI 思考中</span> <Thinking :duration="1.5" />
|
<span class="mr-2">AI 思考中</span> <Thinking :duration="1.5" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作按钮区域 -->
|
||||||
|
<div class="action-buttons" v-if="showActions && orgContent">
|
||||||
|
<van-button
|
||||||
|
size="mini"
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
@click="handleRegenerate"
|
||||||
|
:disabled="isGenerating"
|
||||||
|
>
|
||||||
|
{{ isGenerating ? '生成中...' : '重新生成' }}
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
|
||||||
import { showImagePreview } from 'vant'
|
import { showImagePreview } from 'vant'
|
||||||
import Thinking from '../Thinking.vue'
|
import Thinking from '../Thinking.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
content: {
|
content: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -42,12 +58,37 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '/images/gpt-icon.png',
|
default: '/images/gpt-icon.png',
|
||||||
},
|
},
|
||||||
|
showActions: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
isGenerating: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
messageId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits(['regenerate'])
|
||||||
|
|
||||||
const content = computed(() => {
|
const content = computed(() => {
|
||||||
return props.content.text
|
return props.content.text
|
||||||
})
|
})
|
||||||
|
|
||||||
const contentRef = ref(null)
|
const contentRef = ref(null)
|
||||||
|
|
||||||
|
// 处理重新生成
|
||||||
|
const handleRegenerate = () => {
|
||||||
|
emits('regenerate', props.messageId)
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const imgs = contentRef.value.querySelectorAll('img')
|
const imgs = contentRef.value.querySelectorAll('img')
|
||||||
for (let i = 0; i < imgs.length; i++) {
|
for (let i = 0; i < imgs.length; i++) {
|
||||||
@@ -201,6 +242,17 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-left: 5px;
|
||||||
|
|
||||||
|
.van-button {
|
||||||
|
font-size: 12px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -789,6 +789,7 @@ const sendSSERequest = async (message) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 回答完毕,更新完整的消息内容
|
||||||
if (data.type === 'complete') {
|
if (data.type === 'complete') {
|
||||||
chatData.value[chatData.value.length - 1] = data.body
|
chatData.value[chatData.value.length - 1] = data.body
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</van-nav-bar>
|
</van-nav-bar>
|
||||||
|
|
||||||
<!-- 移除分享面板 -->
|
|
||||||
|
|
||||||
<div class="chat-list-wrapper">
|
<div class="chat-list-wrapper">
|
||||||
<div id="message-list-box" :style="{ height: winHeight + 'px' }" class="message-list-box">
|
<div id="message-list-box" :style="{ height: winHeight + 'px' }" class="message-list-box">
|
||||||
<van-list
|
<van-list
|
||||||
@@ -41,6 +39,11 @@
|
|||||||
:content="item.content"
|
:content="item.content"
|
||||||
:icon="item.icon"
|
:icon="item.icon"
|
||||||
:org-content="item.orgContent"
|
:org-content="item.orgContent"
|
||||||
|
:message-id="item.id"
|
||||||
|
:is-generating="isGenerating"
|
||||||
|
:show-actions="item.showAction"
|
||||||
|
:error="item.error"
|
||||||
|
@regenerate="handleRegenerate"
|
||||||
/>
|
/>
|
||||||
</van-cell>
|
</van-cell>
|
||||||
</van-list>
|
</van-list>
|
||||||
@@ -50,23 +53,21 @@
|
|||||||
<div class="chat-box-wrapper">
|
<div class="chat-box-wrapper">
|
||||||
<van-sticky ref="bottomBarRef" :offset-bottom="0" position="bottom">
|
<van-sticky ref="bottomBarRef" :offset-bottom="0" position="bottom">
|
||||||
<van-cell-group inset style="--van-cell-background: var(--van-cell-background-light)">
|
<van-cell-group inset style="--van-cell-background: var(--van-cell-background-light)">
|
||||||
<!-- <div class="flex flex-row p-2">
|
<van-field
|
||||||
<file-list :files="[{ url: 'https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png', name: 'test.png', ext: 'png', size: 1024 }]" />
|
v-model="prompt"
|
||||||
</div> -->
|
center
|
||||||
<van-field v-model="prompt" center clearable placeholder="输入你的问题">
|
clearable
|
||||||
<template #left-icon>
|
placeholder="输入你的问题"
|
||||||
<!-- <div class="flex flex-row">
|
@keyup.enter="sendMessage"
|
||||||
<span class="rounded-full size-6 flex items-center justify-center"><i class="iconfont icon-attachment-cl"></i></span>
|
>
|
||||||
</div> -->
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #button>
|
<template #button>
|
||||||
<van-button size="small" type="primary" @click="sendMessage">发送</van-button>
|
<van-button size="small" type="primary" v-if="!isGenerating" @click="sendMessage"
|
||||||
|
>发送</van-button
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<div class="icon-box">
|
<div class="icon-box">
|
||||||
<van-icon v-if="showStopGenerate" name="stop-circle-o" @click="stopGenerate" />
|
<van-icon v-if="isGenerating" name="stop-circle-o" @click="stopGenerate" />
|
||||||
<van-icon v-if="showReGenerate" name="play-circle-o" @click="reGenerate" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
@@ -74,19 +75,6 @@
|
|||||||
</van-sticky>
|
</van-sticky>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <van-overlay :show="showMic" z-index="100">-->
|
|
||||||
<!-- <div class="mic-wrapper">-->
|
|
||||||
<!-- <div class="image">-->
|
|
||||||
<!-- <van-image-->
|
|
||||||
<!-- width="100"-->
|
|
||||||
<!-- height="100"-->
|
|
||||||
<!-- src="/images/mic.gif"-->
|
|
||||||
<!-- />-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- <van-button type="success" @click="stopVoice">说完了</van-button>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </van-overlay>-->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<van-popup v-model:show="showPicker" position="bottom" class="popup">
|
<van-popup v-model:show="showPicker" position="bottom" class="popup">
|
||||||
@@ -142,7 +130,6 @@ const modelValue = ref('')
|
|||||||
const title = ref(router.currentRoute.value.query['title'])
|
const title = ref(router.currentRoute.value.query['title'])
|
||||||
const chatId = ref(router.currentRoute.value.query['chat_id'])
|
const chatId = ref(router.currentRoute.value.query['chat_id'])
|
||||||
const loginUser = ref(null)
|
const loginUser = ref(null)
|
||||||
// const showMic = ref(false)
|
|
||||||
const showPicker = ref(false)
|
const showPicker = ref(false)
|
||||||
const columns = ref([roles.value, models.value])
|
const columns = ref([roles.value, models.value])
|
||||||
const selectedValues = ref([roleId.value, modelId.value])
|
const selectedValues = ref([roleId.value, modelId.value])
|
||||||
@@ -250,12 +237,6 @@ md.use(emoji)
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
winHeight.value =
|
winHeight.value =
|
||||||
window.innerHeight - navBarRef.value.$el.offsetHeight - bottomBarRef.value.$el.offsetHeight - 70
|
window.innerHeight - navBarRef.value.$el.offsetHeight - bottomBarRef.value.$el.offsetHeight - 70
|
||||||
|
|
||||||
// 移除 Clipboard.js 相关内容
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
// Remove WebSocket handler cleanup
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const newChat = (item) => {
|
const newChat = (item) => {
|
||||||
@@ -272,10 +253,7 @@ const newChat = (item) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onLoad = () => {
|
const onLoad = () => {
|
||||||
// checkSession().then(() => {
|
// 加载更多消息的逻辑可以在这里实现
|
||||||
// connect()
|
|
||||||
// }).catch(() => {
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadChatHistory = () => {
|
const loadChatHistory = () => {
|
||||||
@@ -294,6 +272,7 @@ const loadChatHistory = () => {
|
|||||||
text: role.hello_msg,
|
text: role.hello_msg,
|
||||||
},
|
},
|
||||||
orgContent: role.hello_msg,
|
orgContent: role.hello_msg,
|
||||||
|
showAction: false,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -303,8 +282,8 @@ const loadChatHistory = () => {
|
|||||||
chatData.value.push(data[i])
|
chatData.value.push(data[i])
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
data[i].showAction = true
|
||||||
data[i].orgContent = data[i].content
|
data[i].orgContent = data[i].content.text
|
||||||
data[i].content.text = md.render(processContent(data[i].content.text))
|
data[i].content.text = md.render(processContent(data[i].content.text))
|
||||||
chatData.value.push(data[i])
|
chatData.value.push(data[i])
|
||||||
}
|
}
|
||||||
@@ -326,13 +305,12 @@ const loadChatHistory = () => {
|
|||||||
|
|
||||||
// 创建 socket 连接
|
// 创建 socket 连接
|
||||||
const prompt = ref('')
|
const prompt = ref('')
|
||||||
const showStopGenerate = ref(false) // 停止生成
|
|
||||||
const showReGenerate = ref(false) // 重新生成
|
|
||||||
const previousText = ref('') // 上一次提问
|
const previousText = ref('') // 上一次提问
|
||||||
const lineBuffer = ref('') // 输出缓冲行
|
const lineBuffer = ref('') // 输出缓冲行
|
||||||
const canSend = ref(true)
|
const isGenerating = ref(false)
|
||||||
const isNewMsg = ref(true)
|
const isNewMsg = ref(true)
|
||||||
const stream = ref(store.chatStream)
|
const stream = ref(store.chatStream)
|
||||||
|
const abortController = new AbortController()
|
||||||
watch(
|
watch(
|
||||||
() => store.chatStream,
|
() => store.chatStream,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
@@ -340,18 +318,6 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const disableInput = (force) => {
|
|
||||||
canSend.value = false
|
|
||||||
showReGenerate.value = false
|
|
||||||
showStopGenerate.value = !force
|
|
||||||
}
|
|
||||||
|
|
||||||
const enableInput = () => {
|
|
||||||
canSend.value = true
|
|
||||||
showReGenerate.value = previousText.value !== ''
|
|
||||||
showStopGenerate.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将聊天框的滚动条滑动到最底部
|
// 将聊天框的滚动条滑动到最底部
|
||||||
const scrollListBox = () => {
|
const scrollListBox = () => {
|
||||||
document
|
document
|
||||||
@@ -362,6 +328,7 @@ const scrollListBox = () => {
|
|||||||
// 发送 SSE 请求
|
// 发送 SSE 请求
|
||||||
const sendSSERequest = async (message) => {
|
const sendSSERequest = async (message) => {
|
||||||
try {
|
try {
|
||||||
|
isGenerating.value = true
|
||||||
await fetchEventSource('/api/chat/message', {
|
await fetchEventSource('/api/chat/message', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -369,6 +336,13 @@ const sendSSERequest = async (message) => {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify(message),
|
body: JSON.stringify(message),
|
||||||
openWhenHidden: true,
|
openWhenHidden: true,
|
||||||
|
// 重试机制,避免连接断开后一直重试
|
||||||
|
retry: 3000,
|
||||||
|
// 设置重试延迟为0,确保不重试
|
||||||
|
retryDelay: 3000,
|
||||||
|
// 设置最大重试次数为0
|
||||||
|
maxRetries: 3,
|
||||||
|
signal: abortController.signal,
|
||||||
onopen(response) {
|
onopen(response) {
|
||||||
if (response.ok && response.status === 200) {
|
if (response.ok && response.status === 200) {
|
||||||
console.log('SSE connection opened')
|
console.log('SSE connection opened')
|
||||||
@@ -380,13 +354,13 @@ const sendSSERequest = async (message) => {
|
|||||||
try {
|
try {
|
||||||
const data = JSON.parse(msg.data)
|
const data = JSON.parse(msg.data)
|
||||||
if (data.type === 'error') {
|
if (data.type === 'error') {
|
||||||
showMessageError(data.body)
|
chatData.value[chatData.value.length - 1].error = data.body
|
||||||
enableInput()
|
isGenerating.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.type === 'end') {
|
if (data.type === 'end') {
|
||||||
enableInput()
|
isGenerating.value = false
|
||||||
lineBuffer.value = '' // 清空缓冲
|
lineBuffer.value = '' // 清空缓冲
|
||||||
isNewMsg.value = true
|
isNewMsg.value = true
|
||||||
return
|
return
|
||||||
@@ -429,32 +403,49 @@ const sendSSERequest = async (message) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 回答完毕,更新完整的消息内容
|
||||||
|
if (data.type === 'complete') {
|
||||||
|
data.body.showAction = true
|
||||||
|
data.body.orgContent = data.body.content.text
|
||||||
|
chatData.value[chatData.value.length - 1] = data.body
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing message:', error)
|
console.error('Error processing message:', error)
|
||||||
enableInput()
|
isGenerating.value = false
|
||||||
showMessageError('消息处理出错,请重试')
|
showMessageError('消息处理出错,请重试')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onerror(err) {
|
onerror(err) {
|
||||||
console.error('SSE Error:', err)
|
console.error('SSE Error:', err)
|
||||||
enableInput()
|
try {
|
||||||
|
abortController.value && abortController.value.abort()
|
||||||
|
} catch (e) {
|
||||||
|
console.error('AbortController abort error:', e)
|
||||||
|
}
|
||||||
|
isGenerating.value = false
|
||||||
showMessageError('连接已断开,请重试')
|
showMessageError('连接已断开,请重试')
|
||||||
},
|
},
|
||||||
onclose() {
|
onclose() {
|
||||||
console.log('SSE connection closed')
|
console.log('SSE connection closed')
|
||||||
enableInput()
|
isGenerating.value = false
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
abortController.value && abortController.value.abort()
|
||||||
|
} catch (e) {
|
||||||
|
console.error('AbortController abort error:', e)
|
||||||
|
}
|
||||||
console.error('Failed to send message:', error)
|
console.error('Failed to send message:', error)
|
||||||
enableInput()
|
isGenerating.value = false
|
||||||
showMessageError('发送消息失败,请重试')
|
showMessageError('发送消息失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送消息
|
// 发送消息
|
||||||
const sendMessage = () => {
|
const sendMessage = () => {
|
||||||
if (canSend.value === false) {
|
if (isGenerating.value) {
|
||||||
showToast('AI 正在作答中,请稍后...')
|
showToast('AI 正在作答中,请稍后...')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -489,8 +480,6 @@ const sendMessage = () => {
|
|||||||
scrollListBox()
|
scrollListBox()
|
||||||
})
|
})
|
||||||
|
|
||||||
disableInput(false)
|
|
||||||
|
|
||||||
// 发送 SSE 请求
|
// 发送 SSE 请求
|
||||||
sendSSERequest({
|
sendSSERequest({
|
||||||
user_id: loginUser.value.id,
|
user_id: loginUser.value.id,
|
||||||
@@ -506,16 +495,51 @@ const sendMessage = () => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新生成
|
// 停止生成
|
||||||
const reGenerate = () => {
|
const stopGenerate = function () {
|
||||||
disableInput(false)
|
if (abortController.value) {
|
||||||
const text = '重新生成上述问题的答案:' + previousText.value
|
abortController.value.abort()
|
||||||
// 追加消息
|
isGenerating.value = false
|
||||||
|
httpGet('/api/chat/stop?session_id=' + getClientId())
|
||||||
|
.then(() => {
|
||||||
|
console.log('会话已中断')
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
showMessageError('中断对话失败:' + e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理从ChatReply组件触发的重新生成
|
||||||
|
const handleRegenerate = (messageId) => {
|
||||||
|
if (isGenerating.value) {
|
||||||
|
showToast('AI 正在作答中,请稍后...')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('messageId', messageId)
|
||||||
|
console.log('chatData.value', chatData.value)
|
||||||
|
|
||||||
|
// 判断 messageId 是整数
|
||||||
|
if (messageId !== '' && isNaN(messageId)) {
|
||||||
|
showToast('消息 ID 不合法,无法重新生成')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chatData.value = chatData.value.filter((item) => item.id < messageId && !item.isHello)
|
||||||
|
const userPrompt = chatData.value.pop()
|
||||||
|
|
||||||
|
// 添加空回复消息
|
||||||
|
const _role = getRoleById(roleId.value)
|
||||||
chatData.value.push({
|
chatData.value.push({
|
||||||
type: 'prompt',
|
chat_id: chatId,
|
||||||
|
role_id: roleId.value,
|
||||||
|
type: 'reply',
|
||||||
id: randString(32),
|
id: randString(32),
|
||||||
icon: loginUser.value.avatar,
|
icon: _role['icon'],
|
||||||
content: renderInputText(text),
|
content: {
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 发送 SSE 请求
|
// 发送 SSE 请求
|
||||||
@@ -524,13 +548,12 @@ const reGenerate = () => {
|
|||||||
role_id: roleId.value,
|
role_id: roleId.value,
|
||||||
model_id: modelId.value,
|
model_id: modelId.value,
|
||||||
chat_id: chatId.value,
|
chat_id: chatId.value,
|
||||||
prompt: previousText.value,
|
last_msg_id: messageId,
|
||||||
|
prompt: userPrompt.content.text,
|
||||||
stream: stream.value,
|
stream: stream.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除 showShare、shareOptions、shareChat 相关内容
|
|
||||||
|
|
||||||
const getRoleById = function (rid) {
|
const getRoleById = function (rid) {
|
||||||
for (let i = 0; i < roles.value.length; i++) {
|
for (let i = 0; i < roles.value.length; i++) {
|
||||||
if (roles.value[i]['id'] === rid) {
|
if (roles.value[i]['id'] === rid) {
|
||||||
|
|||||||
Reference in New Issue
Block a user