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
2b078911
authored
Jul 08, 2024
by
YunaiV
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【代码评审】AI:写作相关的建议
parent
96a499a8
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
204 additions
and
192 deletions
+204
-192
src/api/ai/writer/index.ts
+3
-1
src/views/ai/chat/index/components/conversation/ConversationList.vue
+1
-2
src/views/ai/writer/components/Left.vue
+98
-92
src/views/ai/writer/components/Right.vue
+40
-40
src/views/ai/writer/components/Tag.vue
+13
-14
src/views/ai/writer/index.vue
+49
-43
No files found.
src/api/ai/writer/index.ts
View file @
2b078911
...
@@ -3,7 +3,9 @@ import { fetchEventSource } from '@microsoft/fetch-event-source'
...
@@ -3,7 +3,9 @@ import { fetchEventSource } from '@microsoft/fetch-event-source'
import
{
getAccessToken
}
from
'@/utils/auth'
import
{
getAccessToken
}
from
'@/utils/auth'
import
{
config
}
from
'@/config/axios/config'
import
{
config
}
from
'@/config/axios/config'
// TODO @hhhero:可以改成 WriteVO 哈,主要是保持一致
export
interface
WriteParams
{
export
interface
WriteParams
{
// TODO @hhhero:注释。每个属性的后面哈。会更简洁一点
/**
/**
* 1:撰写 2:回复
* 1:撰写 2:回复
*/
*/
...
@@ -33,6 +35,7 @@ export interface WriteParams {
...
@@ -33,6 +35,7 @@ export interface WriteParams {
*/
*/
language
:
number
language
:
number
}
}
export
const
writeStream
=
({
export
const
writeStream
=
({
data
,
data
,
onClose
,
onClose
,
...
@@ -46,7 +49,6 @@ export const writeStream = ({
...
@@ -46,7 +49,6 @@ export const writeStream = ({
onClose
?:
(...
args
:
any
[])
=>
void
onClose
?:
(...
args
:
any
[])
=>
void
ctrl
:
AbortController
ctrl
:
AbortController
})
=>
{
})
=>
{
// return request.post({ url: '/ai/write/generate-stream', data })
const
token
=
getAccessToken
()
const
token
=
getAccessToken
()
return
fetchEventSource
(
`
${
config
.
base_url
}
/ai/write/generate-stream`
,
{
return
fetchEventSource
(
`
${
config
.
base_url
}
/ai/write/generate-stream`
,
{
method
:
'post'
,
method
:
'post'
,
...
...
src/views/ai/chat/index/components/conversation/ConversationList.vue
View file @
2b078911
...
@@ -226,7 +226,7 @@ const conversationTimeGroup = async (list: ChatConversationVO[]) => {
...
@@ -226,7 +226,7 @@ const conversationTimeGroup = async (list: ChatConversationVO[]) => {
const
threeDays
=
3
*
oneDay
const
threeDays
=
3
*
oneDay
const
sevenDays
=
7
*
oneDay
const
sevenDays
=
7
*
oneDay
const
thirtyDays
=
30
*
oneDay
const
thirtyDays
=
30
*
oneDay
for
(
const
conversation
:
ChatConversationVO
of
list
)
{
for
(
const
conversation
of
list
)
{
// 置顶
// 置顶
if
(
conversation
.
pinned
)
{
if
(
conversation
.
pinned
)
{
groupMap
[
'置顶'
].
push
(
conversation
)
groupMap
[
'置顶'
].
push
(
conversation
)
...
@@ -247,7 +247,6 @@ const conversationTimeGroup = async (list: ChatConversationVO[]) => {
...
@@ -247,7 +247,6 @@ const conversationTimeGroup = async (list: ChatConversationVO[]) => {
groupMap
[
'三十天前'
].
push
(
conversation
)
groupMap
[
'三十天前'
].
push
(
conversation
)
}
}
}
}
console
.
log
(
'----groupMap'
,
groupMap
)
return
groupMap
return
groupMap
}
}
...
...
src/views/ai/writer/components/Left.vue
View file @
2b078911
<
template
>
<
template
>
<!-- 定义
tab组件
-->
<!-- 定义
tab 组件:撰写/回复等
-->
<DefineTab
v-slot=
"
{ active, text, itemClick }">
<DefineTab
v-slot=
"
{ active, text, itemClick }">
<span
<span
class=
"inline-block w-1/2 rounded-full cursor-pointer text-center leading-[30px] relative z-1 text-[5C6370] hover:text-black"
class=
"inline-block w-1/2 rounded-full cursor-pointer text-center leading-[30px] relative z-1 text-[5C6370] hover:text-black"
...
@@ -9,7 +9,7 @@
...
@@ -9,7 +9,7 @@
{{
text
}}
{{
text
}}
</span>
</span>
</DefineTab>
</DefineTab>
<!-- 定义
label组件
-->
<!-- 定义
label 组件:长度/格式/语气/语言等
-->
<DefineLabel
v-slot=
"
{ label, hint, hintClick }">
<DefineLabel
v-slot=
"
{ label, hint, hintClick }">
<h3
class=
"mt-5 mb-3 flex items-center justify-between text-[14px]"
>
<h3
class=
"mt-5 mb-3 flex items-center justify-between text-[14px]"
>
<span>
{{
label
}}
</span>
<span>
{{
label
}}
</span>
...
@@ -23,6 +23,7 @@
...
@@ -23,6 +23,7 @@
</span>
</span>
</h3>
</h3>
</DefineLabel>
</DefineLabel>
<!-- TODO 小屏幕的时候是定位在左边的,大屏是分开的 -->
<!-- TODO 小屏幕的时候是定位在左边的,大屏是分开的 -->
<div
class=
"relative"
v-bind=
"$attrs"
>
<div
class=
"relative"
v-bind=
"$attrs"
>
<!-- tab -->
<!-- tab -->
...
@@ -99,97 +100,102 @@
...
@@ -99,97 +100,102 @@
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
createReusableTemplate
}
from
'@vueuse/core'
import
{
createReusableTemplate
}
from
'@vueuse/core'
import
{
ref
}
from
'vue'
import
{
ref
}
from
'vue'
import
Tag
from
'./Tag.vue'
import
Tag
from
'./Tag.vue'
import
{
WriteParams
}
from
'@/api/ai/writer'
import
{
WriteParams
}
from
'@/api/ai/writer'
import
{
omit
}
from
'lodash-es'
import
{
omit
}
from
'lodash-es'
import
{
getIntDictOptions
}
from
'@/utils/dict'
import
{
getIntDictOptions
}
from
'@/utils/dict'
import
dataJson
from
'../data.json'
import
dataJson
from
'../data.json'
type
TabType
=
WriteParams
[
'type'
]
type
TabType
=
WriteParams
[
'type'
]
const
message
=
useMessage
()
const
message
=
useMessage
()
defineProps
<
{
defineProps
<
{
isWriting
:
boolean
isWriting
:
boolean
}
>
()
}
>
()
const
emits
=
defineEmits
<
{
const
emits
=
defineEmits
<
{
(
e
:
'submit'
,
params
:
Partial
<
WriteParams
>
)
(
e
:
'submit'
,
params
:
Partial
<
WriteParams
>
)
(
e
:
'example'
,
param
:
'write'
|
'reply'
)
(
e
:
'example'
,
param
:
'write'
|
'reply'
)
}
>
()
}
>
()
const
example
=
(
type
:
'write'
|
'reply'
)
=>
{
const
example
=
(
type
:
'write'
|
'reply'
)
=>
{
writeForm
.
value
=
{
writeForm
.
value
=
{
...
initData
,
...
initData
,
...
omit
(
dataJson
[
type
],
[
'data'
])
...
omit
(
dataJson
[
type
],
[
'data'
])
}
emits
(
'example'
,
type
)
}
const
selectedTab
=
ref
<
TabType
>
(
1
)
const
tabs
:
{
text
:
string
value
:
TabType
}[]
=
[
{
text
:
'撰写'
,
value
:
1
},
{
text
:
'回复'
,
value
:
2
}
]
const
[
DefineTab
,
ReuseTab
]
=
createReusableTemplate
<
{
active
?:
boolean
text
:
string
itemClick
:
()
=>
void
}
>
()
const
initData
:
WriteParams
=
{
type
:
1
,
prompt
:
''
,
originalContent
:
''
,
tone
:
1
,
language
:
1
,
length
:
1
,
format
:
1
}
const
writeForm
=
ref
<
WriteParams
>
({
...
initData
})
const
writeTags
=
{
// 长度
lenTags
:
getIntDictOptions
(
'ai_write_length'
),
// 格式
formatTags
:
getIntDictOptions
(
'ai_write_format'
),
// 语气
toneTags
:
getIntDictOptions
(
'ai_write_tone'
),
// 语言
langTags
:
getIntDictOptions
(
'ai_write_language'
)
//
}
}
emits
(
'example'
,
type
)
const
[
DefineLabel
,
ReuseLabel
]
=
createReusableTemplate
<
{
}
label
:
string
class
?:
string
const
selectedTab
=
ref
<
TabType
>
(
1
)
hint
?:
string
const
tabs
:
{
hintClick
?:
()
=>
void
text
:
string
}
>
()
value
:
TabType
}[]
=
[
const
switchTab
=
(
value
:
TabType
)
=>
{
{
text
:
'撰写'
,
value
:
1
},
// TODO @hhhero:1、2 这个枚举到 constants 里。方便后续万一要调整
selectedTab
.
value
=
value
{
text
:
'回复'
,
value
:
2
}
writeForm
.
value
=
{
...
initData
}
]
const
[
DefineTab
,
ReuseTab
]
=
createReusableTemplate
<
{
active
?:
boolean
text
:
string
itemClick
:
()
=>
void
}
>
()
const
initData
:
WriteParams
=
{
type
:
1
,
prompt
:
''
,
originalContent
:
''
,
tone
:
1
,
language
:
1
,
length
:
1
,
format
:
1
}
// TODO @hhhero:这个字段,要不叫 formData,和其他模块保持一致。然后 initData 和它也更好对应上
const
writeForm
=
ref
<
WriteParams
>
({
...
initData
})
// TODO @hhhero:这种一次性的变量,要不直接 vue template 直接调用。目的是:让 ts 这块,更专注逻辑哈。
const
writeTags
=
{
// 长度 TODO @hhhero:注释放在和面哈;
// TODO @hhhero:一般 length 不用缩写哈。更完整会更容易阅读;
lenTags
:
getIntDictOptions
(
'ai_write_length'
),
// 格式
formatTags
:
getIntDictOptions
(
'ai_write_format'
),
// 语气
toneTags
:
getIntDictOptions
(
'ai_write_tone'
),
// 语言
langTags
:
getIntDictOptions
(
'ai_write_language'
)
//
}
// TODO @hhhero:这个写法不错。要不写个简单的注释,我怕很多人不懂哈。
const
[
DefineLabel
,
ReuseLabel
]
=
createReusableTemplate
<
{
label
:
string
class
?:
string
hint
?:
string
hintClick
?:
()
=>
void
}
>
()
const
switchTab
=
(
value
:
TabType
)
=>
{
selectedTab
.
value
=
value
writeForm
.
value
=
{
...
initData
}
}
const
submit
=
()
=>
{
if
(
selectedTab
.
value
===
2
&&
!
writeForm
.
value
.
originalContent
)
{
message
.
warning
(
'请输入原文'
)
return
}
}
if
(
!
writeForm
.
value
.
prompt
)
{
const
submit
=
()
=>
{
message
.
warning
(
`请输入
${
selectedTab
.
value
===
1
?
'写作'
:
'回复'
}
内容`
)
if
(
selectedTab
.
value
===
2
&&
!
writeForm
.
value
.
originalContent
)
{
return
message
.
warning
(
'请输入原文'
)
return
}
else
if
(
!
writeForm
.
value
.
prompt
)
{
message
.
warning
(
`请输入
${
selectedTab
.
value
===
1
?
'写作'
:
'回复'
}
内容`
)
return
}
emits
(
'submit'
,
{
...(
selectedTab
.
value
===
1
?
omit
(
writeForm
.
value
,
[
'originalContent'
])
:
writeForm
.
value
),
type
:
selectedTab
.
value
})
}
}
emits
(
'submit'
,
{
...(
selectedTab
.
value
===
1
?
omit
(
writeForm
.
value
,
[
'originalContent'
])
:
writeForm
.
value
),
type
:
selectedTab
.
value
})
}
</
script
>
</
script
>
src/views/ai/writer/components/Right.vue
View file @
2b078911
...
@@ -35,52 +35,52 @@
...
@@ -35,52 +35,52 @@
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
useClipboard
}
from
'@vueuse/core'
import
{
useClipboard
}
from
'@vueuse/core'
const
message
=
useMessage
()
const
props
=
defineProps
({
msg
:
{
type
:
String
,
default
:
''
},
isWriting
:
{
type
:
Boolean
,
default
:
false
}
})
const
emits
=
defineEmits
([
'update:msg'
,
'stopStream'
])
const
message
=
useMessage
()
const
{
copied
,
copy
}
=
useClipboard
()
const
{
copied
,
copy
}
=
useClipboard
()
const
props
=
defineProps
({
msg
:
{
type
:
String
,
default
:
''
},
isWriting
:
{
type
:
Boolean
,
default
:
false
}
})
const
compMsg
=
computed
({
const
emits
=
defineEmits
([
'update:msg'
,
'stopStream'
])
get
()
{
return
props
.
msg
},
set
(
val
)
{
emits
(
'update:msg'
,
val
)
}
})
const
showCopy
=
computed
(()
=>
props
.
msg
&&
!
props
.
isWriting
)
// TODO @hhhero:是不是 Msg 改成 Content 这种哈。或者 Message。
const
compMsg
=
computed
({
get
()
{
return
props
.
msg
},
set
(
val
)
{
emits
(
'update:msg'
,
val
)
}
})
const
inputId
=
computed
(()
=>
getCurrentInstance
()?.
uid
)
/** 滚动 */
const
contentRef
=
ref
<
HTMLDivElement
>
()
defineExpose
({
scrollToBottom
()
{
contentRef
.
value
?.
scrollTo
(
0
,
contentRef
.
value
?.
scrollHeight
)
}
})
const
contentRef
=
ref
<
HTMLDivElement
>
()
/** 点击复制的时候复制内容 */
defineExpose
({
const
showCopy
=
computed
(()
=>
props
.
msg
&&
!
props
.
isWriting
)
// 是否展示拷贝
scrollToBottom
()
{
const
inputId
=
computed
(()
=>
getCurrentInstance
()?.
uid
)
// TODO @hhhero:这个可以写个注释哈
contentRef
.
value
?.
scrollTo
(
0
,
contentRef
.
value
?.
scrollHeight
)
const
copyMsg
=
()
=>
{
}
copy
(
props
.
msg
)
})
}
// 点击复制的时候复制msg
watch
(
copied
,
(
val
)
=>
{
const
copyMsg
=
()
=>
{
if
(
val
)
{
copy
(
props
.
msg
)
message
.
success
(
'复制成功'
)
}
}
})
watch
(
copied
,
(
val
)
=>
{
console
.
log
({
copied
:
val
})
if
(
val
)
{
message
.
success
(
'复制成功'
)
}
})
</
script
>
</
script
>
src/views/ai/writer/components/Tag.vue
View file @
2b078911
...
@@ -13,20 +13,19 @@
...
@@ -13,20 +13,19 @@
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
const
props
=
withDefaults
(
const
props
=
withDefaults
(
defineProps
<
{
defineProps
<
{
tags
:
{
label
:
string
;
value
:
string
}[]
tags
:
{
label
:
string
;
value
:
string
}[]
modelValue
:
string
modelValue
:
string
[
k
:
string
]:
any
[
k
:
string
]:
any
}
>
(),
}
>
(),
{
{
tags
:
()
=>
[]
tags
:
()
=>
[]
}
}
)
)
const
emits
=
defineEmits
<
{
const
emits
=
defineEmits
<
{
(
e
:
'update:modelValue'
,
value
:
string
):
void
(
e
:
'update:modelValue'
,
value
:
string
):
void
}
>
()
}
>
()
</
script
>
</
script
>
<
style
scoped
></
style
>
<
style
scoped
></
style
>
src/views/ai/writer/index.vue
View file @
2b078911
<!-- TODO @hhhero:挪到 write/index/index.vue 里。因为后续会有 write/manager/index.vue 管理内容 -->
<
template
>
<
template
>
<div
class=
"h-[calc(100vh-var(--top-tool-height)-var(--app-footer-height)-40px)] -m-5 flex"
>
<div
class=
"h-[calc(100vh-var(--top-tool-height)-var(--app-footer-height)-40px)] -m-5 flex"
>
<Left
:is-writing=
"isWriting"
class=
"h-full"
@
submit=
"submit"
@
example=
"example"
/>
<Left
:is-writing=
"isWriting"
class=
"h-full"
@
submit=
"submit"
@
example=
"handleExampleClick"
/>
<!-- TODO @hhhero:顶部应该有个预览的 header -->
<!-- TODO @hhhero:整个 Right 组件的框,没铺满的感觉? -->
<Right
<Right
:is-writing=
"isWriting"
:is-writing=
"isWriting"
@
stop-stream=
"stopStream"
@
stop-stream=
"stopStream"
ref=
"rightRef"
ref=
"rightRef"
class=
"flex-grow"
class=
"flex-grow"
v-model:msg=
"
msg
Result"
v-model:msg=
"
write
Result"
/>
/>
</div>
</div>
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
Left
from
'./components/Left.vue'
import
Left
from
'./components/Left.vue'
import
Right
from
'./components/Right.vue'
import
Right
from
'./components/Right.vue'
import
{
writeStream
}
from
'@/api/ai/writer'
// TODO @hhhero:搞成 WriteApi 哈
import
dataJson
from
'./data.json'
import
{
writeStream
}
from
'@/api/ai/writer'
// TODO @hhhero:dataJson 放到 ai/utils/utils.ts
import
dataJson
from
'./data.json'
const
message
=
useMessage
()
const
message
=
useMessage
()
const
msgResult
=
ref
(
''
)
const
isWriting
=
ref
(
false
)
const
abortController
=
ref
<
AbortController
>
()
const
writeResult
=
ref
(
''
)
// 写作结果
const
isWriting
=
ref
(
false
)
// 是否正在写作中
const
abortController
=
ref
<
AbortController
>
()
// // 写作进行中 abort 控制器(控制 stream 写作)
const
stopStream
=
()
=>
{
/** 停止 stream 生成 */
abortController
.
value
?.
abort
()
const
stopStream
=
()
=>
{
isWriting
.
value
=
false
abortController
.
value
?.
abort
()
}
isWriting
.
value
=
false
}
const
rightRef
=
ref
<
InstanceType
<
typeof
Right
>>
()
/** 执行写作 */
const
rightRef
=
ref
<
InstanceType
<
typeof
Right
>>
()
const
submit
=
async
(
data
)
=>
{
abortController
.
value
=
new
AbortController
()
writeResult
.
value
=
''
isWriting
.
value
=
true
await
writeStream
({
data
,
onMessage
:
async
(
res
)
=>
{
const
{
code
,
data
,
msg
}
=
JSON
.
parse
(
res
.
data
)
if
(
code
!==
0
)
{
message
.
alert
(
`写作异常!
${
msg
}
`
)
stopStream
()
return
}
writeResult
.
value
=
writeResult
.
value
+
data
nextTick
(()
=>
{
rightRef
.
value
?.
scrollToBottom
()
})
},
ctrl
:
abortController
.
value
,
onClose
:
stopStream
,
onError
:
stopStream
// TODO @hhhero: error 的时候,是不是要打印下错误哈
})
}
// 点击示例触发
/** 点击示例触发 */
const
example
=
(
type
:
keyof
typeof
dataJson
)
=>
{
const
handleExampleClick
=
(
type
:
keyof
typeof
dataJson
)
=>
{
msgResult
.
value
=
dataJson
[
type
].
data
writeResult
.
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
>
</
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