Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
F
Ftt_Common
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Ftt_Common
Commits
67a4bda7
Commit
67a4bda7
authored
Sep 18, 2026
by
段廷瑞
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: Agent问答交互功能优化
parent
8d4b5d0b
Changes
12
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
744 additions
and
225 deletions
+744
-225
Tools.Agent.dll
AppHome/Tools.Agent.dll
+0
-0
Tools.Agent.xml
AppHome/Tools.Agent.xml
+135
-30
AgentEngine.cs
Tools.Agent/Agent/AgentEngine.cs
+1
-1
KbRecall.cs
Tools.Agent/Agent/Answer/KbRecall.cs
+25
-4
AgentConfig.cs
Tools.Agent/Config/AgentConfig.cs
+4
-2
AgentSettingsForm.cs
Tools.Agent/Forms/AgentSettingsForm.cs
+3
-108
KbService.cs
Tools.Agent/Kb/KbService.cs
+61
-0
ChatRtfCache.cs
Tools.Agent/Support/ChatRtfCache.cs
+25
-0
MarkdownRenderer.cs
Tools.Agent/Support/MarkdownRenderer.cs
+260
-1
RichEditObjects.cs
Tools.Agent/Support/RichEditObjects.cs
+95
-0
Tools.Agent.csproj
Tools.Agent/Tools.Agent.csproj
+1
-0
wMyAgent.cs
Tools.Agent/wMyAgent.cs
+134
-79
No files found.
AppHome/Tools.Agent.dll
View file @
67a4bda7
No preview for this file type
AppHome/Tools.Agent.xml
View file @
67a4bda7
This diff is collapsed.
Click to expand it.
Tools.Agent/Agent/AgentEngine.cs
View file @
67a4bda7
...
@@ -297,7 +297,7 @@ namespace Tools.MyAgent.Agent
...
@@ -297,7 +297,7 @@ namespace Tools.MyAgent.Agent
?
cfg
.
KbHitThreshold
?
cfg
.
KbHitThreshold
:
0.72
;
:
0.72
;
_recall
.
ImagesEnabled
=
cfg
.
AnswerWithImages
;
// 关掉附图即不采图(省掉目录扫描)
_recall
.
ImagesEnabled
=
cfg
.
AnswerWithImages
;
// 关掉附图即不采图(省掉目录扫描)
_recall
.
MaxImages
=
cfg
.
MaxAnswerImages
>
0
?
Math
.
Min
(
cfg
.
MaxAnswerImages
,
1
2
)
:
4
;
_recall
.
MaxImages
=
cfg
.
MaxAnswerImages
>
0
?
Math
.
Min
(
cfg
.
MaxAnswerImages
,
1
6
)
:
16
;
CostEstimator
.
Configure
(
cfg
.
CustomPrices
);
// R6 费用估算:应用自定义价格表覆盖
CostEstimator
.
Configure
(
cfg
.
CustomPrices
);
// R6 费用估算:应用自定义价格表覆盖
// P0 连接串脱敏 / P3 工具权限:预存连接与角色规则随配置注入
// P0 连接串脱敏 / P3 工具权限:预存连接与角色规则随配置注入
...
...
Tools.Agent/Agent/Answer/KbRecall.cs
View file @
67a4bda7
...
@@ -84,8 +84,8 @@ namespace Tools.MyAgent.Agent
...
@@ -84,8 +84,8 @@ namespace Tools.MyAgent.Agent
/// <summary>是否采集关联图片</summary>
/// <summary>是否采集关联图片</summary>
public
bool
ImagesEnabled
=
true
;
public
bool
ImagesEnabled
=
true
;
/// <summary>关联图片上限</summary>
/// <summary>关联图片上限
(内部默认 16;引擎 Configure 按 AgentConfig.MaxAnswerImages 注入)
</summary>
public
int
MaxImages
=
4
;
public
int
MaxImages
=
16
;
/// <summary>纳入证据链的命中上限(主命中 + 其余)</summary>
/// <summary>纳入证据链的命中上限(主命中 + 其余)</summary>
private
const
int
MaxHits
=
3
;
private
const
int
MaxHits
=
3
;
...
@@ -404,9 +404,10 @@ namespace Tools.MyAgent.Agent
...
@@ -404,9 +404,10 @@ namespace Tools.MyAgent.Agent
}
}
/// <summary>
/// <summary>
/// 采集关联图片(
三条规则合并去重,最多 max 张,全部过沙箱
校验):
/// 采集关联图片(
四条规则合并去重,最多 max 张,全部过
校验):
/// ① 命中段落内的 markdown 图片引用;② 命中行 ±20 行内的图片引用;
/// ① 命中段落内的 markdown 图片引用;② 命中行 ±20 行内的图片引用;
/// ③ 同目录与 images/图片/assets/imgs 下与文档名或命中关键词有字面交集的图片。
/// ③ 同目录与 images/图片/assets/imgs 下与文档名或命中关键词有字面交集的图片;
/// ④ docx 内嵌位图(v1.3):Word 图片在 zip 包内(word/media/),文本提取与同目录规则都够不着 ⇒ 解包提取。
/// </summary>
/// </summary>
private
void
CollectImages
(
KbHit
hit
,
string
[]
lines
,
int
max
)
private
void
CollectImages
(
KbHit
hit
,
string
[]
lines
,
int
max
)
{
{
...
@@ -459,6 +460,26 @@ namespace Tools.MyAgent.Agent
...
@@ -459,6 +460,26 @@ namespace Tools.MyAgent.Agent
}
}
catch
{
}
catch
{
}
}
}
// ④ Word 内嵌位图(v1.3):docx 的图片在 zip 包内(word/media/),①②③ 均够不着 ⇒ 显式解包。
// 提取落点在本机缓存(OutputRoot/kb_media/),不在知识库沙箱内 —— 路径由我们生成、可信,
// 故不走 AddImageAbs 的知识库根校验(那里会因「不在 KB 根下」把缓存图全部拒掉),只做存在/扩展/去重。
try
{
if
(
hit
.
Images
.
Count
<
max
&&
string
.
Equals
(
Path
.
GetExtension
(
hit
.
AbsPath
??
""
),
".docx"
,
StringComparison
.
OrdinalIgnoreCase
))
{
var
embedded
=
_kb
.
ExtractDocxImages
(
hit
.
AbsPath
,
max
-
hit
.
Images
.
Count
);
foreach
(
var
p
in
embedded
)
{
if
(
hit
.
Images
.
Count
>=
max
)
break
;
if
(
string
.
IsNullOrEmpty
(
p
)
||
!
File
.
Exists
(
p
))
continue
;
if
(!
seen
.
Add
(
p
))
continue
;
// 扩展白名单已在 KbService 侧校验
hit
.
Images
.
Add
(
p
);
}
}
}
catch
{
}
}
}
/// <summary>命中段落里的图片链接 → 绝对路径(相对文档目录解析),校验通过后加入</summary>
/// <summary>命中段落里的图片链接 → 绝对路径(相对文档目录解析),校验通过后加入</summary>
...
...
Tools.Agent/Config/AgentConfig.cs
View file @
67a4bda7
...
@@ -111,6 +111,8 @@
...
@@ -111,6 +111,8 @@
public
bool
EnableImageInput
{
get
;
set
;
}
=
true
;
public
bool
EnableImageInput
{
get
;
set
;
}
=
true
;
// ════════════════ 高级项:回答形态(旋转指示 / 本地直答 / 契约化输出 / 附图) ════════════════
// ════════════════ 高级项:回答形态(旋转指示 / 本地直答 / 契约化输出 / 附图) ════════════════
// 说明(v1.2):本组不提供设置页 —— 均为内部默认值(降低设置页认知负担);
// 保留可序列化属性,供手改配置文件微调,或将来需要时再挂 UI。
/// <summary>
/// <summary>
/// 本地召回直答(回答形态改造 P3):本轮问题在知识库命中达阈值时,走「单轮直答」通道(不下发工具),
/// 本地召回直答(回答形态改造 P3):本轮问题在知识库命中达阈值时,走「单轮直答」通道(不下发工具),
...
@@ -133,8 +135,8 @@
...
@@ -133,8 +135,8 @@
/// <summary>回答附图(P5):本轮回答命中文档关联的图片以缩略图栏展示,点击放大(复用 wBitmapView)</summary>
/// <summary>回答附图(P5):本轮回答命中文档关联的图片以缩略图栏展示,点击放大(复用 wBitmapView)</summary>
public
bool
AnswerWithImages
{
get
;
set
;
}
=
true
;
public
bool
AnswerWithImages
{
get
;
set
;
}
=
true
;
/// <summary>回答附图上限(张,1~
8
)</summary>
/// <summary>回答附图上限(张,1~
16;暂定 16,无设置页,手改配置文件可调
)</summary>
public
int
MaxAnswerImages
{
get
;
set
;
}
=
4
;
public
int
MaxAnswerImages
{
get
;
set
;
}
=
16
;
/// <summary>
/// <summary>
/// 本地直答阈值(0.45~0.95):融合匹配度 ≥ 该值走单轮直答(mode=kb);
/// 本地直答阈值(0.45~0.95):融合匹配度 ≥ 该值走单轮直答(mode=kb);
...
...
Tools.Agent/Forms/AgentSettingsForm.cs
View file @
67a4bda7
...
@@ -120,7 +120,7 @@ namespace Tools.MyAgent.Forms
...
@@ -120,7 +120,7 @@ namespace Tools.MyAgent.Forms
InitializeComponent
();
InitializeComponent
();
BuildDbConnectionsTab
();
// P0:预存数据库连接(db_query 按 ID 引用,聊天不回显密码)
BuildDbConnectionsTab
();
// P0:预存数据库连接(db_query 按 ID 引用,聊天不回显密码)
BuildToolPermissionUi
();
// P3:工具级权限(按角色)输入区
BuildToolPermissionUi
();
// P3:工具级权限(按角色)输入区
BuildAnswerShapeTab
();
// 回答形态:旋转思考指示 / 本地直答 / 契约化输出 / 回答附图
// 回答形态不再提供设置页(v1.2):全部内部默认(见 AgentConfig 注释),降低设置页认知负担
BindConfig
();
// 配置值回填控件(控件创建见 Designer.cs)
BindConfig
();
// 配置值回填控件(控件创建见 Designer.cs)
ApplyProviderMode
();
// 布局联动:BaseUrl 行显隐
ApplyProviderMode
();
// 布局联动:BaseUrl 行显隐
ApplyKbMode
();
// 布局联动:共享 账号/密码/测试连接 行显隐
ApplyKbMode
();
// 布局联动:共享 账号/密码/测试连接 行显隐
...
@@ -236,99 +236,6 @@ namespace Tools.MyAgent.Forms
...
@@ -236,99 +236,6 @@ namespace Tools.MyAgent.Forms
return
rules
;
return
rules
;
}
}
// ══ 回答形态:旋转思考指示 / 本地召回直答 / 契约化输出 / 回答附图 ══
private
CheckBox
chkQuickAnswer
;
private
CheckBox
chkConciseAnswer
;
private
CheckBox
chkShowThinking
;
private
CheckBox
chkAnswerImages
;
private
NumericUpDown
numMaxAnswerImages
;
private
NumericUpDown
numKbHitThreshold
;
/// <summary>
/// 新增「回答形态」选项卡(程序化创建,避免侵入设计器):
/// 思考呈现方式、本地召回直答、契约化输出、回答附图与直答阈值。
/// </summary>
private
void
BuildAnswerShapeTab
()
{
var
tabShape
=
new
TabPage
{
Text
=
"回答形态"
,
Padding
=
new
Padding
(
3
)
};
var
grp
=
new
GroupBox
{
Text
=
"回答形态(思考指示 / 本地直答 / 契约化输出 / 附图)"
,
Location
=
new
Point
(
8
,
12
),
Size
=
new
Size
(
596
,
324
),
};
chkQuickAnswer
=
new
CheckBox
{
Text
=
"本地召回直答:知识库命中达阈值时单轮直答(跳过工具循环);召回异常自动降级"
,
Location
=
new
Point
(
16
,
28
),
AutoSize
=
true
,
};
chkConciseAnswer
=
new
CheckBox
{
Text
=
"契约化输出:知识问答三段式(原因/方案/引用),排查类六段式"
,
Location
=
new
Point
(
16
,
56
),
AutoSize
=
true
,
};
chkShowThinking
=
new
CheckBox
{
Text
=
"仍在问答区渲染思考文本(默认关闭:仅右下旋转指示;调试用)"
,
Location
=
new
Point
(
16
,
84
),
AutoSize
=
true
,
};
chkAnswerImages
=
new
CheckBox
{
Text
=
"回答附图:命中文档关联的图片以缩略图栏展示(点击放大)"
,
Location
=
new
Point
(
16
,
112
),
AutoSize
=
true
,
};
var
lblThreshold
=
new
Label
{
Text
=
"直答阈值"
,
Location
=
new
Point
(
16
,
150
),
AutoSize
=
true
};
numKbHitThreshold
=
new
NumericUpDown
{
Location
=
new
Point
(
96
,
146
),
Size
=
new
Size
(
70
,
23
),
DecimalPlaces
=
2
,
Increment
=
0.01
M
,
Minimum
=
0.45
M
,
Maximum
=
0.95
M
,
Value
=
0.72
M
,
};
var
lblMaxImages
=
new
Label
{
Text
=
"附图上限"
,
Location
=
new
Point
(
196
,
150
),
AutoSize
=
true
};
numMaxAnswerImages
=
new
NumericUpDown
{
Location
=
new
Point
(
276
,
146
),
Size
=
new
Size
(
70
,
23
),
Minimum
=
1
,
Maximum
=
8
,
Value
=
4
,
};
var
hint
=
new
Label
{
Text
=
"直答阈值:[阈值, 0.95] 直接以证据作答;[0.45, 阈值) 标注「部分命中」并允许模型补充;"
+
"低于 0.45 视为未召回,回退原多轮工具循环。\r\n"
+
"关闭「本地召回直答」后行为与改造前完全一致(多轮工具循环 + 原回答形态)。"
,
Location
=
new
Point
(
16
,
186
),
AutoSize
=
false
,
Size
=
new
Size
(
564
,
60
),
ForeColor
=
Color
.
Gray
,
};
grp
.
Controls
.
Add
(
chkQuickAnswer
);
grp
.
Controls
.
Add
(
chkConciseAnswer
);
grp
.
Controls
.
Add
(
chkShowThinking
);
grp
.
Controls
.
Add
(
chkAnswerImages
);
grp
.
Controls
.
Add
(
lblThreshold
);
grp
.
Controls
.
Add
(
numKbHitThreshold
);
grp
.
Controls
.
Add
(
lblMaxImages
);
grp
.
Controls
.
Add
(
numMaxAnswerImages
);
grp
.
Controls
.
Add
(
hint
);
tabShape
.
Controls
.
Add
(
grp
);
tabMain
.
TabPages
.
Add
(
tabShape
);
}
// ══ 配置绑定:把 AgentConfig 值回填到控件(设计器仅设默认值) ══
// ══ 配置绑定:把 AgentConfig 值回填到控件(设计器仅设默认值) ══
private
void
BindConfig
()
private
void
BindConfig
()
...
@@ -390,13 +297,7 @@ namespace Tools.MyAgent.Forms
...
@@ -390,13 +297,7 @@ namespace Tools.MyAgent.Forms
// 可选能力页:工具级权限(P3,角色白名单)
// 可选能力页:工具级权限(P3,角色白名单)
txtToolRoles
.
Lines
=
ToolRoleRulesToLines
(
_config
.
ToolRoleAllow
);
txtToolRoles
.
Lines
=
ToolRoleRulesToLines
(
_config
.
ToolRoleAllow
);
// 回答形态页:思考指示 / 本地直答 / 契约化输出 / 附图 / 阈值
// 回答形态组不绑定控件(无设置页):AgentConfig 保持内部默认/配置文件值
chkQuickAnswer
.
Checked
=
_config
.
QuickAnswer
;
chkConciseAnswer
.
Checked
=
_config
.
ConciseAnswer
;
chkShowThinking
.
Checked
=
_config
.
ShowThinking
;
chkAnswerImages
.
Checked
=
_config
.
AnswerWithImages
;
numMaxAnswerImages
.
Value
=
ClampToControl
(
_config
.
MaxAnswerImages
,
numMaxAnswerImages
,
4
);
numKbHitThreshold
.
Value
=
ClampDecimalToControl
(
_config
.
KbHitThreshold
,
numKbHitThreshold
,
0.72
M
);
// 数据库连接(P0):预存连接串(内存明文态)回填表格
// 数据库连接(P0):预存连接串(内存明文态)回填表格
dgvDbConn
.
Rows
.
Clear
();
dgvDbConn
.
Rows
.
Clear
();
...
@@ -853,13 +754,7 @@ namespace Tools.MyAgent.Forms
...
@@ -853,13 +754,7 @@ namespace Tools.MyAgent.Forms
_config
.
EnableImageInput
=
chkImageInput
.
Checked
;
// v2.2 多模态:图片提问开关
_config
.
EnableImageInput
=
chkImageInput
.
Checked
;
// v2.2 多模态:图片提问开关
// 可选能力页:工具级权限(P3)
// 可选能力页:工具级权限(P3)
_config
.
ToolRoleAllow
=
ParseToolRoleRules
(
txtToolRoles
.
Text
);
_config
.
ToolRoleAllow
=
ParseToolRoleRules
(
txtToolRoles
.
Text
);
// 回答形态页:思考指示 / 本地直答 / 契约化输出 / 附图 / 阈值
// 回答形态组不落盘(无设置页):AgentConfig 保持内部默认/配置文件值,投影由 wMyAgent.SaveAgentConfig 负责
_config
.
QuickAnswer
=
chkQuickAnswer
.
Checked
;
_config
.
ConciseAnswer
=
chkConciseAnswer
.
Checked
;
_config
.
ShowThinking
=
chkShowThinking
.
Checked
;
_config
.
AnswerWithImages
=
chkAnswerImages
.
Checked
;
_config
.
MaxAnswerImages
=
(
int
)
numMaxAnswerImages
.
Value
;
_config
.
KbHitThreshold
=
(
double
)
numKbHitThreshold
.
Value
;
// 数据库连接(P0):表格 → 字典(ID 大小写不敏感,空格清理;空 ID 行忽略)
// 数据库连接(P0):表格 → 字典(ID 大小写不敏感,空格清理;空 ID 行忽略)
var
conns
=
new
Dictionary
<
string
,
string
>(
StringComparer
.
OrdinalIgnoreCase
);
var
conns
=
new
Dictionary
<
string
,
string
>(
StringComparer
.
OrdinalIgnoreCase
);
foreach
(
DataGridViewRow
r
in
dgvDbConn
.
Rows
)
foreach
(
DataGridViewRow
r
in
dgvDbConn
.
Rows
)
...
...
Tools.Agent/Kb/KbService.cs
View file @
67a4bda7
...
@@ -1871,6 +1871,67 @@ namespace Tools.MyAgent.Kb
...
@@ -1871,6 +1871,67 @@ namespace Tools.MyAgent.Kb
return
sb
.
ToString
();
return
sb
.
ToString
();
}
}
/// <summary>
/// 提取 docx 内嵌位图到本地缓存并返回绝对路径(v1.3:Word 内嵌图片进入回答附图轨)。
/// <para>docx 是 zip 包,图片在 word/media/* 下,正文提取(<see cref="ReadDocx"/>)不含图片引用,
/// 故附图采集必须显式解包。提取落点 {OutputRoot}/kb_media/{md5(路径)}/{安全文件名} ——
/// 在本机数据目录而**不在知识库内**(共享/只读知识库可安全使用);同名文件已存在直接复用(幂等)。
/// 只返回白名单位图扩展(emf/wmf 矢量图、>10MB 巨图跳过);返回顺序即包内文档序(近似正文出现序)。</para>
/// </summary>
public
List
<
string
>
ExtractDocxImages
(
string
docxPath
,
int
max
)
{
var
result
=
new
List
<
string
>();
if
(
max
<=
0
||
string
.
IsNullOrEmpty
(
docxPath
)
||
!
File
.
Exists
(
docxPath
))
return
result
;
if
(
string
.
IsNullOrEmpty
(
OutputRoot
)
||
!
IsOutputValid
)
return
result
;
try
{
string
cacheDir
=
Path
.
Combine
(
OutputRoot
,
"kb_media"
,
StableHash64
(
NormPath
(
docxPath
)).
Substring
(
0
,
12
));
// 12 位短哈希:caption 可读;本地缓存 48bit 碰撞可忽略
Directory
.
CreateDirectory
(
cacheDir
);
using
(
var
zip
=
ZipFile
.
OpenRead
(
docxPath
))
{
foreach
(
var
entry
in
zip
.
Entries
)
{
if
(
result
.
Count
>=
max
)
break
;
string
fullName
=
entry
.
FullName
??
""
;
// 只认 word/media/ 下的资源(文档正文的内嵌图;页眉页脚/批注图也在此目录,一并可接受)
if
(
fullName
.
IndexOf
(
"word/media/"
,
StringComparison
.
OrdinalIgnoreCase
)
<
0
)
continue
;
string
name
=
Path
.
GetFileName
(
fullName
.
Replace
(
'\\'
,
'/'
))
??
""
;
// 安全文件名:ZipArchiveEntry 名可含路径段/特殊字符,只取末段并拒绝 .. 与非法字符
if
(
name
.
Length
==
0
||
name
.
IndexOf
(
".."
,
StringComparison
.
Ordinal
)
>=
0
||
name
.
IndexOfAny
(
Path
.
GetInvalidFileNameChars
())
>=
0
)
continue
;
string
ext
=
Path
.
GetExtension
(
name
).
ToLowerInvariant
();
if
(!
IsImageExtension
(
ext
))
continue
;
// emf/wmf 等矢量图不进附图轨
if
(
entry
.
Length
>
10
*
1024
*
1024
)
continue
;
// >10MB 巨图跳过(缩略图加载不值)
string
abs
=
Path
.
Combine
(
cacheDir
,
name
);
if
(!
File
.
Exists
(
abs
)
||
new
FileInfo
(
abs
).
Length
!=
entry
.
Length
)
{
try
{
entry
.
ExtractToFile
(
abs
,
true
);
}
catch
{
continue
;
}
}
if
(
File
.
Exists
(
abs
))
result
.
Add
(
abs
);
}
}
}
catch
(
Exception
ex
)
{
try
{
AgentLogger
.
Log
(
"提取 docx 内嵌图失败:"
+
ex
.
Message
);
}
catch
{
}
}
return
result
;
}
/// <summary>路径 → 16 进制短哈希(缓存目录键;MD5 足够,非安全用途)</summary>
private
static
string
StableHash64
(
string
s
)
{
using
(
var
md5
=
System
.
Security
.
Cryptography
.
MD5
.
Create
())
{
var
bytes
=
md5
.
ComputeHash
(
Encoding
.
UTF8
.
GetBytes
(
s
??
""
));
var
sb
=
new
StringBuilder
(
32
);
foreach
(
byte
b
in
bytes
)
sb
.
Append
(
b
.
ToString
(
"x2"
));
return
sb
.
ToString
();
}
}
/// <summary>
/// <summary>
/// 提取 PDF 全文(PdfPig net45,支持文本层和中文字体映射)。
/// 提取 PDF 全文(PdfPig net45,支持文本层和中文字体映射)。
/// 扫描版(无文字层)PDF 返回 null,由调用方提示 OCR。
/// 扫描版(无文字层)PDF 返回 null,由调用方提示 OCR。
...
...
Tools.Agent/Support/ChatRtfCache.cs
View file @
67a4bda7
...
@@ -16,6 +16,14 @@ namespace Tools.MyAgent
...
@@ -16,6 +16,14 @@ namespace Tools.MyAgent
private
string
_userName
=
""
;
private
string
_userName
=
""
;
private
readonly
List
<
string
>
_keys
=
new
List
<
string
>();
private
readonly
List
<
string
>
_keys
=
new
List
<
string
>();
private
readonly
List
<
string
>
_blocks
=
new
List
<
string
>();
private
readonly
List
<
string
>
_blocks
=
new
List
<
string
>();
private
readonly
List
<
List
<
string
>>
_msgImages
=
new
List
<
List
<
string
>>();
/// <summary>
/// 最近一次 Build 的缩略图序列(v1.3.5):全部消息成功内联成 \pict 的图片绝对路径按渲染顺序拼接。
/// 与 IRichEditOle 对象表(文档序,wMyAgent.RebuildAnswerRefs 读取)按下标一一对应,
/// 供「点击缩略图放大」映射。命中缓存的块沿用上次记录。
/// </summary>
public
List
<
string
>
LastImageSequence
{
get
;
private
set
;
}
public
string
Build
(
List
<
ChatMessage
>
messages
,
string
welcomeText
,
string
assistantName
,
string
userName
,
Func
<
int
,
bool
>
isDetailsExpanded
=
null
)
public
string
Build
(
List
<
ChatMessage
>
messages
,
string
welcomeText
,
string
assistantName
,
string
userName
,
Func
<
int
,
bool
>
isDetailsExpanded
=
null
)
{
{
...
@@ -37,6 +45,7 @@ namespace Tools.MyAgent
...
@@ -37,6 +45,7 @@ namespace Tools.MyAgent
{
{
_keys
.
Add
(
null
);
_keys
.
Add
(
null
);
_blocks
.
Add
(
null
);
_blocks
.
Add
(
null
);
_msgImages
.
Add
(
null
);
}
}
var
sb
=
new
StringBuilder
(
4096
);
var
sb
=
new
StringBuilder
(
4096
);
...
@@ -53,7 +62,11 @@ namespace Tools.MyAgent
...
@@ -53,7 +62,11 @@ namespace Tools.MyAgent
string
key
=
MessageKey
(
messages
[
i
],
_assistantName
,
_userName
,
expanded
);
string
key
=
MessageKey
(
messages
[
i
],
_assistantName
,
_userName
,
expanded
);
if
(!
string
.
Equals
(
_keys
[
i
],
key
,
StringComparison
.
Ordinal
))
if
(!
string
.
Equals
(
_keys
[
i
],
key
,
StringComparison
.
Ordinal
))
{
{
// v1.3.5:重渲染该块时借 MarkdownRenderer.ImageCollector 记录成功内联的图片(渲染顺序)
MarkdownRenderer
.
ImageCollector
=
new
List
<
string
>();
_blocks
[
i
]
=
MarkdownRenderer
.
RenderMessageBlock
(
messages
[
i
],
_assistantName
,
_userName
,
expanded
);
_blocks
[
i
]
=
MarkdownRenderer
.
RenderMessageBlock
(
messages
[
i
],
_assistantName
,
_userName
,
expanded
);
_msgImages
[
i
]
=
MarkdownRenderer
.
ImageCollector
??
new
List
<
string
>();
MarkdownRenderer
.
ImageCollector
=
null
;
_keys
[
i
]
=
key
;
_keys
[
i
]
=
key
;
}
}
sb
.
Append
(
_blocks
[
i
]);
sb
.
Append
(
_blocks
[
i
]);
...
@@ -61,6 +74,16 @@ namespace Tools.MyAgent
...
@@ -61,6 +74,16 @@ namespace Tools.MyAgent
sb
.
Append
(
@"\par"
);
sb
.
Append
(
@"\par"
);
}
}
}
}
// 缩略图点击映射(v1.3.5):文档内 \pict 按消息顺序排列,与 _msgImages 拼接顺序一致
var
seq
=
new
List
<
string
>();
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
var
imgs
=
i
<
_msgImages
.
Count
?
_msgImages
[
i
]
:
null
;
if
(
imgs
!=
null
)
seq
.
AddRange
(
imgs
);
}
LastImageSequence
=
seq
;
sb
.
Append
(
MarkdownRenderer
.
RtfSuffix
);
sb
.
Append
(
MarkdownRenderer
.
RtfSuffix
);
return
sb
.
ToString
();
return
sb
.
ToString
();
}
}
...
@@ -70,6 +93,8 @@ namespace Tools.MyAgent
...
@@ -70,6 +93,8 @@ namespace Tools.MyAgent
{
{
_keys
.
Clear
();
_keys
.
Clear
();
_blocks
.
Clear
();
_blocks
.
Clear
();
_msgImages
.
Clear
();
LastImageSequence
=
new
List
<
string
>();
_welcomeKey
=
""
;
_welcomeKey
=
""
;
_welcomeRtf
=
""
;
_welcomeRtf
=
""
;
_assistantName
=
""
;
_assistantName
=
""
;
...
...
Tools.Agent/Support/MarkdownRenderer.cs
View file @
67a4bda7
This diff is collapsed.
Click to expand it.
Tools.Agent/Support/RichEditObjects.cs
0 → 100644
View file @
67a4bda7
using
System
;
using
System.Collections.Generic
;
using
System.Runtime.InteropServices
;
using
System.Windows.Forms
;
namespace
Tools.MyAgent
{
/// <summary>
/// RichEdit 内联对象位置表(v1.3.5 缩略图点击):经 EM_GETOLEINTERFACE 取 IRichEditOle,
/// 逐个读取 REOBJECT.cp —— \pict 在 RichEdit20W 文本流中占一个对象字符,cp 即它在 Text 里的下标;
/// 对象按文档序返回,与渲染序列(ChatRtfCache.LastImageSequence)按下标一一对应。
/// <para>背景:实测 \pict 的对象字符在 RichTextBox.Text 里表现为 0x20(与真实空格无法区分),文本扫描不可行;
/// 本互操作是唯一可靠的命中通道。任何异常/可疑数据都返回空表(功能静默降级为不可点,不影响其余交互)。</para>
/// </summary>
internal
static
class
RichEditObjects
{
private
const
int
EmGetOleInterface
=
0x043C
;
// WM_USER + 60
/// <summary>REO_GETOBJ_NO_INTERFACES:只取结构体(cp/clsid),不取 COM 接口指针</summary>
private
const
int
ReoGetObjNoInterfaces
=
0x0
;
/// <summary>对象数上限(超限视为互操作异常数据,整体弃用)</summary>
private
const
int
MaxObjects
=
4096
;
[
StructLayout
(
LayoutKind
.
Sequential
)]
private
struct
ReObject
{
public
int
cbObject
;
public
int
cp
;
public
Guid
clsid
;
public
IntPtr
oleobj
;
public
IntPtr
stg
;
public
int
dvAspect
;
public
int
dwFlags
;
public
IntPtr
dwUser
;
}
[
ComImport
,
Guid
(
"00020D00-0000-0000-C000-000000000046"
),
InterfaceType
(
ComInterfaceType
.
InterfaceIsIUnknown
)]
private
interface
IRichEditOle
{
// vtable 顺序与 richole.h 严格一致;.NET interop 允许只声明用到的前缀方法
[
PreserveSig
]
int
GetClientSite
(
out
IntPtr
callback
);
[
PreserveSig
]
int
GetObjectCount
();
[
PreserveSig
]
int
GetObject
(
int
iob
,
ref
ReObject
reo
,
int
flags
);
}
[
DllImport
(
"user32.dll"
)]
private
static
extern
IntPtr
SendMessage
(
IntPtr
hWnd
,
int
msg
,
int
wParam
,
out
IntPtr
lParam
);
/// <summary>返回控件里全部内联对象(\pict 缩略图)的字符位置(文档序);异常或数据可疑时返回空表。</summary>
internal
static
List
<
int
>
GetObjectCharPositions
(
RichTextBox
rtb
)
{
var
positions
=
new
List
<
int
>();
try
{
if
(
rtb
==
null
||
!
rtb
.
IsHandleCreated
)
return
positions
;
IntPtr
punk
;
SendMessage
(
rtb
.
Handle
,
EmGetOleInterface
,
0
,
out
punk
);
if
(
punk
==
IntPtr
.
Zero
)
return
positions
;
try
{
var
ole
=
(
IRichEditOle
)
Marshal
.
GetObjectForIUnknown
(
punk
);
try
{
int
count
=
ole
.
GetObjectCount
();
if
(
count
<
0
||
count
>
MaxObjects
)
return
positions
;
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
var
reo
=
new
ReObject
();
reo
.
cbObject
=
Marshal
.
SizeOf
(
typeof
(
ReObject
));
if
(
ole
.
GetObject
(
i
,
ref
reo
,
ReoGetObjNoInterfaces
)
!=
0
)
break
;
positions
.
Add
(
reo
.
cp
);
}
}
finally
{
Marshal
.
ReleaseComObject
(
ole
);
}
}
finally
{
Marshal
.
Release
(
punk
);
}
// 完整性校验:位置须严格递增且落在文本范围内,否则视为互操作异常整体弃用
int
textLen
=
rtb
.
TextLength
;
for
(
int
i
=
0
;
i
<
positions
.
Count
;
i
++)
{
if
(
positions
[
i
]
<
0
||
positions
[
i
]
>=
textLen
||
(
i
>
0
&&
positions
[
i
]
<=
positions
[
i
-
1
]))
{
positions
.
Clear
();
break
;
}
}
}
catch
{
positions
.
Clear
();
}
return
positions
;
}
}
}
Tools.Agent/Tools.Agent.csproj
View file @
67a4bda7
...
@@ -164,6 +164,7 @@
...
@@ -164,6 +164,7 @@
<Compile
Include=
"Support\NoCaretRichTextBox.cs"
>
<Compile
Include=
"Support\NoCaretRichTextBox.cs"
>
<SubType>
Component
</SubType>
<SubType>
Component
</SubType>
</Compile>
</Compile>
<Compile
Include=
"Support\RichEditObjects.cs"
/>
<Compile
Include=
"Support\StorageCleanup.cs"
/>
<Compile
Include=
"Support\StorageCleanup.cs"
/>
<Compile
Include=
"Support\TextEncoding.cs"
/>
<Compile
Include=
"Support\TextEncoding.cs"
/>
<Compile
Include=
"Support\TextPatterns.cs"
/>
<Compile
Include=
"Support\TextPatterns.cs"
/>
...
...
Tools.Agent/wMyAgent.cs
View file @
67a4bda7
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment