소스 검색

[代码优化]AI: 写作添加注释,增加可读性,调整代码,方便后续调整

hhhero 11 달 전
부모
커밋
5730707be9

+ 9 - 32
src/api/ai/writer/index.ts

@@ -3,37 +3,14 @@ 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:回复
-   */
-  type: 1 | 2
-  /**
-   * 写作内容提示 1。撰写 2回复
-   */
-  prompt: string
-  /**
-   *  原文
-   */
-  originalContent: string
-  /**
-   * 长度
-   */
-  length: number
-  /**
-   * 格式
-   */
-  format: number
-  /**
-   * 语气
-   */
-  tone: number
-  /**
-   * 语言
-   */
-  language: number
+export interface WriteVO {
+  type: 1 | 2 // 1:撰写 2:回复
+  prompt: string // 写作内容提示 1。撰写 2回复
+  originalContent: string // 原文
+  length: number // 长度
+  format: number // 格式
+  tone: number // 语气
+  language: number // 语言
 }
 
 export const writeStream = ({
@@ -43,7 +20,7 @@ export const writeStream = ({
   onError,
   ctrl
 }: {
-  data: WriteParams
+  data: WriteVO
   onMessage?: (res: any) => void
   onError?: (...args: any[]) => void
   onClose?: (...args: any[]) => void

+ 8 - 0
src/views/ai/utils/constants.ts

@@ -40,3 +40,11 @@ export const AiMusicStatusEnum = {
   SUCCESS: 20, // 已完成
   FAIL: 30 // 已失败
 }
+
+/**
+ * AI 写作类型的枚举
+ */
+export enum AiWriteTypeEnum {
+  WRITING = 1, // 撰写
+  REPLY // 回复
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 5 - 0
src/views/ai/utils/utils.ts


+ 42 - 50
src/views/ai/writer/components/Left.vue

@@ -24,7 +24,7 @@
     </h3>
   </DefineLabel>
 
-  <!-- TODO 小屏幕的时候是定位在左边的,大屏是分开的 -->
+  <!-- TODO @hhhero 小屏幕的时候是定位在左边的,大屏是分开的 -->
   <div class="relative" v-bind="$attrs">
     <!-- tab -->
     <div
@@ -32,7 +32,7 @@
     >
       <div
         class="flex items-center relative after:content-[''] after:block after:bg-white after:h-[30px] after:w-1/2 after:absolute after:top-0 after:left-0 after:transition-transform after:rounded-full"
-        :class="selectedTab === 2 && 'after:transform after:translate-x-[100%]'"
+        :class="selectedTab === AiWriteTypeEnum.REPLY && 'after:transform after:translate-x-[100%]'"
       >
         <ReuseTab
           v-for="tab in tabs"
@@ -53,7 +53,7 @@
             type="textarea"
             :rows="5"
             :maxlength="500"
-            v-model="writeForm.prompt"
+            v-model="formData.prompt"
             placeholder="请输入写作内容"
             showWordLimit
           />
@@ -65,7 +65,7 @@
             type="textarea"
             :rows="5"
             :maxlength="500"
-            v-model="writeForm.originalContent"
+            v-model="formData.originalContent"
             placeholder="请输入原文"
             showWordLimit
           />
@@ -75,20 +75,20 @@
             type="textarea"
             :rows="5"
             :maxlength="500"
-            v-model="writeForm.prompt"
+            v-model="formData.prompt"
             placeholder="请输入回复内容"
             showWordLimit
           />
         </template>
 
         <ReuseLabel label="长度" />
-        <Tag v-model="writeForm.length" :tags="writeTags.lenTags" />
+        <Tag v-model="formData.length" :tags="getIntDictOptions('ai_write_length')" />
         <ReuseLabel label="格式" />
-        <Tag v-model="writeForm.format" :tags="writeTags.formatTags" />
+        <Tag v-model="formData.format" :tags="getIntDictOptions('ai_write_format')" />
         <ReuseLabel label="语气" />
-        <Tag v-model="writeForm.tone" :tags="writeTags.toneTags" />
+        <Tag v-model="formData.tone" :tags="getIntDictOptions('ai_write_tone')" />
         <ReuseLabel label="语言" />
-        <Tag v-model="writeForm.language" :tags="writeTags.langTags" />
+        <Tag v-model="formData.language" :tags="getIntDictOptions('ai_write_language')" />
 
         <div class="flex items-center justify-center mt-3">
           <el-button :disabled="isWriting">重置</el-button>
@@ -103,12 +103,13 @@
 import { createReusableTemplate } from '@vueuse/core'
 import { ref } from 'vue'
 import Tag from './Tag.vue'
-import { WriteParams } from '@/api/ai/writer'
+import { WriteVO } from '@/api/ai/writer'
 import { omit } from 'lodash-es'
 import { getIntDictOptions } from '@/utils/dict'
-import dataJson from '../data.json'
+import { WriteExampleDataJson } from '@/views/ai/utils/utils'
+import { AiWriteTypeEnum } from "@/views/ai/utils/constants";
 
-type TabType = WriteParams['type']
+type TabType = WriteVO['type']
 
 const message = useMessage()
 
@@ -117,25 +118,25 @@ defineProps<{
 }>()
 
 const emits = defineEmits<{
-  (e: 'submit', params: Partial<WriteParams>)
+  (e: 'submit', params: Partial<WriteVO>)
   (e: 'example', param: 'write' | 'reply')
 }>()
 
 const example = (type: 'write' | 'reply') => {
-  writeForm.value = {
+  formData.value = {
     ...initData,
-    ...omit(dataJson[type], ['data'])
+    ...omit(WriteExampleDataJson[type], ['data'])
   }
   emits('example', type)
 }
 
-const selectedTab = ref<TabType>(1)
+const selectedTab = ref<TabType>(AiWriteTypeEnum.WRITING)
 const tabs: {
   text: string
   value: TabType
 }[] = [
-  { text: '撰写', value: 1 }, // TODO @hhhero:1、2 这个枚举到 constants 里。方便后续万一要调整
-  { text: '回复', value: 2 }
+  { text: '撰写', value: AiWriteTypeEnum.WRITING },
+  { text: '回复', value: AiWriteTypeEnum.REPLY }
 ]
 const [DefineTab, ReuseTab] = createReusableTemplate<{
   active?: boolean
@@ -143,7 +144,21 @@ const [DefineTab, ReuseTab] = createReusableTemplate<{
   itemClick: () => void
 }>()
 
-const initData: WriteParams = {
+/**
+ * 可以在template里边定义可复用的组件,DefineLabel,ReuseLabel是采用的解构赋值,都是Vue组件
+ * 直接通过组件的形式使用,<DefineLabel v-slot="{ label, hint, hintClick }">中间是需要复用的组件代码</DefineLabel>,通过<ReuseLabel />来使用定义的组件
+ * DefineLabel里边的v-slot="{ label, hint, hintClick }“相当于是解构了组件的prop,需要注意的是boolean类型,需要显式的赋值比如 <ReuseLabel :flag="true" />
+ * 事件也得以prop形式传入,不能是@event的形式,比如下面的hintClick需要<ReuseLabel :hintClick="() => { doSomething }"/>
+ * @see https://vueuse.org/createReusableTemplate
+ */
+const [DefineLabel, ReuseLabel] = createReusableTemplate<{
+  label: string
+  class?: string
+  hint?: string
+  hintClick?: () => void
+}>()
+
+const initData: WriteVO = {
   type: 1,
   prompt: '',
   originalContent: '',
@@ -152,49 +167,26 @@ const initData: WriteParams = {
   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 formData = ref<WriteVO>({ ...initData })
+/** 切换tab **/
 const switchTab = (value: TabType) => {
   selectedTab.value = value
-  writeForm.value = { ...initData }
+  formData.value = { ...initData }
 }
 
 const submit = () => {
-  if (selectedTab.value === 2 && !writeForm.value.originalContent) {
+  if (selectedTab.value === 2 && !formData.value.originalContent) {
     message.warning('请输入原文')
     return
   }
-  if (!writeForm.value.prompt) {
+  if (!formData.value.prompt) {
     message.warning(`请输入${selectedTab.value === 1 ? '写作' : '回复'}内容`)
     return
   }
   emits('submit', {
-    ...(selectedTab.value === 1 ? omit(writeForm.value, ['originalContent']) : writeForm.value),
+    /** 撰写的时候没有 originalContent 字段**/
+    ...(selectedTab.value === 1 ? omit(formData.value, ['originalContent']) : formData.value),
+    /** 使用选中tab值覆盖当前的type类型 **/
     type: selectedTab.value
   })
 }

+ 15 - 14
src/views/ai/writer/components/Right.vue

@@ -5,9 +5,8 @@
       <el-button
         color="#846af7"
         v-show="showCopy"
-        @click="copyMsg"
-        class="absolute top-2 right-2 copy-btn"
-        :data-clipboard-target="inputId"
+        @click="copyContent"
+        class="absolute top-2 right-2"
       >
         复制
       </el-button>
@@ -23,7 +22,7 @@
         <el-input
           id="inputId"
           type="textarea"
-          v-model="compMsg"
+          v-model="compContent"
           autosize
           :input-style="{ boxShadow: 'none' }"
           resize="none"
@@ -41,25 +40,27 @@ const message = useMessage()
 const { copied, copy } = useClipboard()
 
 const props = defineProps({
-  msg: {
+  content: {
+    // 生成的结果
     type: String,
     default: ''
   },
   isWriting: {
+    // 是否正在生成文章
     type: Boolean,
     default: false
   }
 })
 
-const emits = defineEmits(['update:msg', 'stopStream'])
+const emits = defineEmits(['update:content', 'stopStream'])
 
-// TODO @hhhero:是不是 Msg 改成 Content 这种哈。或者 Message。
-const compMsg = computed({
+// 通过计算属性,双向绑定,更改生成的内容,考虑到用户想要更改生成文章的情况
+const compContent = computed({
   get() {
-    return props.msg
+    return props.content
   },
   set(val) {
-    emits('update:msg', val)
+    emits('update:content', val)
   }
 })
 
@@ -72,12 +73,12 @@ defineExpose({
 })
 
 /** 点击复制的时候复制内容 */
-const showCopy = computed(() => props.msg && !props.isWriting) // 是否展示拷贝
-const inputId = computed(() => getCurrentInstance()?.uid) // TODO @hhhero:这个可以写个注释哈
-const copyMsg = () => {
-  copy(props.msg)
+const showCopy = computed(() => props.content && !props.isWriting) // 是否展示复制按钮,在生成内容完成的时候展示
+const copyContent = () => {
+  copy(props.content)
 }
 
+// 复制成功的时候copied.value为true
 watch(copied, (val) => {
   if (val) {
     message.success('复制成功')

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 3
src/views/ai/writer/data.json


+ 0 - 67
src/views/ai/writer/index.vue

@@ -1,67 +0,0 @@
-<!-- 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="handleExampleClick" />
-    <!-- TODO @hhhero:顶部应该有个预览的 header -->
-    <!-- TODO @hhhero:整个 Right 组件的框,没铺满的感觉? -->
-    <Right
-      :is-writing="isWriting"
-      @stop-stream="stopStream"
-      ref="rightRef"
-      class="flex-grow"
-      v-model:msg="writeResult"
-    />
-  </div>
-</template>
-
-<script setup lang="ts">
-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 writeResult = ref('') // 写作结果
-const isWriting = ref(false) // 是否正在写作中
-const abortController = ref<AbortController>() // // 写作进行中 abort 控制器(控制 stream 写作)
-
-/** 停止 stream 生成 */
-const stopStream = () => {
-  abortController.value?.abort()
-  isWriting.value = false
-}
-
-/** 执行写作 */
-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 handleExampleClick = (type: keyof typeof dataJson) => {
-  writeResult.value = dataJson[type].data
-}
-</script>

+ 67 - 0
src/views/ai/writer/index/index.vue

@@ -0,0 +1,67 @@
+<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="handleExampleClick" />
+    <!-- TODO @hhhero:顶部应该有个预览的 header -->
+    <!-- TODO @hhhero:整个 Right 组件的框,没铺满的感觉? -->
+    <Right
+      :is-writing="isWriting"
+      @stop-stream="stopStream"
+      ref="rightRef"
+      class="flex-grow"
+      v-model:content="writeResult"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import Left from '../components/Left.vue'
+  import Right from '../components/Right.vue'
+  import * as WriteApi from '@/api/ai/writer'
+  import { WriteExampleDataJson } from '@/views/ai/utils/utils'
+
+  const message = useMessage()
+
+  const writeResult = ref('') // 写作结果
+  const isWriting = ref(false) // 是否正在写作中
+  const abortController = ref<AbortController>() // // 写作进行中 abort 控制器(控制 stream 写作)
+
+  /** 停止 stream 生成 */
+  const stopStream = () => {
+    abortController.value?.abort()
+    isWriting.value = false
+  }
+
+  /** 执行写作 */
+  const rightRef = ref<InstanceType<typeof Right>>()
+  const submit = (data) => {
+    abortController.value = new AbortController()
+    writeResult.value = ''
+    isWriting.value = true
+    WriteApi.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: (...err) => {
+        console.error('写作异常', ...err)
+        stopStream()
+      }
+    })
+  }
+
+  /** 点击示例触发 */
+  const handleExampleClick = (type: keyof typeof WriteExampleDataJson) => {
+    writeResult.value = WriteExampleDataJson[type].data
+  }
+</script>

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.