其中文章 《你知道9月1日新实施的〈人工智能生成合成内容标识办法〉吗?》 中使用的标识插件就是这个。
示范内容
比如下面文字是DeepSeek生成的:
[label txt=AI生成]为个人网站上的AI生成内容添加显式标识,既是遵守法规的要求,也是对自己和读者负责任的表现。
先审核你网站上现有的内容,区分出哪些是AI生成的。
根据内容类型(文本、图片、视频等),参考表格中的方法,尽快补充添加显式标识。
未来发布新内容时,养成先添加标识再发布的习惯。
考虑采用一些工具或技巧来简化标识流程,并关注官方的后续技术标准和指南。
希望这份详细的指南能对你有所帮助!如果你对特定类型内容的标识还有更具体的问题,可以告诉我。[/label]
[label txt=AI生成内容 type=warning]这是一段AI生成的文本,请注意甄别真假。[/label]
[label txt=敏感内容 type=danger]这段内容包含敏感信息。[/label]
[label txt=提示信息 type=info]这是一条重要提示。[/label]
[label txt=普通标识]这是普通的标识文本。[/label]
下面是示范图片,由豆包生成 (提示词:墨水书法,一行字:喜欢捣鼓 不断进步 仅文字):
[label txt=此内容AI生成,注意甄别真假]
[/label]
下载插件
添加插件文件夹:LabelMark
Plugin.php文件:
<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
* 内容标识插件,为指定内容添加标识遮罩或标签
*
* @package LabelMark
* @author 寒士杰克
* @version 1.0.1
* @link http://www.hansjack.com
*/
class LabelMark_Plugin implements Typecho_Plugin_Interface
{
/**
* 激活插件方法,如果激活失败,直接抛出异常
*
* @access public
* @return void
* @throws Typecho_Plugin_Exception
*/
public static function activate()
{
Typecho_Plugin::factory('Content')->LabelMark = array('LabelMark_Plugin', 'parse');
}
/**
* 禁用插件方法,如果禁用失败,直接抛出异常
*
* @access public
* @return void
* @throws Typecho_Plugin_Exception
*/
public static function deactivate(){}
/**
* 获取插件配置面板
*
* @access public
* @param Typecho_Widget_Helper_Form $form 配置面板
* @return void
*/
public static function config(Typecho_Widget_Helper_Form $form)
{
$maskBackground = new Typecho_Widget_Helper_Form_Element_Text(
'maskBackground',
NULL,
'rgba(0, 0, 0, 0.8)',
_t('遮罩背景色'),
_t('设置图片和视频遮罩层的背景色,支持rgba格式')
);
$form->addInput($maskBackground);
$textLabelColor = new Typecho_Widget_Helper_Form_Element_Text(
'textLabelColor',
NULL,
'#ff6b6b',
_t('文本标签颜色'),
_t('设置文本标签的背景颜色')
);
$form->addInput($textLabelColor);
$textContentBg = new Typecho_Widget_Helper_Form_Element_Text(
'textContentBg',
NULL,
'#fff3cd',
_t('文本内容背景色'),
_t('设置被标识文本内容的背景颜色,建议使用淡色')
);
$form->addInput($textContentBg);
$textContentBorder = new Typecho_Widget_Helper_Form_Element_Text(
'textContentBorder',
NULL,
'#ffeaa7',
_t('文本内容边框色'),
_t('设置被标识文本内容的边框颜色')
);
$form->addInput($textContentBorder);
$labelPosition = new Typecho_Widget_Helper_Form_Element_Radio(
'labelPosition',
array(
'top-right' => _t('右上角'),
'top-left' => _t('左上角'),
'bottom-right' => _t('右下角'),
'bottom-left' => _t('左下角')
),
'top-right',
_t('标签位置'),
_t('设置文本标签显示的位置')
);
$form->addInput($labelPosition);
$animation = new Typecho_Widget_Helper_Form_Element_Radio(
'animation',
array('enable' => _t('启用'), 'disable' => _t('禁用')),
'enable',
_t('动画效果'),
_t('是否启用悬停动画效果')
);
$form->addInput($animation);
}
/**
* 个人用户的配置面板
*
* @access public
* @param Typecho_Widget_Helper_Form $form
* @return void
*/
public static function personalConfig(Typecho_Widget_Helper_Form $form){}
/**
* 插件实现方法
*
* @access public
* @param string $content 文章内容
* @return string
*/
public static function parse($content)
{
$options = Helper::options();
// 获取插件配置
$pluginConfig = $options->plugin('LabelMark');
$maskBackground = isset($pluginConfig->maskBackground) ? $pluginConfig->maskBackground : 'rgba(0, 0, 0, 0.8)';
$textLabelColor = isset($pluginConfig->textLabelColor) ? $pluginConfig->textLabelColor : '#ff6b6b';
$textContentBg = isset($pluginConfig->textContentBg) ? $pluginConfig->textContentBg : '#fff3cd';
$textContentBorder = isset($pluginConfig->textContentBorder) ? $pluginConfig->textContentBorder : '#ffeaa7';
$labelPosition = isset($pluginConfig->labelPosition) ? $pluginConfig->labelPosition : 'top-right';
$animation = isset($pluginConfig->animation) ? $pluginConfig->animation : 'enable';
// 添加CSS样式
self::addStyles($maskBackground, $textLabelColor, $textContentBg, $textContentBorder, $labelPosition, $animation);
// 解析标签
$content = self::parseLabels($content);
return $content;
}
/**
* 添加样式
*/
private static function addStyles($maskBackground, $textLabelColor, $textContentBg, $textContentBorder, $labelPosition, $animation)
{
static $styleAdded = false;
if ($styleAdded) return;
$animationClass = $animation == 'enable' ? 'labelmark-animation' : '';
// 根据位置设置标签位置样式
$labelPositionStyle = self::getLabelPositionStyle($labelPosition);
$css = <<<CSS
<style>
.labelmark-container {
position: relative;
display: inline-block;
vertical-align: top;
}
.labelmark-text-container {
position: relative;
display: inline-block;
background: {$textContentBg};
border: 2px solid {$textContentBorder};
border-radius: 6px;
padding: 8px 12px;
margin: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
min-height: 20px;
line-height: 1.5;
}
.labelmark-text-content {
position: relative;
z-index: 1;
}
.labelmark-media-container {
position: relative;
display: inline-block;
}
.labelmark-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: {$maskBackground};
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: bold;
text-align: center;
z-index: 10;
cursor: pointer;
border-radius: 4px;
opacity: 1;
visibility: visible;
}
.labelmark-media-container:hover .labelmark-mask {
opacity: 0;
visibility: hidden;
}
.labelmark-text-label {
position: absolute;
{$labelPositionStyle}
background: {$textLabelColor};
color: white;
font-size: 11px;
padding: 3px 8px;
border-radius: 12px;
font-weight: bold;
z-index: 5;
white-space: nowrap;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
border: 2px solid rgba(255,255,255,0.2);
}
/* 不同位置的标签样式 */
.labelmark-text-label.position-top-right {
top: -10px;
right: -10px;
}
.labelmark-text-label.position-top-left {
top: -10px;
left: -10px;
}
.labelmark-text-label.position-bottom-right {
bottom: -10px;
right: -10px;
}
.labelmark-text-label.position-bottom-left {
bottom: -10px;
left: -10px;
}
/* 动画效果 */
.labelmark-animation .labelmark-mask {
transition: all 0.3s ease;
}
.labelmark-animation .labelmark-text-label {
transition: all 0.3s ease;
}
.labelmark-animation .labelmark-text-container {
transition: all 0.3s ease;
}
.labelmark-animation .labelmark-text-container:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.labelmark-animation .labelmark-text-container:hover .labelmark-text-label {
transform: scale(1.1);
box-shadow: 0 3px 8px rgba(0,0,0,0.4);
}
/* 响应式设计 */
@media (max-width: 768px) {
.labelmark-text-label {
font-size: 9px;
padding: 2px 6px;
}
.labelmark-mask {
font-size: 14px;
}
.labelmark-text-container {
padding: 6px 10px;
margin: 2px;
}
}
/* 视频特殊处理 */
.labelmark-video-container {
position: relative;
display: block;
width: 100%;
border-radius: 8px;
overflow: hidden;
}
.labelmark-video-container video,
.labelmark-video-container iframe {
width: 100%;
height: auto;
display: block;
}
/* 图片特殊处理 */
.labelmark-image-container {
border-radius: 8px;
overflow: hidden;
}
.labelmark-image-container img {
max-width: 100%;
height: auto;
display: block;
}
/* 特殊样式:警告类型 */
.labelmark-warning .labelmark-text-container {
background: #fff3cd;
border-color: #ffeaa7;
}
.labelmark-danger .labelmark-text-container {
background: #f8d7da;
border-color: #f5c6cb;
}
.labelmark-info .labelmark-text-container {
background: #d1ecf1;
border-color: #b8daff;
}
/* 脉冲动画 */
@keyframes labelmark-pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.labelmark-pulse .labelmark-text-label {
animation: labelmark-pulse 2s infinite;
}
</style>
CSS;
echo $css;
$styleAdded = true;
}
/**
* 获取标签位置样式
*/
private static function getLabelPositionStyle($position)
{
switch($position) {
case 'top-left':
return 'top: -10px; left: -10px;';
case 'bottom-right':
return 'bottom: -10px; right: -10px;';
case 'bottom-left':
return 'bottom: -10px; left: -10px;';
case 'top-right':
default:
return 'top: -10px; right: -10px;';
}
}
/**
* 解析标签内容
*/
private static function parseLabels($content)
{
// 匹配 [label txt=xxx]content[/label] 格式,支持可选的type参数
$pattern = '/\[label\s+txt=([^\]]+?)(?:\s+type=([^\]]+?))?\](.*?)\[\/label\]/s';
return preg_replace_callback($pattern, function($matches) {
$labelText = trim($matches[1], '"\'');
$type = isset($matches[2]) ? trim($matches[2], '"\'') : 'default';
$innerContent = $matches[3];
// 检测内容类型
if (self::containsImage($innerContent)) {
return self::wrapImageContent($innerContent, $labelText, $type);
} elseif (self::containsVideo($innerContent)) {
return self::wrapVideoContent($innerContent, $labelText, $type);
} else {
return self::wrapTextContent($innerContent, $labelText, $type);
}
}, $content);
}
/**
* 检测是否包含图片
*/
private static function containsImage($content)
{
return preg_match('/!\[.*?\]\(.*?\)|<img[^>]*>/i', $content);
}
/**
* 检测是否包含视频
*/
private static function containsVideo($content)
{
return preg_match('/<video[^>]*>|<iframe[^>]*youtube|<iframe[^>]*vimeo|<iframe[^>]*bilibili/i', $content);
}
/**
* 包装图片内容
*/
private static function wrapImageContent($content, $labelText, $type)
{
// 处理 Markdown 格式的图片
$content = preg_replace_callback('/!\[(.*?)\]\((.*?)\)/', function($matches) {
$alt = $matches[1];
$src = $matches[2];
return '<img src="' . htmlspecialchars($src) . '" alt="' . htmlspecialchars($alt) . '" />';
}, $content);
$typeClass = $type !== 'default' ? 'labelmark-' . $type : '';
return '<div class="labelmark-container labelmark-media-container labelmark-image-container labelmark-animation ' . $typeClass . '">' .
$content .
'<div class="labelmark-mask">' . htmlspecialchars($labelText) . '</div>' .
'</div>';
}
/**
* 包装视频内容
*/
private static function wrapVideoContent($content, $labelText, $type)
{
$typeClass = $type !== 'default' ? 'labelmark-' . $type : '';
return '<div class="labelmark-container labelmark-media-container labelmark-video-container labelmark-animation ' . $typeClass . '">' .
$content .
'<div class="labelmark-mask">' . htmlspecialchars($labelText) . '</div>' .
'</div>';
}
/**
* 包装文本内容
*/
private static function wrapTextContent($content, $labelText, $type)
{
$options = Helper::options();
$pluginConfig = $options->plugin('LabelMark');
$labelPosition = isset($pluginConfig->labelPosition) ? $pluginConfig->labelPosition : 'top-right';
$typeClass = $type !== 'default' ? 'labelmark-' . $type : '';
$pulseClass = in_array($type, ['warning', 'danger']) ? 'labelmark-pulse' : '';
return '<span class="labelmark-container labelmark-text-container labelmark-animation ' . $typeClass . ' ' . $pulseClass . '">' .
'<span class="labelmark-text-content">' . $content . '</span>' .
'<span class="labelmark-text-label position-' . $labelPosition . '">' . htmlspecialchars($labelText) . '</span>' .
'</span>';
}
}
?>
使用教程
1、添加渲染
在文章内容输出前添加LableMark的渲染:
比如BearSimple主题,原输出 (post.php文件):
<?php echo reEmoPost(ShortCode($this->content,$this,$this->user->hasLogin(),$this->fields->ArticleType)); ?>
将 LabelMark 插件的处理加入到现有的处理链中:
<?php
// 获取原始内容
$content = $this->content;
// 先应用 LabelMark 插件处理
$content = Typecho_Plugin::factory('Content')->LabelMark($content);
// 然后应用你原有的其他处理函数
echo reEmoPost(ShortCode($content, $this, $this->user->hasLogin(), $this->fields->ArticleType));
?>
或者,如果你希望 LabelMark 在其他处理之后执行:
<?php
// 获取原始内容
$content = $this->content;
// 先应用原有的处理函数
$content = reEmoPost(ShortCode($content, $this, $this->user->hasLogin(), $this->fields->ArticleType));
// 最后应用 LabelMark 插件处理
$content = Typecho_Plugin::factory('Content')->LabelMark($content);
echo $content;
?>
[label txt=AI生成]我推荐使用第一种方案,让 LabelMark 先处理,因为:
LabelMark 处理的是原始 Markdown 格式的 label 标签
ShortCode 可能也会处理一些类似的标签格式
reEmoPost 处理表情符号[/label]
备注内容
如果上述方法仍然有内容渲染问题,也可以考虑修改插件的激活方式,让它自动集成到 Typecho 的内容处理流程中:
修改 Plugin.php 中的 activate 方法:
public static function activate()
{
// 使用 Typecho 的内容过滤器
Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('LabelMark_Plugin', 'parse');
Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx = array('LabelMark_Plugin', 'parse');
}
这样插件会自动在内容输出时处理,你就不需要在模板中手动调用了,直接使用原来的输出
2、添加文章内容
[label txt=标签示例,仅供参考,部分不适用 type=info] [/label]
评论区