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
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
105 additions
and
93 deletions
+105
-93
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
+44
-38
src/views/ai/writer/components/Right.vue
+22
-22
src/views/ai/writer/components/Tag.vue
+4
-5
src/views/ai/writer/index.vue
+31
-25
No files found.
src/api/ai/writer/index.ts
View file @
2b078911
...
...
@@ -3,7 +3,9 @@ import { fetchEventSource } from '@microsoft/fetch-event-source'
import
{
getAccessToken
}
from
'@/utils/auth'
import
{
config
}
from
'@/config/axios/config'
// TODO @hhhero:可以改成 WriteVO 哈,主要是保持一致
export
interface
WriteParams
{
// TODO @hhhero:注释。每个属性的后面哈。会更简洁一点
/**
* 1:撰写 2:回复
*/
...
...
@@ -33,6 +35,7 @@ export interface WriteParams {
*/
language
:
number
}
export
const
writeStream
=
({
data
,
onClose
,
...
...
@@ -46,7 +49,6 @@ export const writeStream = ({
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`
,
{
method
:
'post'
,
...
...
src/views/ai/chat/index/components/conversation/ConversationList.vue
View file @
2b078911
...
...
@@ -226,7 +226,7 @@ const conversationTimeGroup = async (list: ChatConversationVO[]) => {
const
threeDays
=
3
*
oneDay
const
sevenDays
=
7
*
oneDay
const
thirtyDays
=
30
*
oneDay
for
(
const
conversation
:
ChatConversationVO
of
list
)
{
for
(
const
conversation
of
list
)
{
// 置顶
if
(
conversation
.
pinned
)
{
groupMap
[
'置顶'
].
push
(
conversation
)
...
...
@@ -247,7 +247,6 @@ const conversationTimeGroup = async (list: ChatConversationVO[]) => {
groupMap
[
'三十天前'
].
push
(
conversation
)
}
}
console
.
log
(
'----groupMap'
,
groupMap
)
return
groupMap
}
...
...
src/views/ai/writer/components/Left.vue
View file @
2b078911
<
template
>
<!-- 定义
tab组件
-->
<!-- 定义
tab 组件:撰写/回复等
-->
<DefineTab
v-slot=
"
{ active, text, itemClick }">
<span
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 @@
{{
text
}}
</span>
</DefineTab>
<!-- 定义
label组件
-->
<!-- 定义
label 组件:长度/格式/语气/语言等
-->
<DefineLabel
v-slot=
"
{ label, hint, hintClick }">
<h3
class=
"mt-5 mb-3 flex items-center justify-between text-[14px]"
>
<span>
{{
label
}}
</span>
...
...
@@ -23,6 +23,7 @@
</span>
</h3>
</DefineLabel>
<!-- TODO 小屏幕的时候是定位在左边的,大屏是分开的 -->
<div
class=
"relative"
v-bind=
"$attrs"
>
<!-- tab -->
...
...
@@ -99,50 +100,50 @@
</template>
<
script
setup
lang=
"ts"
>
import
{
createReusableTemplate
}
from
'@vueuse/core'
import
{
ref
}
from
'vue'
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'
import
{
createReusableTemplate
}
from
'@vueuse/core'
import
{
ref
}
from
'vue'
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'
]
type
TabType
=
WriteParams
[
'type'
]
const
message
=
useMessage
()
const
message
=
useMessage
()
defineProps
<
{
defineProps
<
{
isWriting
:
boolean
}
>
()
}
>
()
const
emits
=
defineEmits
<
{
const
emits
=
defineEmits
<
{
(
e
:
'submit'
,
params
:
Partial
<
WriteParams
>
)
(
e
:
'example'
,
param
:
'write'
|
'reply'
)
}
>
()
}
>
()
const
example
=
(
type
:
'write'
|
'reply'
)
=>
{
const
example
=
(
type
:
'write'
|
'reply'
)
=>
{
writeForm
.
value
=
{
...
initData
,
...
omit
(
dataJson
[
type
],
[
'data'
])
}
emits
(
'example'
,
type
)
}
}
const
selectedTab
=
ref
<
TabType
>
(
1
)
const
tabs
:
{
const
selectedTab
=
ref
<
TabType
>
(
1
)
const
tabs
:
{
text
:
string
value
:
TabType
}[]
=
[
{
text
:
'撰写'
,
value
:
1
},
}[]
=
[
{
text
:
'撰写'
,
value
:
1
},
// TODO @hhhero:1、2 这个枚举到 constants 里。方便后续万一要调整
{
text
:
'回复'
,
value
:
2
}
]
const
[
DefineTab
,
ReuseTab
]
=
createReusableTemplate
<
{
]
const
[
DefineTab
,
ReuseTab
]
=
createReusableTemplate
<
{
active
?:
boolean
text
:
string
itemClick
:
()
=>
void
}
>
()
}
>
()
const
initData
:
WriteParams
=
{
const
initData
:
WriteParams
=
{
type
:
1
,
prompt
:
''
,
originalContent
:
''
,
...
...
@@ -150,11 +151,14 @@
language
:
1
,
length
:
1
,
format
:
1
}
const
writeForm
=
ref
<
WriteParams
>
({
...
initData
})
const
writeTags
=
{
// 长度
}
// 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'
),
// 格式
...
...
@@ -165,25 +169,27 @@
// 语言
langTags
:
getIntDictOptions
(
'ai_write_language'
)
//
}
}
const
[
DefineLabel
,
ReuseLabel
]
=
createReusableTemplate
<
{
// TODO @hhhero:这个写法不错。要不写个简单的注释,我怕很多人不懂哈。
const
[
DefineLabel
,
ReuseLabel
]
=
createReusableTemplate
<
{
label
:
string
class
?:
string
hint
?:
string
hintClick
?:
()
=>
void
}
>
()
}
>
()
const
switchTab
=
(
value
:
TabType
)
=>
{
const
switchTab
=
(
value
:
TabType
)
=>
{
selectedTab
.
value
=
value
writeForm
.
value
=
{
...
initData
}
}
}
const
submit
=
()
=>
{
const
submit
=
()
=>
{
if
(
selectedTab
.
value
===
2
&&
!
writeForm
.
value
.
originalContent
)
{
message
.
warning
(
'请输入原文'
)
return
}
else
if
(
!
writeForm
.
value
.
prompt
)
{
}
if
(
!
writeForm
.
value
.
prompt
)
{
message
.
warning
(
`请输入
${
selectedTab
.
value
===
1
?
'写作'
:
'回复'
}
内容`
)
return
}
...
...
@@ -191,5 +197,5 @@
...(
selectedTab
.
value
===
1
?
omit
(
writeForm
.
value
,
[
'originalContent'
])
:
writeForm
.
value
),
type
:
selectedTab
.
value
})
}
}
</
script
>
src/views/ai/writer/components/Right.vue
View file @
2b078911
...
...
@@ -35,9 +35,12 @@
</
template
>
<
script
setup
lang=
"ts"
>
import
{
useClipboard
}
from
'@vueuse/core'
const
message
=
useMessage
()
const
props
=
defineProps
({
import
{
useClipboard
}
from
'@vueuse/core'
const
message
=
useMessage
()
const
{
copied
,
copy
}
=
useClipboard
()
const
props
=
defineProps
({
msg
:
{
type
:
String
,
default
:
''
...
...
@@ -46,41 +49,38 @@
type
:
Boolean
,
default
:
false
}
})
const
emits
=
defineEmits
([
'update:msg'
,
'stopStream'
])
})
const
{
copied
,
copy
}
=
useClipboard
(
)
const
emits
=
defineEmits
([
'update:msg'
,
'stopStream'
]
)
const
compMsg
=
computed
({
// TODO @hhhero:是不是 Msg 改成 Content 这种哈。或者 Message。
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
({
/** 滚动 */
const
contentRef
=
ref
<
HTMLDivElement
>
()
defineExpose
({
scrollToBottom
()
{
contentRef
.
value
?.
scrollTo
(
0
,
contentRef
.
value
?.
scrollHeight
)
}
})
})
// 点击复制的时候复制msg
const
copyMsg
=
()
=>
{
/** 点击复制的时候复制内容 */
const
showCopy
=
computed
(()
=>
props
.
msg
&&
!
props
.
isWriting
)
// 是否展示拷贝
const
inputId
=
computed
(()
=>
getCurrentInstance
()?.
uid
)
// TODO @hhhero:这个可以写个注释哈
const
copyMsg
=
()
=>
{
copy
(
props
.
msg
)
}
}
watch
(
copied
,
(
val
)
=>
{
console
.
log
({
copied
:
val
})
watch
(
copied
,
(
val
)
=>
{
if
(
val
)
{
message
.
success
(
'复制成功'
)
}
})
})
</
script
>
src/views/ai/writer/components/Tag.vue
View file @
2b078911
...
...
@@ -13,7 +13,7 @@
</
template
>
<
script
setup
lang=
"ts"
>
const
props
=
withDefaults
(
const
props
=
withDefaults
(
defineProps
<
{
tags
:
{
label
:
string
;
value
:
string
}[]
modelValue
:
string
...
...
@@ -22,11 +22,10 @@
{
tags
:
()
=>
[]
}
)
)
const
emits
=
defineEmits
<
{
const
emits
=
defineEmits
<
{
(
e
:
'update:modelValue'
,
value
:
string
):
void
}
>
()
}
>
()
</
script
>
<
style
scoped
></
style
>
src/views/ai/writer/index.vue
View file @
2b078911
<!-- TODO @hhhero:挪到 write/index/index.vue 里。因为后续会有 write/manager/index.vue 管理内容 -->
<
template
>
<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
:is-writing=
"isWriting"
@
stop-stream=
"stopStream"
ref=
"rightRef"
class=
"flex-grow"
v-model:msg=
"
msg
Result"
v-model:msg=
"
write
Result"
/>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
Left
from
'./components/Left.vue'
import
Right
from
'./components/Right.vue'
import
{
writeStream
}
from
'@/api/ai/writer'
import
dataJson
from
'./data.json'
import
Left
from
'./components/Left.vue'
import
Right
from
'./components/Right.vue'
// TODO @hhhero:搞成 WriteApi 哈
import
{
writeStream
}
from
'@/api/ai/writer'
// TODO @hhhero:dataJson 放到 ai/utils/utils.ts
import
dataJson
from
'./data.json'
const
message
=
useMessage
()
const
msgResult
=
ref
(
''
)
const
isWriting
=
ref
(
false
)
const
message
=
useMessage
()
const
abortController
=
ref
<
AbortController
>
()
const
writeResult
=
ref
(
''
)
// 写作结果
const
isWriting
=
ref
(
false
)
// 是否正在写作中
const
abortController
=
ref
<
AbortController
>
()
// // 写作进行中 abort 控制器(控制 stream 写作)
const
stopStream
=
()
=>
{
/** 停止 stream 生成 */
const
stopStream
=
()
=>
{
abortController
.
value
?.
abort
()
isWriting
.
value
=
false
}
const
rightRef
=
ref
<
InstanceType
<
typeof
Right
>>
()
// 点击示例触发
const
example
=
(
type
:
keyof
typeof
dataJson
)
=>
{
msgResult
.
value
=
dataJson
[
type
].
data
}
}
const
submit
=
async
(
data
)
=>
{
/** 执行写作 */
const
rightRef
=
ref
<
InstanceType
<
typeof
Right
>>
()
const
submit
=
async
(
data
)
=>
{
abortController
.
value
=
new
AbortController
()
msg
Result
.
value
=
''
write
Result
.
value
=
''
isWriting
.
value
=
true
writeStream
({
await
writeStream
({
data
,
onMessage
:
async
(
res
)
=>
{
const
{
code
,
data
,
msg
}
=
JSON
.
parse
(
res
.
data
)
...
...
@@ -48,14 +49,19 @@
stopStream
()
return
}
msgResult
.
value
=
msg
Result
.
value
+
data
writeResult
.
value
=
write
Result
.
value
+
data
nextTick
(()
=>
{
rightRef
.
value
?.
scrollToBottom
()
})
},
ctrl
:
abortController
.
value
,
onClose
:
stopStream
,
onError
:
stopStream
onError
:
stopStream
// TODO @hhhero: error 的时候,是不是要打印下错误哈
})
}
}
/** 点击示例触发 */
const
handleExampleClick
=
(
type
:
keyof
typeof
dataJson
)
=>
{
writeResult
.
value
=
dataJson
[
type
].
data
}
</
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