Przeglądaj źródła

【代码评审】AI:写作相关的建议

YunaiV 11 miesięcy temu
rodzic
commit
2b0789112f

+ 3 - 1
src/api/ai/writer/index.ts

@@ -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',

+ 1 - 2
src/views/ai/chat/index/components/conversation/ConversationList.vue

@@ -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
 }
 

+ 98 - 92
src/views/ai/writer/components/Left.vue

@@ -1,5 +1,5 @@
 <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,97 +100,102 @@
 </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'
-
-  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
-    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')
-    //
+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']
+
+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'])
   }
-
-  const [DefineLabel, ReuseLabel] = createReusableTemplate<{
-    label: string
-    class?: string
-    hint?: string
-    hintClick?: () => void
-  }>()
-
-  const switchTab = (value: TabType) => {
-    selectedTab.value = value
-    writeForm.value = { ...initData }
+  emits('example', type)
+}
+
+const selectedTab = ref<TabType>(1)
+const tabs: {
+  text: string
+  value: TabType
+}[] = [
+  { text: '撰写', value: 1 }, // TODO @hhhero:1、2 这个枚举到 constants 里。方便后续万一要调整
+  { 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
+}
+// 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
   }
-
-  const submit = () => {
-    if (selectedTab.value === 2 && !writeForm.value.originalContent) {
-      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
-    })
+  if (!writeForm.value.prompt) {
+    message.warning(`请输入${selectedTab.value === 1 ? '写作' : '回复'}内容`)
+    return
   }
+  emits('submit', {
+    ...(selectedTab.value === 1 ? omit(writeForm.value, ['originalContent']) : writeForm.value),
+    type: selectedTab.value
+  })
+}
 </script>

+ 40 - 40
src/views/ai/writer/components/Right.vue

@@ -35,52 +35,52 @@
 </template>
 
 <script setup lang="ts">
-  import { useClipboard } from '@vueuse/core'
-  const message = useMessage()
-  const props = defineProps({
-    msg: {
-      type: String,
-      default: ''
-    },
-    isWriting: {
-      type: Boolean,
-      default: false
-    }
-  })
+import { useClipboard } from '@vueuse/core'
 
-  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({
-    get() {
-      return props.msg
-    },
-    set(val) {
-      emits('update:msg', val)
-    }
-  })
+const emits = defineEmits(['update:msg', 'stopStream'])
 
-  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({
-    scrollToBottom() {
-      contentRef.value?.scrollTo(0, contentRef.value?.scrollHeight)
-    }
-  })
+/** 点击复制的时候复制内容 */
+const showCopy = computed(() => props.msg && !props.isWriting) // 是否展示拷贝
+const inputId = computed(() => getCurrentInstance()?.uid) // TODO @hhhero:这个可以写个注释哈
+const copyMsg = () => {
+  copy(props.msg)
+}
 
-  // 点击复制的时候复制msg
-  const copyMsg = () => {
-    copy(props.msg)
+watch(copied, (val) => {
+  if (val) {
+    message.success('复制成功')
   }
-
-  watch(copied, (val) => {
-    console.log({ copied: val })
-    if (val) {
-      message.success('复制成功')
-    }
-  })
+})
 </script>

+ 13 - 14
src/views/ai/writer/components/Tag.vue

@@ -13,20 +13,19 @@
 </template>
 
 <script setup lang="ts">
-  const props = withDefaults(
-    defineProps<{
-      tags: { label: string; value: string }[]
-      modelValue: string
-      [k: string]: any
-    }>(),
-    {
-      tags: () => []
-    }
-  )
+const props = withDefaults(
+  defineProps<{
+    tags: { label: string; value: string }[]
+    modelValue: string
+    [k: string]: any
+  }>(),
+  {
+    tags: () => []
+  }
+)
 
-  const emits = defineEmits<{
-    (e: 'update:modelValue', value: string): void
-  }>()
+const emits = defineEmits<{
+  (e: 'update:modelValue', value: string): void
+}>()
 </script>
-
 <style scoped></style>

+ 49 - 43
src/views/ai/writer/index.vue

@@ -1,61 +1,67 @@
+<!-- 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="msgResult"
+      v-model:msg="writeResult"
     />
   </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 = () => {
-    abortController.value?.abort()
-    isWriting.value = false
-  }
+/** 停止 stream 生成 */
+const stopStream = () => {
+  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) => {
-    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
-    })
-  }
+/** 点击示例触发 */
+const handleExampleClick = (type: keyof typeof dataJson) => {
+  writeResult.value = dataJson[type].data
+}
 </script>