展示型网站建设的标准如何做网络推广人员
项目开源地址
- energy v2.0 系列, LCL 原生控件, CEF
- lcl v3.0 v3.0 系列为, LCL 原生控件, CEF, Webview2, Webkit2
- XTA SDK
源码分析
本文主要介绍 xtaui.go
是一个基于 LCL 框架的 Go 语言 GUI 应用程序的主文件。
它通过嵌入资源、初始化框架、创建主窗体并运行应用,实现了完整的程序生命周期管理。
本文从资源嵌入、初始化、异常处理、主窗体创建等方面对源码进行了详细分析。
1. 文件结构和包声明
源码文件为 xtaui.go
,包声明为 main
,表示这是一个可执行的 Go 程序。
2. 导入的包
embed
: 用于嵌入静态资源文件。fmt
: 标准输出包,用于打印日志。github.com/energye/lcl/inits
和github.com/energye/lcl/lcl
: 这是 LCL(Light Controls Library)框架的包,用于创建跨平台的 GUI 应用。xta/xtaui/window
: 本地包,用于定义主窗口逻辑。
3. 嵌入资源
- 使用
go:embed
指令嵌入了两个目录:assets
: 用于存放静态资源(如图片、配置文件等)。libs
: 用于存放动态链接库(如平台依赖的二进制文件)。
4. 主函数 (main()
)
- 初始化资源: 调用
inits.Init(lib, resources)
,将嵌入的资源和库文件传递给 LCL 框架进行初始化。 - 初始化应用: 调用
lcl.Application.Initialize()
,初始化 LCL 框架。 - 设置异常处理: 通过
SetOnException
方法设置全局异常处理函数,捕获并打印错误信息。 - 设置主窗体: 调用
SetMainFormOnTaskBar(true)
,确保主窗体显示在任务栏中。 - 创建和运行主窗体: 通过
CreateForm
创建主窗体(MainWindow
),并调用Run()
方法启动应用。
源码概述
xtaui.go
是一个基于 LCL 框架的 Go 语言 GUI 应用程序的主文件。它通过嵌入静态资源和动态链接库,初始化 LCL 框架,并创建和运行主窗口。
资源嵌入与初始化
-
资源嵌入:
使用go:embed
指令将assets
和libs
目录嵌入到可执行文件中。这种方式可以避免在运行时依赖外部文件,使程序更加 portable( portable,便于移植)。 -
初始化流程:
- 调用
inits.Init(lib, resources)
,将嵌入的资源传递给 LCL 框架。 - 调用
lcl.Application.Initialize()
,完成 LCL 框架的初始化。
- 调用
异常处理机制
通过 SetOnException
方法,程序全局捕获异常并打印错误信息。这种机制可以有效避免程序因未捕获的异常而崩溃,同时为调试提供日志支持。
主窗体的创建与运行
-
创建主窗体:
调用lcl.Application.CreateForm(&window.MainWindow)
创建主窗体。MainWindow
是从本地包xta/xtaui/window
中导入的结构体,用于定义窗体的外观和行为。 -
运行应用:
调用lcl.Application.Run()
启动应用程序,进入消息循环,处理用户交互。
LCL 框架简介
LCL是一个跨平台的 GUI 开发框架,支持 Windows、Linux 和 macOS 操作系统。通过 LCL,开发者可以使用 Go 语言快速构建功能丰富的桌面应用。
示例截图
程序文件大小
执行文件: 15M
压缩后: 5M
截图
程序部分源码
main 入口
package mainimport ("embed""fmt""github.com/energye/lcl/inits""github.com/energye/lcl/lcl""xta/xtaui/window"
)//go:embed assets
var resources embed.FS//go:embed libs
var lib embed.FSfunc main() {inits.Init(lib, resources)lcl.Application.Initialize()lcl.Application.SetOnException(func(sender lcl.IObject, e lcl.IException) {fmt.Println("Exception:", e.ToString())})lcl.Application.SetMainFormOnTaskBar(true)lcl.Application.CreateForm(&window.MainWindow)lcl.Application.Run()
}
程序主窗口
package windowimport ("bufio""errors""fmt""github.com/energye/lcl/lcl""github.com/energye/lcl/rtl""github.com/energye/lcl/types""github.com/energye/lcl/types/colors""github.com/energye/xta/chat""io/fs""os""time"_ "xta/xtaui/syso"
)type TMainWindow struct {lcl.TFormmessage lcl.IMemochat lcl.IMemoai chat.IGiteeAIchatBtn lcl.IButtonselFileBtn lcl.IButtonselDirDlg lcl.ISelectDirectoryDialogsaveChatBtn lcl.IButtonsaveDirDlg lcl.ISaveDialogsavePathInp lcl.IMemosaveFileBuf *bufio.WriterfileWindow []*FileWindowtitle string
}var MainWindow TMainWindowfunc (m *TMainWindow) FormCreate(sender lcl.IObject) {m.title = "ENERGY - XTA Chat UI"m.SetCaption(m.title)m.SetPosition(types.PoScreenCenter)m.SetWidth(1024)m.SetHeight(768)m.SetShowHint(true)m.Constraints().SetMinWidth(types.TConstraintSize(m.Width()))m.Constraints().SetMinHeight(types.TConstraintSize(m.Height()))png := lcl.NewPngImage()png.LoadFromFSFile("assets/icon.png")lcl.Application.Icon().Assign(png)png.Free()m.initMainBox()
}func (m *TMainWindow) initMainBox() {go m.initXTASDK()openURL := lcl.NewLinkLabel(m)openURL.SetParent(m)openURL.SetCaption(`<a href="https://ai.gitee.com/models"> [Gitee AI API 获取] </a>`)openURL.SetAlign(types.AlRight)openURL.SetTop(5)openURL.Font().SetSize(12)openURL.SetOnLinkClick(func(sender lcl.IObject, link string, linktype types.TSysLinkType) {rtl.SysOpen(link)})open1URL := lcl.NewLinkLabel(m)open1URL.SetParent(m)open1URL.SetCaption(`<a href="https://github.com/energye/xta"> [XTA AI SDK] </a>`)open1URL.SetAlign(types.AlRight)open1URL.SetTop(5)open1URL.Font().SetSize(12)open1URL.SetOnLinkClick(func(sender lcl.IObject, link string, linktype types.TSysLinkType) {rtl.SysOpen(link)})modules := lcl.NewComboBox(m)modules.SetParent(m)modules.SetLeft(150)modules.Items().AddStrings2(chat.GiteeAIModels())modules.SetItemIndex(17)modules.SetHeight(35)modules.SetWidth(300)modules.Font().SetSize(12)modules.SetOnChange(func(sender lcl.IObject) {module := chat.GiteeAIModelNameEnum(modules.Items().Strings(modules.ItemIndex()))m.ai.SetModel(module)m.message.Lines().Add("模型: " + m.ai.Model())m.SetCaption(m.title + " " + m.ai.Model())})apiKey := lcl.NewEditButton(m)apiKey.SetParent(m)apiKey.SetLeft(modules.Left() + modules.Width() + 5)apiKey.SetPasswordChar(uint16('*'))apiKey.SetHeight(35)apiKey.SetWidth(200)apiKey.Font().SetSize(12)apiKey.Button().SetCaption("API KEY")//apiKey.Button().SetLeft(100)apiKey.Button().SetWidth(80)apiKey.SetOnClick(func(sender lcl.IObject) {m.ai.Options().APIKey = apiKey.Text()})// 消息m.message = lcl.NewMemo(m)m.message.SetParent(m)m.message.SetTop(40)m.message.SetLeft(150)m.message.SetWidth(m.Width() - 150)m.message.SetHeight(m.Height() - 190)m.message.SetBorderStyle(types.BsNone)m.message.SetReadOnly(true)m.message.SetScrollBars(types.SsAutoBoth)m.message.SetWordWrap(true)m.message.SetAnchors(types.NewSet(types.AkLeft, types.AkTop, types.AkRight, types.AkBottom))// 聊天m.chat = lcl.NewMemo(m)m.chat.SetParent(m)m.chat.SetBorderStyle(types.BsNone)m.chat.SetScrollBars(types.SsAutoBoth)m.chat.SetTop(m.message.Top() + m.message.Height() + 3)m.chat.SetLeft(150)m.chat.SetWidth(m.Width())m.chat.SetHeight(100)m.chat.SetAnchors(types.NewSet(types.AkLeft, types.AkRight, types.AkBottom))chatLabel := lcl.NewLabel(m)chatLabel.SetParent(m)chatLabel.SetCaption("发送消息")chatLabel.SetTop(m.chat.Top() + 40)chatLabel.SetLeft(20)chatLabel.Font().SetSize(18)chatLabel.Font().SetColor(colors.ClGray)chatLabel.SetAnchors(types.NewSet(types.AkLeft, types.AkBottom))// 发送消息m.chatBtn = lcl.NewButton(m)m.chatBtn.SetParent(m)m.chatBtn.SetTop(m.chat.Top() + m.chat.Height() + 3)m.chatBtn.SetWidth(100)m.chatBtn.SetHeight(45)m.chatBtn.SetCaption("发 送")m.chatBtn.Font().SetSize(16)m.chatBtn.SetLeft(m.Width() - 100)m.chatBtn.SetAnchors(types.NewSet(types.AkRight, types.AkBottom))m.chatBtn.SetOnClick(m.SendMessage)// 选择文件m.selDirDlg = lcl.NewOpenDialog(m)m.selDirDlg.SetOptions(m.selDirDlg.Options().Include(types.OfShowHelp, types.OfAllowMultiSelect))m.selDirDlg.SetTitle("XTA - AI SDK 打开文件 多选")m.selFileBtn = lcl.NewButton(m)m.selFileBtn.SetParent(m)m.selFileBtn.SetTop(m.chat.Top() + m.chat.Height() + 3)m.selFileBtn.SetWidth(150)m.selFileBtn.SetHeight(40)m.selFileBtn.SetCaption("选择文件/多选")m.selFileBtn.Font().SetSize(12)m.selFileBtn.SetLeft(150)m.selFileBtn.SetOnClick(m.selectFileOrDir)m.selFileBtn.SetAnchors(types.NewSet(types.AkLeft, types.AkBottom))// 保存消息m.saveDirDlg = lcl.NewSaveDialog(m)m.saveDirDlg.SetFilter("文本文件(*.txt)|*.txt|所有文件(*.*)|*.*")m.saveDirDlg.SetTitle("XTA - AI SDK 消息保存")m.saveChatBtn = lcl.NewButton(m)m.saveChatBtn.SetParent(m)m.saveChatBtn.SetTop(m.chat.Top() + m.chat.Height() + 3)m.saveChatBtn.SetLeft(m.selFileBtn.Left() + m.selFileBtn.Width() + 3)m.saveChatBtn.SetCaption("保存消息")m.saveChatBtn.SetWidth(100)m.saveChatBtn.SetHeight(40)m.saveChatBtn.Font().SetSize(12)m.saveChatBtn.SetAnchors(types.NewSet(types.AkLeft, types.AkBottom))m.saveChatBtn.SetOnClick(func(sender lcl.IObject) {if m.saveDirDlg.Execute() {m.savePathInp.SetText(m.saveDirDlg.FileName())}})m.savePathInp = lcl.NewMemo(m)m.savePathInp.SetParent(m)m.savePathInp.SetTop(m.chat.Top() + m.chat.Height() + 3)m.savePathInp.SetLeft(m.saveChatBtn.Left() + m.saveChatBtn.Width() + 3)m.savePathInp.SetHeight(40)m.savePathInp.SetWidth(300)m.savePathInp.Font().SetSize(15)m.savePathInp.SetWordWrap(false)m.savePathInp.SetAnchors(types.NewSet(types.AkLeft, types.AkBottom))var savefile *os.Filem.savePathInp.SetOnChange(func(sender lcl.IObject) {if savefile != nil {savefile.Close()savefile = nilm.saveFileBuf = nil}path := m.savePathInp.Text()fe, err := os.Open(path)if err == nil {defer fe.Close()st, err := fe.Stat()if err == nil {if !st.IsDir() {savefile, err = os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)if err == nil {m.saveFileBuf = bufio.NewWriter(savefile)}}}} else if errors.Is(err, fs.ErrNotExist) {savefile, err = os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)if err == nil {m.saveFileBuf = bufio.NewWriter(savefile)}}})// 窗口显示m.SetOnShow(func(sender lcl.IObject) {m.chat.SetFocus()apiKey.SetText(m.ai.APIKey())})//clearHistory := lcl.NewButton(m)//clearHistory.SetParent(m)//clearHistory.SetOnClick(func(sender lcl.IObject) {// m.ai.History()//})
}// 主窗口左侧创建文件项
func (m *TMainWindow) createFileItem(filewindow *FileWindow) {btn := lcl.NewButton(m)btn.SetParent(m)caption := filewindow.fileDescif caption == "" {caption = filewindow.filenames}btn.SetCaption(caption)btn.SetOnClick(func(sender lcl.IObject) {m.removeFileBtn(filewindow.id)})btn.SetHint("点击删除该文件项")filewindow.fileBtn = btnm.fileWindow = append(m.fileWindow, filewindow)m.resortFileBtns()
}func (m *TMainWindow) removeFileBtn(id string) {var newwindows []*FileWindowfor _, fw := range m.fileWindow {if fw.id == id {lcl.RunOnMainThreadAsync(func(id uint32) {fw.fileBtn.Free()fw.Close()})} else {newwindows = append(newwindows, fw)}}m.fileWindow = newwindowsm.resortFileBtns()
}func (m *TMainWindow) resortFileBtns() {for i, fw := range m.fileWindow {fw.fileBtn.SetLeft(5)fw.fileBtn.SetWidth(130)fw.fileBtn.SetHeight(30)fw.fileBtn.SetTop(int32(i*30) + 5)}
}func (m *TMainWindow) selectFileOrDir(sender lcl.IObject) {if m.selDirDlg.Execute() {files := m.selDirDlg.Files()var file []stringfor i := 0; i < int(files.Count()); i++ {fmt.Println(files.ValueFromIndex(int32(i)))file = append(file, files.ValueFromIndex(int32(i)))}win := createWindow(file, func(window *FileWindow) {m.createFileItem(window)})win.Show()}
}func nowDatetime() string {return time.Now().Format("2006-01-02 15:04:05")
}
XTA - AI SDK
package windowimport ("bytes""fmt""github.com/energye/lcl/lcl""github.com/energye/xta/chat""os""strings"
)func (m *TMainWindow) initXTASDK() {options := chat.DefaultGiteeAIOptionsoptions.APIKey = os.Getenv(chat.ENV_AI_API_KEY)m.ai = chat.NewGiteeAI(options, false)m.ai.System("【系统角色】你具备跨领域知识整合与结构化推理能力的智能助手。始终遵循:事实准确性 > 响应速度 > 表达流畅度的优先级原则。【响应规范】1. 解析阶段:识别问题类型(事实/观点/方法需求),标注关键信息置信度2. 处理阶段:- 事实类:提供最新权威信源+时间戳- 观点类:多视角分析+概率评估 - 方法类:分步实施框架+风险预案3. 输出阶段:采用「结论-依据-延伸」结构,技术概念附带白话解释【安全协议】- 对潜在争议内容自动附加免责声明- 医疗/法律建议必须提示咨询专业人士- 实时监测对话情感倾向,对焦虑/紧急表达启动安抚话术")isFirstRec := falsem.ai.SetOnReceive(func(message *chat.TResponse) {if !isFirstRec {m.message.Lines().Add("回复: " + nowDatetime())isFirstRec = true}// 在异步UI线程里操作lcl.RunOnMainThreadAsync(func(id uint32) {if message != nil {if message.Error != "" {s := fmt.Sprintf("错误: %v %v", message.Error, message.ErrorType)m.message.Lines().Add(s)if m.saveFileBuf != nil {m.saveFileBuf.WriteString(s)m.saveFileBuf.Flush()}}choices := message.Choicesfor _, choice := range choices {if strings.Contains(choice.Delta.Content, "\n") {m.message.Lines().Add(choice.Delta.Content)} else {m.message.SetSelStart(int32(len(m.message.Lines().Text())))m.message.SetSelText(choice.Delta.Content)}if m.saveFileBuf != nil {m.saveFileBuf.WriteString(choice.Delta.Content)m.saveFileBuf.Flush()}}} else {fmt.Println("结束")m.message.Lines().Add("")m.chatBtn.SetEnabled(true)isFirstRec = false}})})m.ai.SetOnFail(func(message *chat.TResponseError) {lcl.RunOnMainThreadSync(func() {s := fmt.Sprintf(" 错误: %v %v %v", message.Code, message.Message, message.Type)m.message.Lines().Add(s)m.chatBtn.SetEnabled(true)isFirstRec = false})})lcl.RunOnMainThreadSync(func() {m.message.Lines().Add("XTA - AI SDK 初始化完成")m.message.Lines().Add("模型: " + m.ai.Model())//m.message.Lines().Add("APIKEY: ........." + m.ai.APIKey()[5:10] + "............")m.SetCaption(m.title + " " + m.ai.Model())})
}func (m *TMainWindow) SendMessage(sender lcl.IObject) {msg := m.chat.Lines().Text()if msg != "" {m.message.Lines().Add("我: " + nowDatetime())m.message.Lines().Add(" " + msg)buf := bytes.Buffer{}buf.WriteString(msg + "\n")for _, fw := range m.fileWindow {if fw.isSend {continue}fw.isSend = truebuf.WriteString(fw.text.Text() + "\n")buf.WriteString(strings.Join(fw.fileContent, "\n"))}m.sendMessage(buf.String())m.chat.SetText("")}
}func (m *TMainWindow) sendMessage(content string) {// 在协程里操作go m.ai.ChatStream(content)m.chatBtn.SetEnabled(false)
}