Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
f72e4f47
authored
Jul 08, 2024
by
hhhero
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[新增]完善AI写作
parent
1afdbe65
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
174 additions
and
30 deletions
+174
-30
src/api/ai/writer/index.ts
+13
-2
src/views/ai/writer/components/Left.vue
+29
-9
src/views/ai/writer/components/Right.vue
+73
-12
src/views/ai/writer/data.json
+11
-0
src/views/ai/writer/index.vue
+48
-7
No files found.
src/api/ai/writer/index.ts
View file @
f72e4f47
import
request
from
'@/config/axios'
import
{
fetchEventSource
}
from
'@microsoft/fetch-event-source'
import
{
getAccessToken
}
from
'@/utils/auth'
...
...
@@ -34,7 +33,19 @@ export interface WriteParams {
*/
language
:
number
}
export
const
writeStream
=
(
data
:
WriteParams
,
onMessage
,
onError
,
onClose
,
ctrl
)
=>
{
export
const
writeStream
=
({
data
,
onClose
,
onMessage
,
onError
,
ctrl
}:
{
data
:
WriteParams
onMessage
?:
(
res
:
any
)
=>
void
onError
?:
(...
args
:
any
[])
=>
void
onClose
?:
(...
args
:
any
[])
=>
void
ctrl
:
AbortController
})
=>
{
// return request.post({ url: '/ai/write/generate-stream', data })
const
token
=
getAccessToken
()
return
fetchEventSource
(
`
${
config
.
base_url
}
/ai/write/generate-stream`
,
{
...
...
src/views/ai/writer/components/Left.vue
View file @
f72e4f47
...
...
@@ -13,7 +13,11 @@
<DefineLabel
v-slot=
"
{ label, hint, hintClick }">
<h3
class=
"mt-5 mb-3 flex items-center justify-between text-[14px]"
>
<span>
{{
label
}}
</span>
<span
@
click=
"hintClick"
v-if=
"hint"
class=
"flex items-center text-[12px] text-[#846af7]"
>
<span
@
click=
"hintClick"
v-if=
"hint"
class=
"flex items-center text-[12px] text-[#846af7] cursor-pointer select-none"
>
<Icon
icon=
"ep:question-filled"
/>
{{
hint
}}
</span>
...
...
@@ -43,7 +47,7 @@
>
<div>
<template
v-if=
"selectedTab === 1"
>
<ReuseLabel
label=
"写作内容"
hint=
"示例"
/>
<ReuseLabel
label=
"写作内容"
hint=
"示例"
:hint-click=
"() => example('write')"
/>
<el-input
type=
"textarea"
:rows=
"5"
...
...
@@ -55,7 +59,7 @@
</
template
>
<
template
v-else
>
<ReuseLabel
label=
"原文"
hint=
"示例"
/>
<ReuseLabel
label=
"原文"
hint=
"示例"
:hint-click=
"() => example('reply')"
/>
<el-input
type=
"textarea"
:rows=
"5"
...
...
@@ -86,8 +90,8 @@
<Tag
v-model=
"writeForm.language"
:tags=
"writeTags.langTags"
/>
<div
class=
"flex items-center justify-center mt-3"
>
<el-button>
重置
</el-button>
<el-button
@
click=
"submit"
color=
"#846af7"
>
生成
</el-button>
<el-button
:disabled=
"isWriting"
>
重置
</el-button>
<el-button
:loading=
"isWriting"
@
click=
"submit"
color=
"#846af7"
>
生成
</el-button>
</div>
</div>
</div>
...
...
@@ -97,18 +101,33 @@
<
script
setup
lang=
"ts"
>
import
{
createReusableTemplate
}
from
'@vueuse/core'
import
{
ref
}
from
'vue'
import
{
ElMessage
}
from
'element-plus'
import
Tag
from
'./Tag.vue'
import
{
WriteParams
}
from
'@/api/ai/writer'
import
{
omit
}
from
'lodash-es'
import
{
getIntDictOptions
}
from
'@/utils/dict'
import
dataJson
from
'../data.json'
type
TabType
=
WriteParams
[
'type'
]
const
message
=
useMessage
()
defineProps
<
{
isWriting
:
boolean
}
>
()
const
emits
=
defineEmits
<
{
(
e
:
'submit'
,
params
:
Partial
<
WriteParams
>
)
(
e
:
'example'
,
param
:
'write'
|
'reply'
)
}
>
()
const
example
=
(
type
:
'write'
|
'reply'
)
=>
{
writeForm
.
value
=
{
...
initData
,
...
omit
(
dataJson
[
type
],
[
'data'
])
}
emits
(
'example'
,
type
)
}
const
selectedTab
=
ref
<
TabType
>
(
1
)
const
tabs
:
{
text
:
string
...
...
@@ -129,12 +148,13 @@
originalContent
:
''
,
tone
:
1
,
language
:
1
,
length
:
1
00
,
length
:
1
,
format
:
1
}
const
writeForm
=
ref
<
WriteParams
>
({
...
initData
})
const
writeTags
=
{
// 长度
lenTags
:
getIntDictOptions
(
'ai_write_length'
),
// 格式
...
...
@@ -161,10 +181,10 @@
const
submit
=
()
=>
{
if
(
selectedTab
.
value
===
2
&&
!
writeForm
.
value
.
originalContent
)
{
ElM
essage
.
warning
(
'请输入原文'
)
m
essage
.
warning
(
'请输入原文'
)
return
}
else
if
(
!
writeForm
.
value
.
prompt
)
{
ElM
essage
.
warning
(
`请输入
${
selectedTab
.
value
===
1
?
'写作'
:
'回复'
}
内容`
)
m
essage
.
warning
(
`请输入
${
selectedTab
.
value
===
1
?
'写作'
:
'回复'
}
内容`
)
return
}
emits
(
'submit'
,
{
...
...
src/views/ai/writer/components/Right.vue
View file @
f72e4f47
<
template
>
<div
class=
"h-full box-border py-6 px-7"
>
<div
class=
"w-full h-full bg-white box-border p-3 sm:p-16 overflow-y-auto"
>
<el-input
type=
"textarea"
:value=
"msg"
autosize
:input-style=
"
{ boxShadow: 'none' }"
resize="none"
placeholder="生成的内容……"
/>
<div
class=
"w-full h-full relative bg-white box-border p-3 sm:p-16 pr-0"
>
<!-- 展示在右上角 -->
<el-button
color=
"#846af7"
v-show=
"showCopy"
@
click=
"copyMsg"
class=
"absolute top-2 right-2 copy-btn"
:data-clipboard-target=
"inputId"
>
复制
</el-button>
<!-- 展示在下面中间的位置 -->
<el-button
v-show=
"isWriting"
class=
"absolute bottom-2 left-1/2 -translate-x-1/2"
@
click=
"emits('stopStream')"
>
终止生成
</el-button>
<div
ref=
"contentRef"
class=
"w-full h-full pr-3 sm:pr-16 overflow-y-auto"
>
<el-input
id=
"inputId"
type=
"textarea"
v-model=
"compMsg"
autosize
:input-style=
"
{ boxShadow: 'none' }"
resize="none"
placeholder="生成的内容……"
/>
</div>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
defineProps
({
import
{
useClipboard
}
from
'@vueuse/core'
const
message
=
useMessage
()
const
props
=
defineProps
({
msg
:
{
type
:
String
,
default
:
''
},
isWriting
:
{
type
:
Boolean
,
default
:
false
}
})
</
script
>
<
style
scoped
></
style
>
const
emits
=
defineEmits
([
'update:msg'
,
'stopStream'
])
const
{
copied
,
copy
}
=
useClipboard
()
const
compMsg
=
computed
({
get
()
{
return
props
.
msg
},
set
(
val
)
{
emits
(
'update:msg'
,
val
)
}
})
const
showCopy
=
computed
(()
=>
props
.
msg
&&
!
props
.
isWriting
)
const
inputId
=
computed
(()
=>
getCurrentInstance
()?.
uid
)
const
contentRef
=
ref
<
HTMLDivElement
>
()
defineExpose
({
scrollToBottom
()
{
contentRef
.
value
?.
scrollTo
(
0
,
contentRef
.
value
?.
scrollHeight
)
}
})
// 点击复制的时候复制msg
const
copyMsg
=
()
=>
{
copy
(
props
.
msg
)
}
watch
(
copied
,
(
val
)
=>
{
console
.
log
({
copied
:
val
})
if
(
val
)
{
message
.
success
(
'复制成功'
)
}
})
</
script
>
src/views/ai/writer/data.json
0 → 100644
View file @
f72e4f47
{
"write"
:
{
"prompt"
:
"vue"
,
"data"
:
"Vue.js 是一种用于构建用户界面的渐进式 JavaScript 框架。它的核心库只关注视图层,易于上手,同时也便于与其他库或已有项目整合。
\n\n
Vue.js 的特点包括:
\n
- 响应式的数据绑定:Vue.js 会自动将数据与 DOM 同步,使得状态管理变得更加简单。
\n
- 组件化:Vue.js 允许开发者通过小型、独立和通常可复用的组件构建大型应用。
\n
- 虚拟 DOM:Vue.js 使用虚拟 DOM 实现快速渲染,提高了性能。
\n\n
在 Vue.js 中,一个典型的应用结构可能包括:
\n
1. 根实例:每个 Vue 应用都需要一个根实例作为入口点。
\n
2. 组件系统:可以创建自定义的可复用组件。
\n
3. 指令:特殊的带有前缀 v- 的属性,为 DOM 元素提供特殊的行为。
\n
4. 插值:用于文本内容,将数据动态地插入到 HTML。
\n
5. 计算属性和侦听器:用于处理数据的复杂逻辑和响应数据变化。
\n
6. 条件渲染:根据条件决定元素的渲染。
\n
7. 列表渲染:用于显示列表数据。
\n
8. 事件处理:响应用户交互。
\n
9. 表单输入绑定:处理表单输入和验证。
\n
10. 组件生命周期钩子:在组件的不同阶段执行特定的函数。
\n\n
Vue.js 还提供了官方的路由器 Vue Router 和状态管理库 Vuex,以支持构建复杂的单页应用(SPA)。
\n\n
在开发过程中,开发者通常会使用 Vue CLI,这是一个强大的命令行工具,用于快速生成 Vue 项目脚手架,集成了诸如 Babel、Webpack 等现代前端工具,以及热重载、代码检测等开发体验优化功能。
\n\n
Vue.js 的生态系统还包括大量的第三方库和插件,如 Vuetify(UI 组件库)、Vue Test Utils(测试工具)等,这些都极大地丰富了 Vue.js 的开发生态。
\n\n
总的来说,Vue.js 是一个灵活、高效的前端框架,适合从小型项目到大型企业级应用的开发。它的易用性、灵活性和强大的社区支持使其成为许多开发者的首选框架之一。"
},
"reply"
:
{
"originalContent"
:
"领导,我想请假"
,
"prompt"
:
"不批"
,
"data"
:
"您的请假申请已收悉,经核实和考虑,暂时无法批准您的请假申请。
\n\n
如有特殊情况或紧急事务,请及时与我联系。
\n\n
祝工作顺利。
\n\n
谢谢。"
}
}
src/views/ai/writer/index.vue
View file @
f72e4f47
<
template
>
<div
class=
"h-[calc(100vh-var(--top-tool-height)-var(--app-footer-height)-40px)] -m-5 flex"
>
<Left
class=
"h-full"
@
submit=
"submit"
/>
<Right
class=
"flex-grow"
:msg=
"msg"
/>
<Left
:is-writing=
"isWriting"
class=
"h-full"
@
submit=
"submit"
@
example=
"example"
/>
<Right
:is-writing=
"isWriting"
@
stop-stream=
"stopStream"
ref=
"rightRef"
class=
"flex-grow"
v-model:msg=
"msgResult"
/>
</div>
</
template
>
...
...
@@ -9,12 +15,47 @@
import
Left
from
'./components/Left.vue'
import
Right
from
'./components/Right.vue'
import
{
writeStream
}
from
'@/api/ai/writer'
import
dataJson
from
'./data.json'
const
msg
=
ref
(
''
)
const
message
=
useMessage
()
const
msgResult
=
ref
(
''
)
const
isWriting
=
ref
(
false
)
const
submit
=
async
(
params
)
=>
{
const
res
=
await
writeStream
(
params
)
const
abortController
=
ref
<
AbortController
>
()
const
stopStream
=
()
=>
{
abortController
.
value
?.
abort
()
isWriting
.
value
=
false
}
</
script
>
<
style
scoped
></
style
>
const
rightRef
=
ref
<
InstanceType
<
typeof
Right
>>
()
// 点击示例触发
const
example
=
(
type
:
keyof
typeof
dataJson
)
=>
{
msgResult
.
value
=
dataJson
[
type
].
data
}
const
submit
=
async
(
data
)
=>
{
abortController
.
value
=
new
AbortController
()
msgResult
.
value
=
''
isWriting
.
value
=
true
writeStream
({
data
,
onMessage
:
async
(
res
)
=>
{
const
{
code
,
data
,
msg
}
=
JSON
.
parse
(
res
.
data
)
if
(
code
!==
0
)
{
message
.
alert
(
`写作异常!
${
msg
}
`
)
stopStream
()
return
}
msgResult
.
value
=
msgResult
.
value
+
data
nextTick
(()
=>
{
rightRef
.
value
?.
scrollToBottom
()
})
},
ctrl
:
abortController
.
value
,
onClose
:
stopStream
,
onError
:
stopStream
})
}
</
script
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment