目录

第一章 项目概述与核心特性

1.1 项目背景与意义

1.2 核心功能特性

第二章 环境与依赖准备

2.1 系统需求与Python环境

2.2 必需的Python库安装

2.3 API服务账户注册与配置

第三章 应用架构与核心代码解析

3.1 整体架构设计与类结构

3.2 Markdown处理引擎

3.3 UI界面构建与布局设计

3.4 核心通信机制

第四章 免费模型与基础使用

4.1 可用的免费模型列表

4.2 基础使用流程与最佳实践

第五章 付费模型配置与进阶使用

5.1 付费模型的种类与定价体系

5.2 修改代码以使用付费模型

5.3 不同分组下的模型选择指南

第六章 高级功能与参数优化

6.1 采样参数的作用与调整方法

6.2 系统提示词的设计与优化

6.3 对话历史管理与导出

第七章 故障排查与优化建议

7.1 常见问题与解决方案

7.2 性能优化与最佳实践

第八章 总结与展望


本文详细介绍如何将API独立站上的模型嵌入UI程序,打造自己的聊天机器人。API独立站点我就能打开

第一章 项目概述与核心特性

1.1 项目背景与意义

在人工智能大模型时代,越来越多的开发者和企业需要集成先进的AI模型到自己的应用中。然而,直接调用各个模型的官方API存在诸多痛点:需要分别注册多个平台账户、维护不同的API密钥、处理各异的接口规范、承担高昂的计费成本。为了解决这些问题,API聚合平台应运而生。本项目基于API镜像站这一高效的OpenAI接口聚合管理平台,融合了丰富的免费和付费模型资源,为用户提供了一套完整的、开箱即用的AI对话机器人解决方案。这套解决方案采用Python与Tkinter框架开发,打造了一个功能强大、界面友好的桌面应用程序,使得普通用户无需复杂的技术背景,就能轻松体验各种最先进的AI模型。

1.2 核心功能特性

这个AI对话机器人应用集成了众多实用功能,使其成为一个专业级的AI交互工具。首先,应用支持实时流式响应,用户输入的问题能够以流式形式返回AI的回答,提供了更加自然流畅的交互体验。其次,应用内置了完整的Markdown格式支持,可以正确渲染标题、加粗、斜体、代码块等各种排版元素,让AI的回答更加易读。第三,应用特别设计了代码提取功能,当AI在回答中包含代码段时,系统会自动识别并提取这些代码块到独立的选项卡中,方便用户查看和复制。此外,应用还提供了丰富的参数调整选项,包括温度、Top P等采样参数,允许用户根据不同场景灵活调整模型的生成行为。应用还支持完整的对话导出功能,可以将整个对话历史保存为文本文件以供日后查阅,同时支持从JSON文件导入设置,方便用户备份和恢复自己的配置。

第二章 环境与依赖准备

2.1 系统需求与Python环境

为了成功运行这个AI对话机器人应用,用户需要准备一个合适的开发环境。应用要求Python 3.7或以上版本,因为代码中使用了f-string等现代Python特性。在操作系统方面,由于应用使用了Tkinter作为GUI框架,理论上可以运行在Windows、macOS和Linux等多个平台上,但在实际使用中,Windows和macOS用户体验通常最佳。建议用户在安装Python时,确保勾选了"Add Python to PATH"选项,这样可以在命令行中直接调用Python。

2.2 必需的Python库安装

项目的核心依赖包括requests库用于发送HTTP请求、json库用于处理JSON数据、threading库用于多线程操作。这些库中,requests是唯一需要额外安装的第三方库,而json和threading已经包含在Python标准库中。用户可以通过以下命令安装所有必需的库:

pip install requests

对于需要使用最新版本或特定版本的requests库的用户,可以使用以下命令:

pip install --upgrade requests

如果用户所在的网络环境访问PyPI速度较慢,可以考虑使用国内镜像源,例如阿里云镜像或清华大学镜像。一个完整的带有镜像源的安装命令示例为:

pip install -i https://mirrors.aliyun.com/pypi/simple/ requests

2.3 API服务账户注册与配置

在开始使用本应用之前,用户必须在api.aigc.bar平台上注册账户并获取API密钥。注册过程非常直观:用户需要访问这个注册链接,按照页面提示填写基本信息如邮箱地址和密码,完成邮箱验证后即可注册成功。

注册完成后,用户进入令牌管理页面,根据实际需求添加令牌,将看到自己生成的API密钥。这个密钥是访问所有AI模型的凭证,用户需要妥善保管并在应用启动时输入。为了安全起见,不建议在代码中硬编码API密钥,而应该通过环境变量或配置文件的方式存储。本应用设计了一个自动保存机制,用户首次输入API密钥后,点击"保存"按钮,系统会将密钥保存到本地文件中,下次启动应用时会自动加载,无需重复输入。

如使用免费的,则使用这个:

如果使用高级点的模型,也可充值后使用付费分组。

第三章 应用架构与核心代码解析

3.1 整体架构设计与类结构

这个应用的设计采用了面向对象编程范式,由两个主要类组成:MarkdownFormatter和ChatBotUI。MarkdownFormatter类专门负责处理Markdown格式的文本转换和渲染,它包含了两个重要的静态方法,一个是extract_code_blocks方法,用于从AI返回的文本中识别并提取代码块,另一个是format_text方法,用于将Markdown格式的文本正确地插入到Tkinter的Text widget中,并应用相应的标签样式。ChatBotUI类是整个应用的主类,负责创建用户界面、处理用户交互、管理对话历史、与API通信等核心业务逻辑。这种分离的设计使得代码结构清晰,各个模块职责明确,易于维护和扩展。

3.2 Markdown处理引擎

Markdown处理引擎是这个应用的一个独特之处,它能够正确地识别和渲染各种Markdown格式元素。extract_code_blocks方法使用了正则表达式来识别代码块,具体使用的模式是r'```(?:python|py)?\s*\n(.*?)\n```',这个模式能够匹配三个反引号包围的代码块,并且支持可选的python语言标识符。该方法的核心逻辑是遍历所有匹配到的代码块,逐个提取其内容,同时删除原文本中的代码块标记,返回处理后的文本和代码块列表。这样做的好处是,AI返回的代码会被自动分离出来,可以在独立的选项卡中以高亮的代码编辑器样式显示,提升了代码的可读性。

format_text方法的工作原理更加复杂,它逐行处理文本,首先检查每一行是否是标题(以#开头),如果是标题则根据#的数量确定标题级别(1到3级),应用相应的标签样式。对于非标题行,方法会逐个字符地处理,在每个位置使用正则表达式同时检查多种格式标记:文本(加粗)、文本(斜体)、文本(代码)和链接文本(链接)。当发现格式标记时,取出最先出现的那个,应用相应的tag标签,然后继续处理剩余的文本。这种逐个处理的方式虽然计算量较大,但保证了格式的正确性,避免了格式标记之间的冲突。

3.3 UI界面构建与布局设计

应用的用户界面采用了左右分栏的经典布局设计。左侧是一个统一的参数配置面板,使用了LabelFrame容器来组织各个参数控制元素。这个面板包含了API密钥管理、模型选择、系统提示词、生成参数调整等各个功能区域。API密钥部分采用了一个Frame容器,在其中水平排列了一个Entry输入框、一个"保存"按钮和一个"注册"按钮,使得用户可以方便地输入密钥或快速跳转到注册页面。系统提示词使用了ScrolledText组件,这样即使提示词内容过长也能正常显示。参数调整部分(最大输出长度、温度、Top P)采用了Spinbox和Scale等专门的组件,提供了图形化的参数调整体验。

右侧是主要的聊天和代码展示区域,采用了Notebook(选项卡)组件来实现多选项卡的界面。默认情况下,应用启动时会创建一个"聊天"选项卡,显示聊天内容。当AI返回的内容中包含代码块时,应用会动态地添加新的代码选项卡,每个代码块对应一个独立的选项卡。这样的设计使得用户可以轻松地在聊天内容和代码之间切换查看,而无需在冗长的对话历史中来回翻滚。

3.4 核心通信机制

应用与API的通信采用了一个精心设计的流程。当用户点击"发送"按钮或按下Ctrl+Enter快捷键后,应用首先会验证API密钥是否已输入,然后检查用户是否正在等待上一条消息的回复。通过验证后,用户的消息会被添加到对话历史中,UI中会显示用户消息,输入框会被清空。接着,应用会设置一个标志位is_loading为True,禁用发送按钮,更新状态标签为"正在处理...",并在后台开启一个新的工作线程来处理API请求,避免阻塞主UI线程。

在工作线程中,应用构造了一个遵循OpenAI API格式的请求payload,包含了用户选择的模型、完整的消息历史(包括系统提示词)、各种采样参数。该payload被发送到api.aigc.bar的v1/chat/completions端点。应用使用了stream=True参数来启用流式响应,这意味着API会立即开始返回结果,而不是等待完整的响应生成。应用通过response.iter_lines()方法逐行迭代处理返回的数据,每一行都是一个符合Server-Sent Events格式的JSON对象。对于包含reasoning_content字段的数据块,应用将其识别为模型的思考过程(某些模型如DeepSeek推理模型会返回此字段),并以特殊的"thinking"样式显示。对于包含content字段的数据块,应用将其作为最终回答,以"assistant"样式显示。

第四章 免费模型与基础使用

4.1 可用的免费模型列表

api.aigc.bar平台为用户提供了多个免费使用的AI模型,这些模型涵盖了多个领域和能力水平。目前应用代码中预配置的免费模型包括以下几个:首先是GPT系列模型中的gpt-4.1-nano和gpt-5-nano这两个轻量级版本,这些模型虽然参数量较小,但在基础的文本生成和问题回答方面表现可靠,适合那些对响应速度有要求或输入量较小的场景。其次是DeepSeek系列的deepseek-r1-0528和deepseek-v3两个模型,其中r1模型是推理专向模型,特别擅长数学、编程等需要逻辑推理的任务,会返回详细的思考过程;deepseek-v3是多用途模型,在知识问答、文本生成等方面表现均衡。此外还有deepseek-v3.1,这是v3的一个改进版本。最后是gemini-2.5-flash-lite,这是Google最新推出的轻量化Gemini模型,在多模态理解和快速响应方面表现优异。这些免费模型的可用额度通常有限制,新用户一般会获得初始的免费试用额度,具体的额度和有效期需要用户在平台上查看。

4.2 基础使用流程与最佳实践

使用本应用的基础流程非常简洁。首先,用户启动应用后,在左侧参数面板的"API密钥"输入框中粘贴自己的API密钥,然后点击"保存"按钮,系统会将密钥保存到本地。其次,用户在"选择模型"下拉框中选择想要使用的模型,比如选择gpt-4.1-nano进行快速体验。如果需要自定义AI的行为风格,可以在"系统提示词"的大文本框中修改系统提示,比如可以将默认的中文助手改为特定行业的专家角色。然后用户在右侧的输入框中输入想要问的问题,点击"发送"按钮或按下Ctrl+Enter快捷键,应用就会向API发送请求。等待几秒钟后(具体时间取决于模型复杂度和网络延迟),AI的回答就会以流式的形式出现在上方的聊天展示区,用户可以实时看到回答的逐字生成过程。如果AI的回答中包含代码块,应用会自动创建新的代码选项卡,用户可以点击选项卡查看代码,右键选择"复制代码"将代码复制到剪贴板。

在实际使用中,为了获得更好的效果,用户应该注意以下几个最佳实践:首先,在提问时要尽可能清晰具体,提供充足的上下文信息,这样AI能够更准确地理解需求。其次,对于需要长篇幅回答或深度思考的问题,应该选择较高的max_tokens值(比如8192或更高),同时可以适当提高温度参数以获得更创意的回答。第三,对于对答案准确性有严格要求的场景(比如代码生成、数学计算),应该选择推理能力强的模型(如deepseek-r1)或设置较低的温度值(0.3-0.5)以获得更一致的回答。第四,如果需要维持一个连贯的多轮对话,应该避免频繁地清空对话历史,这样AI可以基于之前的上下文进行更一致的回答。

第五章 付费模型配置与进阶使用

5.1 付费模型的种类与定价体系

虽然API平台提供了多个免费模型供初级用户体验,但对于需要更强大能力的用户,平台还提供了多个付费模型选项。付费模型通常分为几个层级:基础层模型包括像gpt-5-mini这样的快速、经济的模型,适合日常文本生成和简单问答任务,价格最低廉;中端层模型包括gpt-4.1、claude-code等,这些模型具有更强的理解能力和生成质量,适合需要更高准确度的任务;高端层模型包括gpt-5.1、claude-4.5-opus、claude-4.5-sonnet等最新发布的模型,具有最强的能力,适合复杂的推理、创意写作等高端任务;专用模型则包括gemini-pro(Google的旗舰模型)、llama-2等开源模型的托管版本,这些模型在特定领域可能有独特的优势。平台通常采用按照输入token数和输出token数分别计费的模式,不同模型的价格差异较大,用户需要根据自己的预算和需求选择合适的模型。

5.2 修改代码以使用付费模型

要在应用中使用付费模型,用户需要修改ChatBotUI类中的models列表。原始的模型列表定义在__init__方法中,看起来如下:

self.models = [
    "gpt-4.1-nano",
    "gpt-5-nano",
    "deepseek-r1-0528",
    "deepseek-v3",
    "deepseek-v3.1",
    "gemini-2.5-flash-lite"
]

要添加付费模型,用户只需要在这个列表中添加新的模型字符串。例如,如果用户想添加OpenAI的GPT-4 Turbo模型和Claude 3.5 Sonnet模型,修改后的列表应该如下所示:

self.models = [
    "gpt-4.1-nano",
    "gpt-5-nano",
    "deepseek-r1-0528",
    "deepseek-v3",
    "deepseek-v3.1",
    "gemini-2.5-flash-lite",
    "gpt-4-turbo",
    "gpt-4-turbo-preview",
    "claude-3.5-sonnet",
    "claude-3-opus",
    "gemini-pro"
]

添加完模型后,当用户启动应用时,在"选择模型"下拉框中就会看到新增的模型选项。用户可以选择任何一个付费模型,然后像使用免费模型一样发送消息,应用会自动调用该模型的API。需要注意的是,一旦选择了付费模型,每一次API调用都会根据平台的定价产生费用。为了避免意外的大额消费,建议用户在实验新模型时先使用较小的max_tokens值,并且在平台上设置每日或每月的消费上限。

5.3 不同分组下的模型选择指南

在api.aigc.bar平台上,各个模型通常按照功能分组进行组织。第一个分组是通用文本生成模型组,这一组包括所有的GPT系列模型和通用的Claude模型,这些模型在各种任务上都有不错的表现,适合那些没有特殊需求的一般用户。用户如果不确定选择哪个模型,可以考虑从这个分组中选择。第二个分组是推理和编程专向模型组,这一组主要包括DeepSeek的r1和v3系列,以及OpenAI的GPT-4系列,这些模型在代码生成、数学推理、逻辑问题解决等方面表现特别突出,适合开发人员和科研工作者。如果用户的任务涉及编写复杂代码或解决深层次的逻辑问题,应该优先选择这个分组中的模型。第三个分组是多模态模型组,包括Google的Gemini系列和其他支持图像输入的模型,虽然本应用的当前版本主要处理文本,但这些模型的文本生成能力也很强,在需要更全面理解的场景下表现更好。

对于不同场景,用户有以下建议的选择策略:如果进行日常问答和文本生成,推荐使用gpt-4.1-nano或gpt-5-nano这样的免费轻量级模型,既能保证质量又能节省成本。如果需要编写代码或解决数学问题,推荐使用deepseek-r1-0528或deepseek-v3,因为这两个模型在代码质量和推理准确性上都很突出,而且deepseek-r1-0528会返回详细的思考过程,帮助用户理解AI的推理逻辑。如果追求最好的输出质量且预算充足,可以选择付费的gpt-5.2或claude-4.5-sonnet,这两个模型在创意写作、深度分析等复杂任务上能力最强。如果进行快速原型设计或对延迟有严格要求,可以选择gemini-2.5-flash-lite或其他标注为"flash"的轻量版本,这些模型的响应速度最快。

第六章 高级功能与参数优化

6.1 采样参数的作用与调整方法

应用中提供的温度(Temperature)和Top P两个采样参数是控制模型生成行为的关键因素。温度参数的取值范围是0到2,它控制了模型生成结果的随机性程度。当温度设置为0时,模型会采用最贪心的策略,总是选择概率最高的下一个token,生成的结果最确定、最可预测,适合对答案准确性要求很高的场景如代码生成或数据提取。当温度设置为1时,这是一个平衡点,模型会以正常的概率分布进行采样。当温度设置为更高的值(如1.5或2)时,模型会倾向于选择概率较低但更多样化的token,生成的结果会更富创意、更多样化,适合创意写作或头脑风暴场景。Top P参数的取值范围是0到1,它采用核采样的策略,表示只从累计概率达到P的最可能的token中进行采样。例如,当Top P设置为0.9时,模型只会在那些累计概率达到90%的token中进行选择,这样可以避免选择极低概率的离奇token,同时保留一定的多样性。

在实际使用中,一个常用的参数组合是温度0.7、Top P 0.9,这个组合在多数场景下提供了很好的平衡。当用户发现模型的回答显得过于保守或重复时,可以尝试提高温度值或降低Top P值。相反,当模型的回答显得过于随意或与问题偏离时,应该降低温度或提高Top P值。

6.2 系统提示词的设计与优化

系统提示词(System Prompt)是指导AI行为的关键指令。应用中默认的系统提示词是"你是一个专业、友善且富有创意的AI助手。你会用中文回答用户的问题。"这个提示词设置了基本的角色定义和语言偏好。但用户可以根据实际需求定制系统提示词,来引导AI产生特定风格或领域的回答。例如,如果用户想让AI扮演一个C++编程专家的角色,可以将系统提示词改为"你是一位资深的C++工程师,拥有20年的软件开发经验。当用户提出关于C++的问题时,你应该从实战经验出发,提供深入、专业的解答,并给出最佳实践建议。"又或者,如果用户想让AI充当英文翻译助手,可以设置"你是一位资深的翻译工作者,精通英文和中文。你的任务是准确、优雅地将用户提供的文本翻译成指定的语言,保留原文的含义和风格。"这样的系统提示词。设计好的系统提示词应该包括以下几个要素:清晰的角色定义(你是什么角色)、明确的任务目标(你要做什么)、输出格式要求(如果需要)、以及任何特殊的限制或偏好。

6.3 对话历史管理与导出

应用实现了完整的对话历史管理功能。在后台,应用使用一个列表来存储所有的对话消息,每条消息包括"role"(消息发送者的角色,可以是"user"或"assistant")和"content"(消息的实际内容)两个字段。这个历史列表在内存中维护,用户与AI的每一条往来都会被记录。当用户清空对话时,这个列表会被重置。应用还提供了"导出对话"功能,用户点击这个按钮后,应用会打开一个文件保存对话框,用户可以选择保存位置和文件名。导出的文件是纯文本格式,包含了对话时间戳、使用的模型名称以及完整的对话内容,这样用户可以保存重要的对话记录供日后参考,或者用于分享讨论或学习。

应用还支持"导入设置"功能,这允许用户从一个JSON文件中加载之前保存的配置。用户可以手动编辑这样的JSON文件来快速设置多个配置方案。一个典型的设置JSON文件看起来如下:

{
    "api_key": "sk-xxxxxxxxxxxx",
    "model": "deepseek-v3",
    "system_prompt": "你是一位资深的Python编程专家",
    "max_tokens": 8192,
    "temperature": 0.7,
    "top_p": 0.9
}

用户可以为不同的使用场景创建多个这样的配置文件,然后根据需要导入不同的配置,这样可以大大提高工作效率。

第七章 故障排查与优化建议

7.1 常见问题与解决方案

在使用过程中,用户可能会遇到各种问题。首先是连接错误,如果应用显示"连接失败",通常意味着无法连接到API站的服务器。这可能是由于网络问题、DNS解析失败或API服务器暂时不可用导致的。解决方法是检查网络连接是否正常(可以尝试用浏览器打开这个API独立站确认),确认API地址是否正确,如果还是无法连接可以尝试更换网络或使用VPN。其次是超时错误,应用的默认超时时间是120秒,如果在这个时间内没有收到完整的响应,就会显示超时错误。这通常发生在max_tokens设置过高、网络延迟大或API服务器响应慢的情况下。解决方法是降低max_tokens值,或者选择一个更轻量化的模型,或者等待一段时间后重试。第三个常见问题是API错误,应用会显示具体的错误码和错误信息。最常见的是401错误(未授权),这表示API密钥无效或已过期,需要检查API密钥是否正确输入。还有429错误(请求过于频繁),这表示API请求频率超过了平台的限制,需要降低请求频率。

7.2 性能优化与最佳实践

为了获得最佳的使用体验,用户应该注意以下几个优化点。首先,合理设置max_tokens参数,不要盲目设置为最大值。对于一般的问答任务,2048或4096的max_tokens通常就足够了,只有在需要生成长篇幅内容时才需要设置更高的值。较低的max_tokens不仅会提高响应速度,还会降低API调用成本。其次,在切换模型时要考虑速度和质量的权衡。轻量级模型(nano版本、flash版本)响应速度快但生成质量可能较低,适合快速原型和实时交互。复杂模型(turbo版本、opus版本)生成质量高但响应较慢,适合对质量有要求的任务。第三,利用系统提示词来约束模型的行为,一个好的系统提示词可以大大提升回答的相关性和准确性,减少冗余内容,从而提高token利用效率和成本效益。第四,定期检查api.aigc.bar平台上的消费统计和余额,设置合理的日均或月均消费上限,避免因为过度使用导致意外的费用。

第八章 总结与展望

本文详细介绍了如何基于api.aigc.bar平台和Python Tkinter框架,搭建一个功能完整、易于使用的AI对话机器人应用。从环境准备、基础使用、到高级功能,我们覆盖了使用者可能需要了解的各个方面。通过这个应用,用户可以轻松地体验各种免费和付费的先进AI模型,包括OpenAI的GPT系列、DeepSeek的推理模型、Google的Gemini模型等。应用内置的Markdown渲染、代码提取、流式响应等特性,使得与AI的交互更加高效和愉快。

展望未来,这个应用还有很多潜在的扩展方向。例如,可以添加多文件上传和处理能力,让用户可以直接在应用中分析文档、图片等多种文件格式。可以集成本地模型支持,让用户选择在本地运行轻量级模型而不仅仅依赖云端API。可以添加插件系统,让第三方开发者为应用开发各种功能扩展。可以实现更智能的对话管理,比如自动摘要、主题提取等功能。随着AI技术的不断进步,API站这样的聚合平台会集成越来越多的新模型,而这个应用框架已经为这样的扩展做好了准备。希望通过本文,开发者和用户能够充分利用这个强大的工具,探索AI的无限可能性。

窗体程序完整代码实现如下:

import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox, filedialog
import requests
import json
import threading
from datetime import datetime
import os
import re
import webbrowser


class MarkdownFormatter:
    """处理Markdown格式化"""

    @staticmethod
    def extract_code_blocks(text):
        """提取代码块,返回 (非代码文本, 代码块列表)"""
        code_blocks = []
        # 更灵活的正则表达式,支持多种格式
        pattern = r'```(?:python|py)?\s*\n(.*?)\n```'

        matches = list(re.finditer(pattern, text, flags=re.DOTALL))

        # 提取所有代码块
        for match in matches:
            code_content = match.group(1).strip()
            if code_content:
                code_blocks.append(code_content)

        # 替换代码块为空字符串,保留其他文本
        text_without_code = re.sub(pattern, '', text, flags=re.DOTALL)

        return text_without_code.strip(), code_blocks

    @staticmethod
    def format_text(widget, text, base_tag=""):
        """将Markdown格式的文本插入到Text widget"""
        widget.config(state=tk.NORMAL)

        # 分行处理
        lines = text.split('\n')
        for line_idx, line in enumerate(lines):
            # 处理标题 # ## ###
            heading_match = re.match(r'^(#{1,6})\s+(.+)$', line)
            if heading_match:
                level = min(len(heading_match.group(1)), 3)
                content = heading_match.group(2)
                widget.insert(tk.END, content, f"heading{level}")
                if line_idx < len(lines) - 1:
                    widget.insert(tk.END, '\n')
                continue

            # 处理该行中的Markdown格式
            pos = 0
            while pos < len(line):
                # 查找所有格式
                bold_match = re.search(r'\*\*(.+?)\*\*', line[pos:])
                italic_match = re.search(r'(?<!\*)\*([^*]+?)\*(?!\*)', line[pos:])
                code_match = re.search(r'`([^`]+?)`', line[pos:])
                link_match = re.search(r'\[([^\]]+)\]\(([^\)]+)\)', line[pos:])

                matches = []
                if bold_match:
                    matches.append(('bold', bold_match.start(), bold_match.end(), bold_match.group(1), '**'))
                if italic_match:
                    matches.append(('italic', italic_match.start(), italic_match.end(), italic_match.group(1), '*'))
                if code_match:
                    matches.append(('code', code_match.start(), code_match.end(), code_match.group(1), '`'))
                if link_match:
                    matches.append(('link', link_match.start(), link_match.end(), link_match.group(1), 'link'))

                if not matches:
                    # 没有格式,直接插入剩余文本
                    widget.insert(tk.END, line[pos:], base_tag)
                    break

                # 找到最早出现的匹配
                matches.sort(key=lambda x: x[1])
                match_type, start, end, content, marker = matches[0]

                # 插入匹配前的文本
                if start > 0:
                    widget.insert(tk.END, line[pos:pos + start], base_tag)

                # 插入格式化的文本
                if match_type == 'link':
                    widget.insert(tk.END, content, 'link')
                else:
                    widget.insert(tk.END, content, match_type)

                pos += end

            # 添加换行符(除了最后一行)
            if line_idx < len(lines) - 1:
                widget.insert(tk.END, '\n')

        widget.config(state=tk.DISABLED)


class ChatBotUI:
    def __init__(self, root):
        self.root = root
        self.root.title("AI聊天机器人")
        self.root.geometry("1600x800")

        self.api_key = ""
        self.api_url = "https://api.aigc.bar/v1/chat/completions"
        self.register_url = "https://api.aigc.bar/register?aff=UP4F"
        self.tutorial_url = "https://blog.csdn.net/nmdbbzcl/article/details/156234830"
        self.api_key_file = os.path.join(os.getcwd(), "api_key.txt")

        self.models = [
            "gpt-4.1-nano",
            "gpt-5-nano",
            "deepseek-r1-0528",
            "deepseek-v3",
            "deepseek-v3.1",
            "gemini-2.5-flash-lite"
        ]

        self.conversation_history = []
        self.is_loading = False
        self.code_tab_count = 0
        self.setup_fonts()
        self.load_api_key()
        self.create_ui()

    def setup_fonts(self):
        """设置中文字体"""
        try:
            self.chinese_font = ("微软雅黑", 10)
            self.title_font = ("微软雅黑", 11, "bold")
            self.large_font = ("微软雅黑", 12, "bold")
            self.code_font = ("Courier New", 10)
        except:
            try:
                self.chinese_font = ("WenQuanYi Micro Hei", 10)
                self.title_font = ("WenQuanYi Micro Hei", 11, "bold")
                self.large_font = ("WenQuanYi Micro Hei", 12, "bold")
                self.code_font = ("Courier New", 10)
            except:
                self.chinese_font = ("Helvetica", 10)
                self.title_font = ("Helvetica", 11, "bold")
                self.large_font = ("Helvetica", 12, "bold")
                self.code_font = ("Courier New", 10)

    def load_api_key(self):
        """从文件加载API密钥"""
        try:
            if os.path.exists(self.api_key_file):
                with open(self.api_key_file, 'r', encoding='utf-8') as f:
                    api_key = f.read().strip()
                    if api_key:
                        self.api_key = api_key
        except Exception as e:
            print(f"加载API密钥失败: {e}")

    def save_api_key(self, api_key):
        """保存API密钥到文件"""
        try:
            if api_key:
                with open(self.api_key_file, 'w', encoding='utf-8') as f:
                    f.write(api_key)
                return True
        except Exception as e:
            print(f"保存API密钥失败: {e}")
            messagebox.showerror("错误", f"保存API密钥失败:{str(e)}")
            return False
        return False

    def create_ui(self):
        """创建用户界面"""
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)

        # 左侧配置面板
        left_frame = ttk.LabelFrame(main_frame, text="参数配置", padding=12)
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, padx=5, pady=5)
        left_frame.config(width=300)

        # API密钥部分
        ttk.Label(left_frame, text="API密钥:", font=self.title_font).pack(anchor=tk.W, pady=(5, 2))
        api_key_frame = ttk.Frame(left_frame)
        api_key_frame.pack(anchor=tk.W, pady=(0, 5), fill=tk.X)

        self.api_key_var = tk.StringVar(value=self.api_key)
        self.api_key_entry = ttk.Entry(api_key_frame, textvariable=self.api_key_var,
                                       width=20, font=self.chinese_font)
        self.api_key_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 2))

        ttk.Button(api_key_frame, text="保存", command=self.update_api_key, width=6).pack(side=tk.LEFT, padx=2)
        ttk.Button(api_key_frame, text="注册",
                   command=self.open_register_page, width=6).pack(side=tk.LEFT, padx=2)

        ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)

        ttk.Label(left_frame, text="选择模型:", font=self.title_font).pack(anchor=tk.W, pady=(5, 2))
        self.model_var = tk.StringVar(value=self.models[0])
        model_combo = ttk.Combobox(left_frame, textvariable=self.model_var,
                                   values=self.models, state="readonly", width=25, font=self.chinese_font)
        model_combo.pack(anchor=tk.W, pady=(0, 10), fill=tk.X)

        ttk.Label(left_frame, text="系统提示词 (System Prompt):", font=self.title_font).pack(anchor=tk.W, pady=(5, 2))
        self.system_prompt = scrolledtext.ScrolledText(left_frame, height=7, width=30,
                                                       font=self.chinese_font, wrap=tk.WORD)
        self.system_prompt.pack(pady=(0, 10), fill=tk.BOTH, expand=True)
        self.system_prompt.insert(tk.END, "你是一个专业、友善且富有创意的AI助手。\n你会用中文回答用户的问题。")

        ttk.Label(left_frame, text="最大输出长度 (tokens):", font=self.title_font).pack(anchor=tk.W, pady=(5, 2))
        max_tokens_frame = ttk.Frame(left_frame)
        max_tokens_frame.pack(anchor=tk.W, pady=(0, 10), fill=tk.X)
        self.max_tokens_var = tk.StringVar(value="16384")
        ttk.Spinbox(max_tokens_frame, from_=100, to=16384, textvariable=self.max_tokens_var,
                    width=10, font=self.chinese_font).pack(side=tk.LEFT)
        ttk.Label(max_tokens_frame, text="(100-16384)", font=self.chinese_font).pack(side=tk.LEFT, padx=5)

        ttk.Label(left_frame, text="温度 (Temperature):", font=self.title_font).pack(anchor=tk.W, pady=(5, 2))
        temp_frame = ttk.Frame(left_frame)
        temp_frame.pack(anchor=tk.W, pady=(0, 10), fill=tk.X)
        self.temperature_var = tk.StringVar(value="0.7")
        temp_scale = ttk.Scale(temp_frame, from_=0, to=2, orient=tk.HORIZONTAL,
                               variable=self.temperature_var, command=self.update_temp_label)
        temp_scale.pack(side=tk.LEFT, fill=tk.X, expand=True)
        self.temp_label = ttk.Label(temp_frame, text="0.7", width=5, font=self.chinese_font)
        self.temp_label.pack(side=tk.LEFT, padx=5)

        ttk.Label(left_frame, text="Top P:", font=self.title_font).pack(anchor=tk.W, pady=(5, 2))
        topp_frame = ttk.Frame(left_frame)
        topp_frame.pack(anchor=tk.W, pady=(0, 15), fill=tk.X)
        self.top_p_var = tk.StringVar(value="0.9")
        topp_scale = ttk.Scale(topp_frame, from_=0, to=1, orient=tk.HORIZONTAL,
                               variable=self.top_p_var, command=self.update_topp_label)
        topp_scale.pack(side=tk.LEFT, fill=tk.X, expand=True)
        self.topp_label = ttk.Label(topp_frame, text="0.9", width=5, font=self.chinese_font)
        self.topp_label.pack(side=tk.LEFT, padx=5)

        button_frame = ttk.Frame(left_frame)
        button_frame.pack(anchor=tk.W, pady=(10, 0), fill=tk.X)
        ttk.Button(button_frame, text="清空对话", command=self.clear_conversation).pack(fill=tk.X, pady=2)
        ttk.Button(button_frame, text="导出对话", command=self.export_chat).pack(fill=tk.X, pady=2)
        ttk.Button(button_frame, text="导入设置", command=self.import_settings).pack(fill=tk.X, pady=2)
        ttk.Button(button_frame, text="使用教程", command=self.open_tutorial_page).pack(fill=tk.X, pady=2)

        ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
        ttk.Label(left_frame, text="状态:", font=self.title_font).pack(anchor=tk.W, pady=(5, 2))
        self.status_label = ttk.Label(left_frame, text="就绪", font=self.chinese_font, foreground="green")
        self.status_label.pack(anchor=tk.W)

        # 右侧主面板(包含聊天和代码选项卡)
        right_frame = ttk.LabelFrame(main_frame, text="聊天与代码", padding=10)
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)

        # 创建选项卡容器
        self.main_notebook = ttk.Notebook(right_frame)
        self.main_notebook.pack(fill=tk.BOTH, expand=True, pady=(0, 10))

        # 聊天选项卡
        chat_tab = ttk.Frame(self.main_notebook)
        self.main_notebook.add(chat_tab, text="聊天")

        self.chat_display = scrolledtext.ScrolledText(chat_tab, height=25, width=70,
                                                      font=self.chinese_font, wrap=tk.WORD,
                                                      state=tk.DISABLED, bg="#f5f5f5")
        self.chat_display.pack(fill=tk.BOTH, expand=True)

        # 标签配置
        self.chat_display.tag_config("user", foreground="#0066cc",
                                     font=(self.chinese_font[0], self.chinese_font[1], "bold"))
        self.chat_display.tag_config("assistant", foreground="#009900",
                                     font=(self.chinese_font[0], self.chinese_font[1], "bold"))
        self.chat_display.tag_config("thinking", foreground="#FF9500",
                                     font=(self.chinese_font[0], self.chinese_font[1], "bold"))
        self.chat_display.tag_config("error", foreground="#cc0000",
                                     font=(self.chinese_font[0], self.chinese_font[1], "bold"))
        self.chat_display.tag_config("timestamp", foreground="#999999", font=(self.chinese_font[0], 9))

        # Markdown标签
        self.chat_display.tag_config("bold", font=(self.chinese_font[0], self.chinese_font[1], "bold"))
        self.chat_display.tag_config("italic", font=(self.chinese_font[0], self.chinese_font[1], "italic"))
        self.chat_display.tag_config("code", foreground="#d63384", background="#f8f9fa", font=("Courier", 9))
        self.chat_display.tag_config("heading1", font=(self.chinese_font[0], 14, "bold"), foreground="#000080")
        self.chat_display.tag_config("heading2", font=(self.chinese_font[0], 12, "bold"), foreground="#000080")
        self.chat_display.tag_config("heading3", font=(self.chinese_font[0], 11, "bold"), foreground="#000080")
        self.chat_display.tag_config("link", foreground="#0066cc", underline=True)

        # 输入区域
        ttk.Label(right_frame, text="输入消息 (Ctrl+Enter 快速发送):", font=self.title_font).pack(anchor=tk.W,
                                                                                                  pady=(5, 2))

        self.input_text = tk.Text(right_frame, height=5, width=70, font=self.chinese_font, wrap=tk.WORD)
        self.input_text.pack(fill=tk.BOTH, padx=0, pady=(0, 8))
        self.input_text.bind("<Control-Return>", lambda e: self.send_message())

        button_frame_right = ttk.Frame(right_frame)
        button_frame_right.pack(fill=tk.X, padx=0, pady=0)

        self.send_button = ttk.Button(button_frame_right, text="发送", command=self.send_message)
        self.send_button.pack(side=tk.LEFT, padx=3)
        ttk.Button(button_frame_right, text="清空输入", command=self.clear_input).pack(side=tk.LEFT, padx=3)
        ttk.Button(button_frame_right, text="复制最后回复", command=self.copy_last_response).pack(side=tk.LEFT, padx=3)

    def add_code_tab(self, code_content):
        """添加代码选项卡"""
        self.code_tab_count += 1
        tab_name = f"代码 {self.code_tab_count}"

        try:
            code_frame = ttk.Frame(self.main_notebook)
            self.main_notebook.add(code_frame, text=tab_name)

            # 创建代码显示区域
            code_display = scrolledtext.ScrolledText(code_frame, font=self.code_font,
                                                     wrap=tk.NONE, bg="#2b2b2b",
                                                     fg="#f8f8f2", insertbackground="white")
            code_display.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

            # 插入代码内容
            code_display.insert(tk.END, code_content)
            code_display.config(state=tk.DISABLED)

            # 添加右键菜单复制功能
            def copy_code():
                self.root.clipboard_clear()
                self.root.clipboard_append(code_content)
                self.status_label.config(text="代码已复制到剪贴板", foreground="blue")

            popup_menu = tk.Menu(code_display, tearoff=0)
            popup_menu.add_command(label="复制代码", command=copy_code)

            def show_popup(e):
                try:
                    popup_menu.tk_popup(e.x_root, e.y_root)
                finally:
                    popup_menu.grab_release()

            code_display.bind("<Button-3>", show_popup)
        except Exception as e:
            print(f"添加代码选项卡出错: {e}")

    def open_register_page(self):
        """打开注册页面"""
        webbrowser.open(self.register_url)

    def open_tutorial_page(self):
        """打开使用教程页面"""
        webbrowser.open(self.tutorial_url)

    def update_api_key(self):
        """更新并保存API密钥"""
        new_api_key = self.api_key_var.get().strip()
        if not new_api_key:
            messagebox.showwarning("提示", "请输入API密钥")
            return

        if self.save_api_key(new_api_key):
            self.api_key = new_api_key
            messagebox.showinfo("成功", "API密钥已保存")
            self.status_label.config(text="API密钥已保存", foreground="blue")

    def update_temp_label(self, value):
        """更新温度标签"""
        self.temp_label.config(text=f"{float(value):.2f}")

    def update_topp_label(self, value):
        """更新Top P标签"""
        self.topp_label.config(text=f"{float(value):.2f}")

    def send_message(self):
        """发送消息"""
        # 获取当前API密钥(从输入框获取)
        current_api_key = self.api_key_var.get().strip()
        if not current_api_key:
            messagebox.showerror("错误", "请先填写API密钥或点击注册按钮获取密钥")
            return

        # 更新实例变量
        self.api_key = current_api_key

        if self.is_loading:
            messagebox.showwarning("提示", "正在等待上一条消息的回复,请稍候...")
            return

        user_message = self.input_text.get("1.0", tk.END).strip()

        if not user_message:
            messagebox.showwarning("提示", "请输入消息")
            return

        self.display_message("你", user_message, "user")
        self.clear_input()

        self.is_loading = True
        self.send_button.config(state=tk.DISABLED)
        self.status_label.config(text="正在处理...", foreground="orange")
        self.root.update()

        threading.Thread(target=self.get_response, args=(user_message,), daemon=True).start()

    def get_response(self, user_message):
        """获取AI响应(在工作线程中执行)"""
        try:
            self.conversation_history.append({
                "role": "user",
                "content": user_message
            })

            headers = {
                "Authorization": f"Bearer {self.api_key}",
                "Content-Type": "application/json"
            }

            messages = []
            system_prompt = self.system_prompt.get("1.0", tk.END).strip()
            if system_prompt:
                messages.append({
                    "role": "system",
                    "content": system_prompt
                })

            if len(self.conversation_history) > 1:
                messages.extend(self.conversation_history[-2:])
            else:
                messages.extend(self.conversation_history)

            data = {
                "model": self.model_var.get(),
                "messages": messages,
                "max_tokens": int(self.max_tokens_var.get()),
                "temperature": float(self.temperature_var.get()),
                "top_p": float(self.top_p_var.get()),
                "stream": True
            }

            response = requests.post(self.api_url, headers=headers, json=data, timeout=120, stream=True)

            if response.status_code != 200:
                error_msg = f"API错误 {response.status_code}\n"
                try:
                    error_json = response.json()
                    error_msg += json.dumps(error_json, ensure_ascii=False, indent=2)
                except:
                    error_msg += response.text

                self.display_message("错误", error_msg, "error")
                self.status_label.config(text="错误", foreground="red")
                return

            # 处理流式响应
            assistant_message = ""
            reasoning_content = ""
            timestamp = datetime.now().strftime("%H:%M:%S")

            thinking_shown = False
            content_started = False
            update_count = 0

            self.chat_display.config(state=tk.NORMAL)
            self.chat_display.insert(tk.END, f"[{timestamp}] ", "timestamp")

            for line in response.iter_lines():
                if not line:
                    continue

                line = line.decode('utf-8') if isinstance(line, bytes) else line
                line = line.strip()

                if line == "[DONE]" or line == "data: [DONE]":
                    break

                if line.startswith("data: "):
                    data_str = line[6:]
                elif line.startswith("{"):
                    data_str = line
                else:
                    continue

                try:
                    chunk = json.loads(data_str)

                    if "error" in chunk:
                        error_msg = chunk.get("error", {}).get("message", "未知错误")
                        self.chat_display.insert(tk.END, f"流式错误: {error_msg}\n", "error")
                        break

                    choices = chunk.get("choices", [])
                    if not choices or len(choices) == 0:
                        continue

                    delta = choices[0].get("delta", {})

                    # 处理reasoning_content (思考过程)
                    reasoning = delta.get("reasoning_content")
                    if reasoning:
                        if not thinking_shown:
                            self.chat_display.insert(tk.END, "思考过程:\n", "thinking")
                            thinking_shown = True

                        reasoning_content += reasoning
                        self.chat_display.insert(tk.END, reasoning)

                    # 处理content (最终内容)
                    content = delta.get("content")
                    if content:
                        if not content_started:
                            if thinking_shown:
                                self.chat_display.insert(tk.END, "\n\n最终回复:\n", "assistant")
                            else:
                                self.chat_display.insert(tk.END, "AI:\n", "assistant")
                            content_started = True

                        assistant_message += content
                        self.chat_display.insert(tk.END, content)

                    # 每5个数据块更新一次UI
                    update_count += 1
                    if update_count % 5 == 0:
                        self.chat_display.see(tk.END)
                        self.root.update()

                except json.JSONDecodeError:
                    continue

            # 完成流式接收
            self.chat_display.insert(tk.END, "\n\n")
            self.chat_display.see(tk.END)
            self.chat_display.config(state=tk.DISABLED)
            self.root.update()

            # 处理回复和代码块
            if assistant_message:
                # 提取代码块
                text_without_code, code_blocks = MarkdownFormatter.extract_code_blocks(assistant_message)

                # 重新格式化聊天窗口(移除代码块)
                self.chat_display.config(state=tk.NORMAL)

                try:
                    if thinking_shown:
                        search_text = "最终回复:\n"
                    else:
                        search_text = "AI:\n"

                    pos = self.chat_display.search(search_text, "1.0", nocase=True)
                    if pos:
                        pos_line, pos_col = pos.split('.')
                        pos_line = str(int(pos_line) + 1)
                        start_pos = f"{pos_line}.0"

                        self.chat_display.delete(start_pos, tk.END)

                        if text_without_code:
                            MarkdownFormatter.format_text(self.chat_display, text_without_code)
                        self.chat_display.insert(tk.END, "\n\n")
                except Exception as e:
                    print(f"格式化错误: {e}")

                self.chat_display.config(state=tk.DISABLED)

                # 添加代码选项卡
                if code_blocks:
                    for code_block in code_blocks:
                        self.add_code_tab(code_block)

                # 保存对话历史
                self.conversation_history.append({
                    "role": "assistant",
                    "content": assistant_message
                })

                self.status_label.config(text="就绪", foreground="green")
            else:
                self.display_message("错误", "未收到有效的AI响应", "error")
                self.status_label.config(text="无响应", foreground="red")

        except requests.exceptions.Timeout:
            self.display_message("错误", "请求超时(120秒)。请检查网络连接或尝试更小的输出长度。", "error")
            self.status_label.config(text="超时错误", foreground="red")
        except requests.exceptions.ConnectionError as e:
            self.display_message("错误", f"连接失败:{str(e)}\n请检查API地址和网络连接。", "error")
            self.status_label.config(text="连接错误", foreground="red")
        except Exception as e:
            self.display_message("错误", f"发生错误:{str(e)}", "error")
            self.status_label.config(text="未知错误", foreground="red")

        finally:
            self.is_loading = False
            self.send_button.config(state=tk.NORMAL)
            if self.status_label.cget("text") == "正在处理...":
                self.status_label.config(text="就绪", foreground="green")

    def display_message(self, sender, message, tag=""):
        """显示消息"""
        self.chat_display.config(state=tk.NORMAL)
        timestamp = datetime.now().strftime("%H:%M:%S")

        self.chat_display.insert(tk.END, f"[{timestamp}] ", "timestamp")
        self.chat_display.insert(tk.END, f"{sender}:\n", tag)

        if tag in ["user", "error"]:
            self.chat_display.insert(tk.END, f"{message}\n\n")
        else:
            MarkdownFormatter.format_text(self.chat_display, message)
            self.chat_display.insert(tk.END, "\n\n")

        self.chat_display.config(state=tk.DISABLED)
        self.chat_display.see(tk.END)

    def clear_conversation(self):
        """清空对话历史"""
        if messagebox.askyesno("确认", "确定要清空所有对话历史吗?"):
            self.conversation_history = []
            self.chat_display.config(state=tk.NORMAL)
            self.chat_display.delete("1.0", tk.END)
            self.chat_display.config(state=tk.DISABLED)
            self.status_label.config(text="对话已清空", foreground="blue")
            # 清除所有代码选项卡(保留聊天选项卡)
            while self.main_notebook.index("end") > 1:
                self.main_notebook.forget(1)
            self.code_tab_count = 0

    def clear_input(self):
        """清空输入框"""
        self.input_text.delete("1.0", tk.END)

    def copy_last_response(self):
        """复制最后一条AI回复"""
        if self.conversation_history:
            for i in range(len(self.conversation_history) - 1, -1, -1):
                if self.conversation_history[i]["role"] == "assistant":
                    message = self.conversation_history[i]["content"]
                    self.root.clipboard_clear()
                    self.root.clipboard_append(message)
                    self.status_label.config(text="已复制到剪贴板", foreground="blue")
                    return
        messagebox.showinfo("提示", "没有可复制的AI回复")

    def export_chat(self):
        """导出对话为文本文件"""
        if not self.conversation_history:
            messagebox.showwarning("提示", "没有对话可导出")
            return

        file_path = filedialog.asksaveasfilename(defaultextension=".txt",
                                                 filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")])
        if file_path:
            try:
                with open(file_path, 'w', encoding='utf-8') as f:
                    f.write(f"对话导出 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
                    f.write(f"模型: {self.model_var.get()}\n")
                    f.write("=" * 60 + "\n\n")

                    for msg in self.conversation_history:
                        sender = "用户" if msg["role"] == "user" else "AI"
                        f.write(f"{sender}:\n{msg['content']}\n\n")

                messagebox.showinfo("成功", f"对话已导出到:{file_path}")
            except Exception as e:
                messagebox.showerror("错误", f"导出失败:{str(e)}")

    def import_settings(self):
        """从JSON文件导入设置"""
        file_path = filedialog.askopenfilename(filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")])
        if file_path:
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    settings = json.load(f)

                if "api_key" in settings:
                    self.api_key_var.set(settings["api_key"])
                if "system_prompt" in settings:
                    self.system_prompt.delete("1.0", tk.END)
                    self.system_prompt.insert(tk.END, settings["system_prompt"])
                if "max_tokens" in settings:
                    self.max_tokens_var.set(str(settings["max_tokens"]))
                if "temperature" in settings:
                    self.temperature_var.set(str(settings["temperature"]))
                if "top_p" in settings:
                    self.top_p_var.set(str(settings["top_p"]))
                if "model" in settings and settings["model"] in self.models:
                    self.model_var.set(settings["model"])

                messagebox.showinfo("成功", "设置导入完成")
            except Exception as e:
                messagebox.showerror("错误", f"导入失败:{str(e)}")


if __name__ == "__main__":
    root = tk.Tk()
    app = ChatBotUI(root)
    root.mainloop()

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐