腾讯云AI代码助手编程挑战赛-算法小助手

作品简介

一个可以帮助学习计算机各种算法的AI小助手,提升工作效率。

技术架构

使用Html语言完成图形化页面的样式,使用JavaScript语言来操作对应的逻辑代码。

实现过程

1、创建一个界面

2、获取数据

3、添加按钮与功能

4、程序优化调试

开发环境、开发流程

系统环境:MacOs系统

开发工具:VSCode

开发插件:腾讯云AI代码助手

关键技术解析

1.绘制页面

2.获取数据

3.解析数据

4.渲染数据

腾讯云AI代码助手在上述过程中的助力

1.生成页面

2.获取数据

3.处理数据

4.事件绑定执行

使用说明

提问各种算法问题,点击按钮,界面算法内容。

项目源码

TypeScript 复制代码
<template>
  <div class="chat-container">
    <t-chat
      ref="chatRef"
      layout="single"
      class="chat-window"
      :clear-history="chatList.length > 0 && !isStreamLoad"
      @clear="clearConfirm"
    >
      <template v-for="(item, index) in chatList" :key="index">
        <t-chat-item
          :avatar="item.avatar"
          :name="item.name"
          :role="item.role"
          :datetime="item.datetime"
          :text-loading="index === 0 && loading"
        >
          <template #content>
            <div
              :class="['chat-bubble', item.role === 'user' ? 'user-bubble' : 'assistant-bubble']"
              v-html="item.content"
            ></div>
          </template>
          <template v-if="!isStreamLoad" #actions>
            <t-chat-action
              :is-good="isGood[index]"
              :is-bad="isBad[index]"
              @operation="(type, { e }) => handleOperation(type, { e, index })"
              class="feedback-action"
            />
          </template>
        </t-chat-item>
      </template>

      <template #footer>
        <div class="input-area">
          <t-chat-input
            :stop-disabled="isStreamLoad"
            @send="inputEnter"
            @stop="onStop"
            placeholder="请输入您的问题..."
          />
          <t-button
            v-if="isStreamLoad"
            @click="onStop"
            type="danger"
            icon="close"
            circle
            class="stop-button"
          />
        </div>
      </template>
    </t-chat>

    <!-- 反馈表单对话框 -->
    <t-dialog
      v-model:visible="showFeedbackForm"
      :mask-closable="false"
      :show-close="false"
      width="450px"
      class="feedback-dialog"
    >
      <div class="feedback-content">
        <t-textarea
          v-model="feedbackContent"
          placeholder="您的宝贵建议对我们非常重要!您的反馈将帮助我们持续改进!"
          :rows="4"
          class="feedback-textarea"
        />
      </div>
      <template #footer>
        <t-button type="primary" @click="submitFeedback">提交反馈</t-button>
      </template>
    </t-dialog>

    <!-- 分享对话框 -->
    <t-dialog
      v-model:visible="showShareDialog"
      title="分享对话"
      :mask-closable="false"
      :show-close="false"
      width="400px"
      class="share-dialog"
    >
      <div class="share-options">
        <t-button
          variant="outline"
          icon="wechat"
          @click="shareToWeChatFriends"
          class="share-button"
        >
          微信好友
        </t-button>
        <t-button
          variant="outline"
          icon="wechat-moments"
          @click="shareToWeChatMoments"
          class="share-button"
        >
          朋友圈
        </t-button>
        <t-button
          variant="outline"
          icon="weibo"
          @click="shareToWeibo"
          class="share-button"
        >
          微博
        </t-button>
        <t-button
          variant="outline"
          icon="qq"
          @click="shareToQQZone"
          class="share-button"
        >
          QQ空间
        </t-button>
      </div>
    </t-dialog>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { saveAs } from 'file-saver';

// 反馈表单相关的状态
const showFeedbackForm = ref(false);
const feedbackType = ref(null); // 'good' 或 'bad'
const currentFeedbackIndex = ref(null);
const feedbackContent = ref('');

// 分享对话框相关的状态
const showShareDialog = ref(false);

// 滚动到底部
const chatRef = ref(null);
const backBottom = () => {
  chatRef.value.scrollToBottom({
    behavior: 'smooth',
  });
};

// 初始化聊天记录,仅保留初始机器人问候
const chatList = ref([
  {
    avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png', // 小美的头像链接
    name: '客服小Ai',
    datetime: new Date().toLocaleString(),
    content: '早上好,我是你的鹅厂助理小Ai,我熟知计算机各种算法和数据结构,请问你有什么帮助?',
    role: 'assistant',
  },
]);

// 初始化 isGood 和 isBad 数组
const isGood = ref([]);
const isBad = ref([]);

const initializeFeedbackStates = () => {
  isGood.value = chatList.value.map(() => false);
  isBad.value = chatList.value.map(() => false);
};

initializeFeedbackStates();

const fetchCancel = ref(null);
const loading = ref(false);
const isStreamLoad = ref(false);

/**
 * Clears the confirmation by resetting the chat list to an empty array.
 */
const clearConfirm = function () {
  chatList.value = [];
  initializeFeedbackStates();
};

/**
 * Handles the stop event for a fetch operation.
 */
const onStop = function () {
  if (fetchCancel.value) {
    fetchCancel.value.abort();
    loading.value = false;
    isStreamLoad.value = false;
  }
};

/**
 * Handles different types of operations based on the provided type and options.
 */
const handleOperation = function (type, options) {
  const { index } = options;
  if (type === 'good') {
    isGood.value[index] = !isGood.value[index];
    if (isGood.value[index]) {
      isBad.value[index] = false;
      // 打开反馈表单
      feedbackType.value = 'good';
      currentFeedbackIndex.value = index;
      showFeedbackForm.value = true;
    }
  } else if (type === 'bad') {
    isBad.value[index] = !isBad.value[index];
    if (isBad.value[index]) {
      isGood.value[index] = false;
      // 打开反馈表单
      feedbackType.value = 'bad';
      currentFeedbackIndex.value = index;
      showFeedbackForm.value = true;
    }
  }
};

/**
 * 提交反馈表单
 */
const submitFeedback = () => {
  if (currentFeedbackIndex.value === null) {
    MessagePlugin.error('未找到对应的消息!');
    return;
  }

  // 显示固定成功消息
  MessagePlugin.success('感谢您的反馈,我们将持续改进!');

  // 关闭反馈表单
  showFeedbackForm.value = false;
};

/**
 * Handles the asynchronous processing of user input data.
 */
const handleData = async (inputValue) => {
  loading.value = true;
  isStreamLoad.value = true;
  const lastItem = chatList.value[0];
  const messages = [{
    role: 'user',
    content: inputValue,
  }];
  fetchSSE(messages, {
    success(result) {
      loading.value = false;
      const { data } = result;
      lastItem.content += data?.delta?.content;
    },
    complete(isOk, msg) {
      if (!isOk || !lastItem.content) {
        lastItem.role = 'error';
        lastItem.content = msg;
      }
      // 控制终止按钮
      isStreamLoad.value = false;
      loading.value = false;
    },
    cancel(cancel) {
      fetchCancel.value = cancel;
    },
  });
};

/**
 * Handles the input when the enter key is pressed.
 */
const inputEnter = function (inputValue) {
  if (isStreamLoad.value) {
    return;
  }
  if (!inputValue) return;
  const params = {
    avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png', // 用户的头像链接
    name: '您',
    datetime: new Date().toLocaleString(),
    content: inputValue,
    role: 'user',
  };
  chatList.value.unshift(params);
  // 空消息占位
  const params2 = {
    avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png', // 小美的头像链接
    name: '客服小Ai',
    datetime: new Date().toLocaleString(),
    content: '',
    role: 'assistant',
  };
  chatList.value.unshift(params2);
  isGood.value.unshift(false);
  isBad.value.unshift(false);
  handleData(inputValue);
};

/**
 * 解析SSE数据
 */
const fetchSSE = async (messages, options) => {
  const { success, fail, complete, cancel } = options;
  const controller = new AbortController();
  const { signal } = controller;
  cancel?.(controller);
  // your-api-key
  const apiKey = 'sk-6R0hq8U7v3bSbT1u41Lp6kPRwAgf9wnW73WgvSC7WUI73eRO';
  const responsePromise = fetch('/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer${apiKey ? ` ${apiKey}` : ''}`,
    },
    body: JSON.stringify({
      messages, // 消息列表
      model: 'hunyuan-pro', // 模型
      stream: true, // 流式
    }),
    signal,
  }).catch((e) => {
    const msg = e.toString() || '流式接口异常';
    complete?.(false, msg);
    return Promise.reject(e); // 确保错误能够被后续的.catch()捕获
  });

  responsePromise
    .then((response) => {
      if (!response?.ok) {
        complete?.(false, response.statusText);
        fail?.();
        throw new Error('Request failed'); // 抛出错误以便链式调用中的下一个.catch()处理
      }
      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      if (!reader) throw new Error('No reader available');

      const bufferArr = [];
      let dataText = ''; // 记录数据
      const event = { type: null, data: null };

      async function processText({ done, value }) {
        if (done) {
          complete?.(true);
          return Promise.resolve();
        }
        const chunk = decoder.decode(value);
        const buffers = chunk.toString().split(/\r?\n/);
        bufferArr.push(...buffers);
        let i = 0;
        while (i < bufferArr.length) {
          const line = bufferArr[i];
          if (line) {
            dataText += line;
            const response = line.slice(6);
            if (response === '[DONE]') {
              event.type = 'finish';
              dataText = '';
            } else {
              try {
                const choices = JSON.parse(response.trim())?.choices?.[0];
                if (choices.finish_reason === 'stop') {
                  event.type = 'finish';
                  dataText = '';
                } else {
                  event.type = 'delta';
                  event.data = choices;
                }
              } catch (error) {
                console.error('解析错误:', error);
              }
            }
          }
          if (event.type && event.data) {
            const jsonData = { ...event };
            success(jsonData);
            event.type = null;
            event.data = null;
          }
          bufferArr.splice(i, 1);
        }
        return reader.read().then(processText);
      }

      return reader.read().then(processText);
    })
    .catch(() => {
      // 处理整个链式调用过程中发生的任何错误
      fail?.();
    });
};

/**
 * 下载对话记录
 */
const downloadConversation = () => {
  const conversation = chatList.value
    .map((msg) => {
      const time = new Date(msg.datetime).toLocaleString();
      // 去除HTML标签,确保文本格式清晰
      const content = msg.content.replace(/<[^>]+>/g, '');
      return `[${time}] ${msg.name} (${msg.role}): ${content}`;
    })
    .reverse() // 反转以按时间顺序排列
    .join('\n\n');

  const blob = new Blob([conversation], { type: 'text/plain;charset=utf-8' });
  saveAs(blob, 'conversation.txt');
};

/**
 * 分享对话记录(打开分享对话框)
 */
const shareConversation = async () => {
  openShareDialog();
};
</script>

<style lang="less" scoped>
/* 隐藏主题切换时的遮罩层 */
.t-dialog-mask, /* 可能的遮罩层类名 */
.theme-mask {
  display: none !important;
}

/* 新的整体容器样式 */
.chat-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  padding: 30px;
  box-sizing: border-box;
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

/* 工具栏样式更新,按钮统一样式和颜色 */
.toolbar {
  display: flex;
  justify-content: flex-end; /* 按钮靠右排列 */
  gap: 15px;
  margin-bottom: 25px;
}

.toolbar-button {
  background-color: #bae7ff;
  font-size: 16px;
  padding: 8px 16px;
  display: flex;
  align-items: center;
  gap: 6px;
}

/* 聊天窗口样式优化 */
.chat-window {
  flex: 1;
  border: none;
  border-radius: 16px;
  background-color: transparent;
  background-color: rgba(211, 211, 211, 0.5);  box-shadow: 0 8px 24px rgba(197, 235, 28, 0.1);
  overflow: hidden;
}

/* 聊天气泡的通用样式 */
.chat-bubble {
  max-width: 65%;
  padding: 16px 22px;
  border-radius: 30px;
  margin-bottom: 18px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
  word-break: break-word;
  font-size: 16px;
  line-height: 1.5;
}

/* 用户消息气泡 */
.user-bubble {
  background-color: #bae7ff; /* 更清新的蓝色 */
  align-self: flex-end;
  border-top-right-radius: 0;
}

/* 助手消息气泡 */
.assistant-bubble {
  background-color: #d9f7be; /* 柔和的绿色 */
  align-self: flex-start;
  border-top-left-radius: 0;
}

/* 反馈按钮样式修改 */
.feedback-action {
  display: flex;
  gap: 10px;
}

.feedback-action .t-button {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 14px;
  padding: 4px 8px;
  border-radius: 4px;
  transition: background-color 0.3s, color 0.3s;
}

.feedback-action .t-button:hover {
  background-color: rgba(24, 144, 255, 0.1);
}

.active-good {
  color: #52c41a; /* 赞按钮选中时的绿色 */
}

.active-bad {
  color: #ff4d4f; /* 踩按钮选中时的红色 */
}

/* 调整聊天底部布局 */
.input-area {
  display: flex;
  align-items: center;
  gap: 15px;
  padding: 10px 0;
  border-top: 1px solid #f0f0f0;
}

/* 停止按钮样式 */
.stop-button {
  background-color: #ff4d4f;
  border: none;
  width: 40px;
  height: 40px;
}

/* 更新后的反馈表单对话框样式 */
.feedback-dialog {
  .t-dialog__header {
    background-color: #1890ff; /* 蓝色头部 */
    color: #ffffff;
    border-top-left-radius: 16px;
    border-top-right-radius: 16px;
    font-size: 18px;
  }

  .t-dialog__body {
    padding: 25px;
    background-color: #f0f5ff; /* 淡蓝色背景 */
  }

  .t-dialog__footer {
    background-color: #f0f5ff;
    border-bottom-left-radius: 16px;
    border-bottom-right-radius: 16px;
  }
}

.feedback-content p {
  margin-bottom: 14px;
  font-size: 17px;
}

.feedback-content strong {
  color: #60b1fd;
}

.feedback-textarea {
  width: 100%;
  border: 1px solid #1890ff;
  border-radius: 8px;
  padding: 12px;
  resize: vertical;
  font-size: 16px;
  outline: none;
  transition: border-color 0.3s;
}

.feedback-textarea:hover,
.feedback-textarea:focus {
  border-color: #40a9ff;
}

/* 分享对话框样式 */
.share-dialog {
  .t-dialog__header {
    background-color: #40a9ff; /* 蓝色头部 */
    color: #ffffff;
    border-top-left-radius: 16px;
    border-top-right-radius: 16px;
    font-size: 18px;
    text-align: center;
  }

  .t-dialog__body {
    padding: 20px;
    background-color: #a8def7; /* 淡蓝色背景 */
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  .share-dialog__footer {
    display: none; /* 移除对话框底部 */
  }
}

.share-options {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.share-button {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 16px;
  padding: 10px 0;
  border-radius: 8px;
  transition: background-color 0.3s, color 0.3s;
}

.share-button:hover {
  background-color: rgba(24, 144, 255, 0.1);
}

/* 响应式设计 */
@media (max-width: 768px) {
  .chat-container {
    padding: 20px;
  }

  .toolbar {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }

  .chat-bubble {
    max-width: 80%;
  }

  .feedback-dialog,
  .share-dialog {
    width: 90% !important;
  }

  .toolbar-button {
    font-size: 14px;
    padding: 6px 12px;
  }

  .chat-bubble {
    padding: 14px 18px;
    margin-bottom: 16px;
    font-size: 15px;
  }

  .feedback-content p {
    font-size: 16px;
  }

  .feedback-textarea {
    padding: 10px;
    font-size: 15px;
  }

  .share-button {
    font-size: 14px;
    padding: 8px 0;
  }
}
</style>

效果展示

相关推荐
DisonTangor10 天前
【腾讯混元雪耻归来】 Hy4 preview:770B 参数 MoE 旗舰模型,1M 上下文全面开源
人工智能·算法·开源·aigc·腾讯云·腾讯云ai代码助手
Lalolander18 天前
WorkBuddy App 可以连接电脑吗?远程操控桌面端怎么用?
运维·电脑·腾讯云ai代码助手·workbuddy
朴实赋能20 天前
妇儿医院 AI 助手怎么落地?CareWork 本地化智能体的多 Agent 协同与合规边界设计
大数据·人工智能·腾讯云ai代码助手·ai 智能体·医疗多 agent 协同·妇儿医院 ai 助手·本地化部署 ai
MGS浪疯2 个月前
我用 WorkBuddy 做了一个能记住前文的 AI 小说编辑器
人工智能·编辑器·腾讯云·腾讯云ai代码助手·workbuddy开发者分享季·codebuddy开发者分享季
阿伟AI说3 个月前
Codex 桌面版接入国产模型系列二:Codex++
java·开源软件·ai编程·腾讯云ai代码助手
华万通信king3 个月前
腾讯云ADP Agent Portal入门:从零搭建企业级AI智能体
腾讯云·腾讯云ai代码助手·adp
wanmeijuhao3 个月前
腾讯云 AI 代码助手编程挑战赛 + 构建开发板垃圾图片识别AI对话的Copilot
人工智能·腾讯云·copilot·腾讯云ai代码助手
小明说Java4 个月前
Ollama本地安装基础教程
ollama·腾讯云ai代码助手
榮華5 个月前
DOTA全图透视辅助下载DOTA全图科技辅助下载DOTA外挂下载魔兽争霸WAR3全图下载
数据库·科技·游戏·游戏引擎·游戏程序·ai编程·腾讯云ai代码助手
Neolnfra6 个月前
QClaw:你的 AI 智能办公助手
腾讯云ai代码助手·openclaw·qclaw