百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

学用大语言模型(8):通过Kimi批量更改截图文件名称

ztj100 2025-08-01 22:14 3 浏览 0 评论

经常截图,需要根据截图内容对文件更名。

包括没有文字的截图,获取图片可能传递的信息。


用kimi的api,实现了一个简单的应用。

比如:

获取指定文件夹的图片:




进行处理后:



借助ai,整理的主要代码:

// ------------------------------------------------------------
//  File: Form1.cs
//  大语言模型文件管理器 – 支持 Moonshot(Kimi) Vision API
// ------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 大语言模型文件管理器
{
    public partial class Form1 : Form
    {
        private readonly KimiApiClient _kimiClient;

        public Form1()
        {
            InitializeComponent();
            // 如需从环境变量读取,可把 apiKey 设为 null
            _kimiClient = new KimiApiClient("myKey");
            InitializeListView();
        }

        #region UI 初始化
        private void InitializeListView()
        {
            listView1.View = View.Details;
            listView1.CheckBoxes = true;
            listView1.Columns.Add("文件名", 800);
            listView1.Columns.Add("状态", 200);
            listView1.Columns.Add("识别结果", this.listView1.Width-1100);
        }
        #endregion

        #region 事件处理
        private void btnBrowse_Click(object sender, EventArgs e)
        {
            using var dlg = new FolderBrowserDialog();
            if (dlg.ShowDialog() == DialogResult.OK)
                LoadImagesFromFolder(dlg.SelectedPath);
        }

        private async void btnProcess_Click(object sender, EventArgs e)
        {
            if (listView1.CheckedItems.Count == 0)
            {
                MessageBox.Show("请先选择要处理的图片", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            btnProcess.Enabled = false;
            progressBar1.Maximum = listView1.CheckedItems.Count;
            progressBar1.Value = 0;

            await ProcessCheckedItemsAsync();

            btnProcess.Enabled = true;
            MessageBox.Show("处理完成", "完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        #endregion

        #region 业务逻辑
        private void LoadImagesFromFolder(string folderPath)
        {
            listView1.Items.Clear();
            var exts = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
            { ".jpg",".jpeg",".png",".bmp",".gif",".tiff" };

            try
            {
                foreach (var file in Directory.EnumerateFiles(folderPath)
                                              .Where(f => exts.Contains(Path.GetExtension(f))))
                {
                    var item = new ListViewItem(Path.GetFileName(file));
                    item.SubItems.Add("就绪");
                    item.SubItems.Add("");
                    item.Tag = file;
                    listView1.Items.Add(item);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(#34;加载图片时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private async Task ProcessCheckedItemsAsync()
        {
            foreach (ListViewItem item in listView1.CheckedItems)
            {
                try
                {
                    string imagePath = item.Tag.ToString();
                    item.SubItems[1].Text = "处理中";
                    listView1.Refresh();

                    var result = await _kimiClient.ProcessImageAsync(imagePath);


                    var shortName = await _kimiClient.SummarizeToFileNameAsync(result.RecognitionResult);



                    if (!string.IsNullOrEmpty(result.RecognitionResult))
                    {
                        item.SubItems[2].Text = shortName;

                        if (!string.IsNullOrEmpty(result.SuggestedFileName))
                        {
                            string dir = Path.GetDirectoryName(imagePath);
                            string ext = Path.GetExtension(imagePath);
                            string newName = #34;{shortName}{ext}";
                            string newPath = Path.Combine(dir, newName);

                            if (File.Exists(newPath))
                                newPath = Path.Combine(dir,
                                    #34;{shortName}_{Guid.NewGuid():N[..8]}{ext}");

                            File.Move(imagePath, newPath);

                            item.Text = Path.GetFileName(newPath);
                            item.Tag = newPath;
                            item.SubItems[1].Text = "已重命名";
                        }
                        else
                        {
                            item.SubItems[1].Text = "命名失败";
                        }
                    }
                    else
                    {
                        item.SubItems[1].Text = "识别失败";
                    }
                }
                catch (Exception ex)
                {
                    item.SubItems[1].Text = "错误";
                    item.SubItems[2].Text = ex.Message;
                }

                progressBar1.Value++;
                await Task.Delay(50);
            }
        }
        #endregion
    }

    #region 数据模型
    public class ProcessResult
    {
        public string RecognitionResult { get; set; }
        public string SuggestedFileName { get; set; }
    }
    #endregion

    #region API 客户端
    public class KimiApiClient : IDisposable
    {
        private readonly HttpClient _http;
        private readonly string _apiKey;
        private readonly string _endpoint;
        private readonly string _model;

        public KimiApiClient(string apiKey = null,
                             string endpoint = "https://api.moonshot.cn/v1/chat/completions",
                             string model = "moonshot-v1-8k-vision-preview")
        {
            _apiKey = apiKey ?? Environment.GetEnvironmentVariable("KIMI_API_KEY");
            _endpoint = endpoint;
            _model = model;

            if (string.IsNullOrWhiteSpace(_apiKey))
                throw new InvalidOperationException("API 密钥未设置");

            _http = new HttpClient();
            _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey);
            _http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        }

        public async Task<ProcessResult> ProcessImageAsync(string imagePath)
        {
            var result = new ProcessResult();

            try
            {
                // 1. OCR
                var ocr = await AskVisionAsync(imagePath,
                    "请完整识别图片中出现的所有文字,不要额外解释。只需要一句话。");
                if (!string.IsNullOrWhiteSpace(ocr))
                {
                    result.RecognitionResult = ocr.Trim();
                    result.SuggestedFileName = CleanFileName(ocr);
                    return result;


                }

                // 2. 描述
                var desc = await AskVisionAsync(imagePath,
                    "请用简洁的中文描述图片中的主要内容,不要包含文件名信息。只需要一句话。");
                if (!string.IsNullOrWhiteSpace(desc))
                {
                    result.RecognitionResult = desc.Trim();
                    result.SuggestedFileName = CleanFileName(desc);
                    return result;
                }

                // 3. 兜底
                result.RecognitionResult = "无法识别内容";
                result.SuggestedFileName = #34;Kimi_{DateTime.Now:yyyyMMddHHmmss}";
                return result;
            }
            catch (Exception ex)
            {
                result.RecognitionResult = #34;错误: {ex.Message}";
                return result;
            }
        }


        /// <summary>
        /// 把识别结果文本提交给大模型,返回 ≤50 字符的合法文件名片段
        /// </summary>
        public async Task<string> SummarizeToFileNameAsync(string recognitionResult)
        {
            if (string.IsNullOrWhiteSpace(recognitionResult))
                return "未知";

            const int maxChars = 50;

            var prompt =
                #34;请把以下文本归纳为不超过{maxChars}个字符的简短概要," +
                #34;只保留关键中文信息,不要出现标点符号,不要包括逗号、句号、空格等,不要包括其他特殊字符,仅仅是全中文的有意义的概述。\n\n{recognitionResult}";

            var payload = new
            {
                model = "moonshot-v1-8k",          // 普通文本模型即可
                messages = new[]
                {
            new { role = "user", content = prompt }
        },
                max_tokens = 32,
                temperature = 0.1
            };

            var json = JsonSerializer.Serialize(payload,
                new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });

            using var content = new StringContent(json, Encoding.UTF8, "application/json");
            using var cts = new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(15));

            var resp = await _http.PostAsync(_endpoint, content, cts.Token);
            resp.EnsureSuccessStatusCode();

            var respJson = await resp.Content.ReadAsStringAsync();
            using var doc = JsonDocument.Parse(respJson);

            var summary = doc.RootElement
                             .GetProperty("choices")[0]
                             .GetProperty("message")
                             .GetProperty("content")
                             .GetString()?
                             .Trim() ?? "未知";

            // 再做一次本地合法性清理
            var invalid = Path.GetInvalidFileNameChars();
            var sb = new StringBuilder(summary.Length);
            foreach (var c in summary.Take(maxChars))
                sb.Append(invalid.Contains(c) ? '_' : c);

            string cleaned = sb.ToString().Trim('_', '-', ' ');

            cleaned = cleaned.Replace(" ", "");

            return string.IsNullOrEmpty(cleaned) ? "未知" : cleaned;
        }



        #region 内部方法
        private async Task<string> AskVisionAsync(string imagePath, string prompt)
        {
            var bytes = await File.ReadAllBytesAsync(imagePath);
            var b64 = Convert.ToBase64String(bytes);
            var mime = MimeMapping.GetMimeType(imagePath);

            var payload = new
            {
                model = _model,
                messages = new[]
                {
                    new
                    {
                        role = "user",
                        content = new object[]
                        {
                            new { type = "text", text = prompt },
                            new { type = "image_url",
                                  image_url = new { url = #34;data:{mime};base64,{b64}" } }
                        }
                    }
                },
                max_tokens = 300,
                temperature = 0.1
            };

            var json = JsonSerializer.Serialize(payload,
                          new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
            var body = new StringContent(json, Encoding.UTF8, "application/json");

            using var cts = new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(30));
            var resp = await _http.PostAsync(_endpoint, body, cts.Token);
            resp.EnsureSuccessStatusCode();

            var respJson = await resp.Content.ReadAsStringAsync();
            using var doc = JsonDocument.Parse(respJson);

            return doc.RootElement
                      .GetProperty("choices")[0]
                      .GetProperty("message")
                      .GetProperty("content")
                      .GetString();
        }

        private static string CleanFileName(string input)
        {
            if (string.IsNullOrWhiteSpace(input)) return null;

            var invalid = Path.GetInvalidFileNameChars();
            var sb = new StringBuilder();
            foreach (var c in input)
            {
                if (invalid.Contains(c))
                    sb.Append('_');
                else
                    sb.Append(c);
            }

            var cleaned = sb.ToString().Trim();
            if (cleaned.Length > 200) cleaned = cleaned[..200].Trim();
            while (cleaned.EndsWith('.') || cleaned.EndsWith(' '))
                cleaned = cleaned[..^1];

            return cleaned;
        }
        #endregion

        public void Dispose() => _http?.Dispose();
    }

    internal static class MimeMapping
    {
        public static string GetMimeType(string path) =>
            Path.GetExtension(path).ToLowerInvariant() switch
            {
                ".jpg" or ".jpeg" => "image/jpeg",
                ".png" => "image/png",
                ".bmp" => "image/bmp",
                ".gif" => "image/gif",
                ".tiff" => "image/tiff",
                _ => "application/octet-stream"
            };
    }
    #endregion


}

相关推荐

Jquery 详细用法

1、jQuery介绍(1)jQuery是什么?是一个js框架,其主要思想是利用jQuery提供的选择器查找要操作的节点,然后将找到的节点封装成一个jQuery对象。封装成jQuery对象的目的有...

前端开发79条知识点汇总

1.css禁用鼠标事件2.get/post的理解和他们之间的区别http超文本传输协议(HTTP)的设计目的是保证客户机与服务器之间的通信。HTTP的工作方式是客户机与服务器之间的请求-应答协议。...

js基础面试题92-130道题目

92.说说你对作用域链的理解参考答案:作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。...

Web前端必备基础知识点,百万网友:牛逼

1、Web中的常见攻击方式1.SQL注入------常见的安全性问题。解决方案:前端页面需要校验用户的输入数据(限制用户输入的类型、范围、格式、长度),不能只靠后端去校验用户数据。一来可以提高后端处理...

事件——《JS高级程序设计》

一、事件流1.事件流描述的是从页面中接收事件的顺序2.事件冒泡(eventbubble):事件从开始时由最具体的元素(就是嵌套最深的那个节点)开始,逐级向上传播到较为不具体的节点(就是Docu...

前端开发中79条不可忽视的知识点汇总

过往一些不足的地方,通过博客,好好总结一下。1.css禁用鼠标事件...

Chrome 开发工具之Network

经常会听到比如"为什么我的js代码没执行啊?","我明明发送了请求,为什么反应?","我这个网站怎么加载的这么慢?"这类的问题,那么问题既然存在,就需要去解决它,需要解决它,首先我们得找对导致问题的原...

轻量级 React.js 虚拟美化滚动条组件RScroll

前几天有给大家分享一个Vue自定义滚动条组件VScroll。今天再分享一个最新开发的ReactPC端模拟滚动条组件RScroll。...

一文解读JavaScript事件对象和表单对象

前言相信做网站对JavaScript再熟悉不过了,它是一门脚本语言,不同于Python的是,它是一门浏览器脚本语言,而Python则是服务器脚本语言,我们不光要会Python,还要会JavaScrip...

Python函数参数黑科技:*args与**kwargs深度解析

90%的Python程序员不知道,可变参数设计竟能决定函数的灵活性和扩展性!掌握这些技巧,让你的函数适应任何场景!一、函数参数设计的三大进阶技巧...

深入理解Python3密码学:详解PyCrypto库加密、解密与数字签名

在现代计算领域,信息安全逐渐成为焦点话题。密码学,作为信息保护的关键技术之一,允许我们加密(保密)和解密(解密)数据。...

阿里Nacos惊爆安全漏洞,火速升级!(附修复建议)

前言好,我是threedr3am,我发现nacos最新版本1.4.1对于User-Agent绕过安全漏洞的serverIdentitykey-value修复机制,依然存在绕过问题,在nacos开启了...

Python模块:zoneinfo时区支持详解

一、知识导图二、知识讲解(一)zoneinfo模块概述...

Golang开发的一些注意事项(一)

1.channel关闭后读的问题当channel关闭之后再去读取它,虽然不会引发panic,但会直接得到零值,而且ok的值为false。packagemainimport"...

Python鼠标与键盘自动化指南:从入门到进阶——键盘篇

`pynput`是一个用于控制和监控鼠标和键盘的Python库...

取消回复欢迎 发表评论: