Merge 7ecd04b68d
into e85f9f2882
This commit is contained in:
commit
f74d2bbbe6
2
.gitignore
vendored
2
.gitignore
vendored
@ -29,3 +29,5 @@ dist-ssr
|
||||
*.local
|
||||
/pywxdump/ui/web/*
|
||||
/pywxdump/ui/web/assets/*
|
||||
/pywxdump/wxdump_work
|
||||
test2.py
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-#
|
||||
# -------------------------------------------------------------------------------
|
||||
# Name: utils.py
|
||||
# Name: api_utils.py
|
||||
# Description:
|
||||
# Author: xaoyaoo
|
||||
# Date: 2023/12/03
|
||||
|
0
pywxdump/api/api_utils/__init__.py
Normal file
0
pywxdump/api/api_utils/__init__.py
Normal file
14
pywxdump/api/api_utils/dify.py
Normal file
14
pywxdump/api/api_utils/dify.py
Normal file
@ -0,0 +1,14 @@
|
||||
# 接入dify工作流,用来制作微信聊天记录可视化
|
||||
|
||||
class Dify(object):
|
||||
def __init__(self):
|
||||
api_key = ""
|
||||
api_base_url = ""
|
||||
|
||||
#TODO: 文件上传
|
||||
def file_upload(self):
|
||||
pass
|
||||
|
||||
#TODO: 运行工作流
|
||||
def run_workflows(self):
|
||||
pass
|
939
pywxdump/api/api_utils/html.py
Normal file
939
pywxdump/api/api_utils/html.py
Normal file
File diff suppressed because one or more lines are too long
476
pywxdump/api/api_utils/llm.py
Normal file
476
pywxdump/api/api_utils/llm.py
Normal file
@ -0,0 +1,476 @@
|
||||
# LLM api相关
|
||||
import enum
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
import httpx
|
||||
import openai
|
||||
from openai import OpenAI
|
||||
|
||||
from pywxdump.api.remote_server import gc
|
||||
|
||||
|
||||
|
||||
|
||||
class BaseLLMApi(object):
|
||||
def __init__(self,api_key,base_url=None):
|
||||
# 设置名字,以供其他函数使用 !!!不使用,
|
||||
# self.api_key_string = "API_KEY"
|
||||
# self.base_url_string = "BASE_URL"
|
||||
# self.env_api_key_string = self.__class__.__name__ + "_" + self.api_key_string
|
||||
# self.env_base_url_string = self.__class__.__name__ + "_" + self.base_url_string
|
||||
# self.setting_string = self.__class__.__name__ + "_setting"
|
||||
|
||||
|
||||
self.API_KEY = api_key
|
||||
self.BASE_URL = base_url
|
||||
|
||||
self.module = (
|
||||
|
||||
)#模型列表
|
||||
|
||||
|
||||
self.HTTP_CLIENT = None
|
||||
self.isReady = False
|
||||
self.message = []
|
||||
|
||||
|
||||
# 执行初始化方法
|
||||
self.set_default_fn()
|
||||
|
||||
|
||||
|
||||
|
||||
def set_default_fn(self):
|
||||
if not self.module:
|
||||
self.set_default_module()
|
||||
if not self.BASE_URL:
|
||||
self.set_default_base_url()
|
||||
if not self.message:
|
||||
self.set_default_message()
|
||||
|
||||
def set_default_module(self):
|
||||
self.module = ()
|
||||
|
||||
def set_default_base_url(self):
|
||||
self.BASE_URL = ""
|
||||
|
||||
def set_default_message(self):
|
||||
"""
|
||||
要确保message中至少有两个元素,第一个元素为系统消息,第二个元素为用户消息,且第二个元素中有{{content}}
|
||||
"""
|
||||
self.message = [
|
||||
{"role": "system", "content": "You are a helpful assistant"},
|
||||
{"role": "user", "content": "Hello {{content}}"},
|
||||
]
|
||||
|
||||
|
||||
|
||||
def set_module(self, module):
|
||||
self.module = module
|
||||
|
||||
def set_api_key(self, api_key):
|
||||
self.API_KEY = api_key
|
||||
|
||||
def set_base_url(self, base_url):
|
||||
self.BASE_URL = base_url
|
||||
|
||||
|
||||
def set_message(self, message):
|
||||
self.message = message
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# def ready(self):
|
||||
# if not self.API_KEY:
|
||||
# # 从配置中获取,这个功能必须配合网页API开启后才能使用
|
||||
# self.API_KEY = gc.get_conf(gc.at,self.setting_string)[self.api_key_string]
|
||||
# if not self.API_KEY:
|
||||
# raise RuntimeError("API_KEY must be set")
|
||||
# # 设置环境变量
|
||||
# os.environ[self.env_api_key_string] = self.API_KEY
|
||||
# os.environ[self.env_base_url_string] = self.BASE_URL
|
||||
#
|
||||
# self.isReady = True
|
||||
# return
|
||||
|
||||
|
||||
def ready(self):
|
||||
if not self.BASE_URL and not self.API_KEY:
|
||||
raise RuntimeError("API_KEY or BASE_URL must be set")
|
||||
self.isReady = True
|
||||
|
||||
|
||||
|
||||
@property
|
||||
def http_client(self):
|
||||
if not self.isReady:
|
||||
self.ready()
|
||||
try:
|
||||
self.HTTP_CLIENT = OpenAI(api_key=self.API_KEY, base_url=self.BASE_URL)
|
||||
return self.HTTP_CLIENT
|
||||
except:
|
||||
raise RuntimeError("HTTP_CLIENT set not successfully,please check!")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def send_msg(self, message=None, module=None, stream=False):
|
||||
"""
|
||||
向大模型发送信息
|
||||
如果非流式返回,则直接输出内容,
|
||||
否则使用openai文档规定格式输出
|
||||
"""
|
||||
|
||||
if message is None:
|
||||
self.message[1]["content"] = self.message[1].get("content").replace("{{content}}", " ")
|
||||
message = self.message
|
||||
else:
|
||||
self.message[1]["content"] = self.message[1].get("content").replace("{{content}}", message)
|
||||
message = self.message
|
||||
|
||||
response = self.http_client.chat.completions.create(
|
||||
model=self.module[module],
|
||||
messages=message,
|
||||
stream=stream
|
||||
)
|
||||
if not stream:
|
||||
return self.process_msg(response.choices[0].message.content)
|
||||
else:
|
||||
return self.process_msg(response.response.read().decode("utf-8"))
|
||||
|
||||
|
||||
def process_msg(self,x):
|
||||
return x
|
||||
|
||||
class DeepSeekApi(BaseLLMApi):
|
||||
|
||||
|
||||
def set_default_module(self):
|
||||
self.module = (
|
||||
"deepseek-chat",
|
||||
"deepseek-reasoner"
|
||||
)
|
||||
|
||||
def set_default_base_url(self):
|
||||
self.BASE_URL = "https://api.deepseek.com"
|
||||
|
||||
def set_default_message(self):
|
||||
self.message = [
|
||||
{"role": "system", "content": """从内容中提取出以下信息,可以根据内容多少进行列表扩展或增加,请仔细思索怎么填充内容,如果没有给到合理的名称或其他内容,就以合理的方式思考并添加。
|
||||
|
||||
- 最后的输出值使用严格的json格式
|
||||
|
||||
- 不要私自添加json块或减少json块
|
||||
|
||||
- 内容中不要使用换行符,如果内容原本有多个换行符,删掉原本多余的的换行符,只保留一个换行符再加入进去。
|
||||
|
||||
- 内容中如果有很奇怪的字符,比如''\''或''\\''影响代码编译的字符,删除原本的字符再加入进去。
|
||||
|
||||
|
||||
|
||||
{
|
||||
|
||||
"header": {
|
||||
|
||||
"title": "[群名称]报告",
|
||||
|
||||
"date": "[日期]",
|
||||
|
||||
"metaInfo": {
|
||||
|
||||
"totalMessages": "[数量]",
|
||||
|
||||
"activeUsers": "[数量]",
|
||||
|
||||
"timeRange": "[时间范围]"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"sections": {
|
||||
|
||||
"hotTopics": {
|
||||
|
||||
"items": [
|
||||
|
||||
{
|
||||
|
||||
"name": "[热点话题名称]",
|
||||
|
||||
"category": "[话题分类]",
|
||||
|
||||
"summary": "[简要总结(50-100字)]",
|
||||
|
||||
"keywords": ["[关键词1]", "[关键词2]"],
|
||||
|
||||
"mentions": "[次数]"
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"tutorials": {
|
||||
|
||||
"items": [
|
||||
|
||||
{
|
||||
|
||||
"type": "[TUTORIAL | NEWS | RESOURCE]",
|
||||
|
||||
"title": "[分享的教程或资源标题]",
|
||||
|
||||
"sharedBy": "[昵称]",
|
||||
|
||||
"time": "[时间]",
|
||||
|
||||
"summary": "[内容简介]",
|
||||
|
||||
"keyPoints": ["[要点1]", "[要点2]"],
|
||||
|
||||
"url": "[URL]",
|
||||
|
||||
"domain": "[域名]",
|
||||
|
||||
"category": "[分类]"
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"importantMessages": {
|
||||
|
||||
"items": [
|
||||
|
||||
{
|
||||
|
||||
"time": "[消息时间]",
|
||||
|
||||
"sender": "[发送者昵称]",
|
||||
|
||||
"type": "[NOTICE | EVENT | ANNOUNCEMENT | OTHER]",
|
||||
|
||||
"priority": "[高|中|低]",
|
||||
|
||||
"content": "[消息内容]",
|
||||
|
||||
"fullContent": "[完整通知内容]"
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"dialogues": {
|
||||
|
||||
"items": [
|
||||
|
||||
{
|
||||
|
||||
"type": "[DIALOGUE | QUOTE]",
|
||||
|
||||
"messages": [
|
||||
|
||||
{
|
||||
|
||||
"speaker": "[说话者昵称]",
|
||||
|
||||
"time": "[发言时间]",
|
||||
|
||||
"content": "[消息内容]"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"highlight": "[对话中的金句或亮点]",
|
||||
|
||||
"relatedTopic": "[某某话题]"
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"qa": {
|
||||
|
||||
"items": [
|
||||
|
||||
{
|
||||
|
||||
"question": {
|
||||
|
||||
"asker": "[提问者昵称]",
|
||||
|
||||
"time": "[提问时间]",
|
||||
|
||||
"content": "[问题内容]",
|
||||
|
||||
"tags": ["[相关标签1]", "[相关标签2]"]
|
||||
|
||||
},
|
||||
|
||||
"answers": [
|
||||
|
||||
{
|
||||
|
||||
"responder": "[回答者昵称]",
|
||||
|
||||
"time": "[回答时间]",
|
||||
|
||||
"content": "[回答内容]",
|
||||
|
||||
"isAccepted": true
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"analytics": {
|
||||
|
||||
"heatmap": [
|
||||
|
||||
{
|
||||
|
||||
"topic": "[话题名称]",
|
||||
|
||||
"percentage": "[百分比]",
|
||||
|
||||
"color": "#3da9fc",
|
||||
|
||||
"count": "[数量]"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"chattyRanking": [
|
||||
|
||||
{
|
||||
|
||||
"rank": 1,
|
||||
|
||||
"name": "[群友昵称]",
|
||||
|
||||
"count": "[数量]",
|
||||
|
||||
"characteristics": ["[特点1]", "[特点2]"],
|
||||
|
||||
"commonWords": ["[常用词1]", "[常用词2]"]
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"nightOwl": {
|
||||
|
||||
"name": "[熬夜冠军昵称]",
|
||||
|
||||
"title": "[熬夜冠军称号]",
|
||||
|
||||
"latestTime": "[时间]",
|
||||
|
||||
"messageCount": "[数量]",
|
||||
|
||||
"lastMessage": "[最后一条深夜消息内容]"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"wordCloud": {
|
||||
|
||||
"words": [
|
||||
|
||||
{
|
||||
|
||||
"text": "[关键词1]",
|
||||
|
||||
"size": 38,
|
||||
|
||||
"color": "#00b4d8",
|
||||
|
||||
"rotation": -15
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"legend": [
|
||||
|
||||
{"color": "#00b4d8", "label": "[分类1] 相关词汇"},
|
||||
|
||||
{"color": "#4361ee", "label": "[分类2] 相关词汇"}
|
||||
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"footer": {
|
||||
|
||||
"dataSource": "[群名称]聊天记录",
|
||||
|
||||
"generationTime": "[当前时间]",
|
||||
|
||||
"statisticalPeriod": "[日期] [时间范围]",
|
||||
|
||||
"disclaimer": "本报告内容基于群聊公开讨论,如有不当内容或侵权问题请联系管理员处理。"
|
||||
|
||||
}
|
||||
|
||||
}"""},
|
||||
{"role": "user", "content": """你好,以下是我要提取的内容: {{content}}"""},
|
||||
|
||||
]
|
||||
|
||||
|
||||
def process_msg(self,x):
|
||||
"""
|
||||
识别json格式,并返回字典
|
||||
"""
|
||||
pattern = re.compile('{.*}', flags=re.IGNORECASE | re.MULTILINE | re.S)
|
||||
# print(pattern.search(json_data).group())
|
||||
|
||||
json_data = json.loads(pattern.search(x).group())
|
||||
return json_data
|
||||
|
||||
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# deepseek_api = DeepSeekApi("sk-2ed4377a895d4ce18e086258c254fc8e")
|
||||
#
|
||||
# response = deepseek_api.send_msg(module=0,message="""""")
|
||||
# print(response)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
# Author: xaoyaoo
|
||||
# Date: 2024/04/20
|
||||
# -------------------------------------------------------------------------------
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
from pywxdump.db import DBHandler
|
||||
@ -39,5 +40,135 @@ def export_json(wxid, outpath, db_config, my_wxid="我", indent=4):
|
||||
return True, f"导出成功: {outpath}"
|
||||
|
||||
|
||||
def export_json_mini(wxid, outpath, db_config, my_wxid="我", indent=4):
|
||||
# 确保输出目录存在
|
||||
if not os.path.exists(outpath):
|
||||
outpath = os.path.join(os.getcwd(), "export_mini" + os.sep + wxid)
|
||||
os.makedirs(outpath, exist_ok=True)
|
||||
|
||||
db = DBHandler(db_config, my_wxid)
|
||||
|
||||
# 获取消息总数
|
||||
count = db.get_msgs_count(wxid)
|
||||
chatCount = count.get(wxid, 0)
|
||||
if chatCount == 0:
|
||||
return False, "没有聊天记录"
|
||||
|
||||
users = {}
|
||||
page_size = chatCount + 1 # 保持与原函数一致的分页逻辑
|
||||
|
||||
for i in range(0, chatCount, page_size):
|
||||
start_index = i
|
||||
data, users_t = db.get_msgs(wxid, start_index, page_size)
|
||||
users.update(users_t) # 合并用户信息
|
||||
|
||||
if not data:
|
||||
continue
|
||||
|
||||
# 构建简化数据
|
||||
mini_data = []
|
||||
for msg in data:
|
||||
# 获取昵称(优先用备注,没有则用昵称,最后用wxid)
|
||||
user_info = users.get(msg.get("talker"), {})
|
||||
nickname = user_info.get("remark") or user_info.get("nickname") or msg.get("talker")
|
||||
|
||||
mini_msg = {
|
||||
"nickname": nickname,
|
||||
"message": msg.get("msg", ""),
|
||||
"time": msg.get("CreateTime", "")
|
||||
}
|
||||
mini_data.append(mini_msg)
|
||||
|
||||
# 保存简化后的文件
|
||||
save_path = os.path.join(outpath, f"{wxid}_mini_{i}_{i + page_size}.json")
|
||||
with open(save_path, "w", encoding="utf-8") as f:
|
||||
json.dump(mini_data, f, ensure_ascii=False, indent=indent)
|
||||
|
||||
return True, f"简化版导出成功: {outpath}"
|
||||
|
||||
|
||||
def export_json_mini_time_limit(wxid, outpath, db_config, my_wxid="我",
|
||||
start_createtime=None, end_createtime=None, indent=4):
|
||||
"""
|
||||
带时间过滤的简化版聊天记录导出
|
||||
|
||||
:param start_createtime: 开始时间(格式:2025-4-30 16:55:01)
|
||||
:param end_createtime: 结束时间(格式:2025-4-30 16:55:01)
|
||||
"""
|
||||
# 创建输出目录
|
||||
if not os.path.exists(outpath):
|
||||
outpath = os.path.join(os.getcwd(), "export_mini" + os.sep + wxid)
|
||||
os.makedirs(outpath, exist_ok=True)
|
||||
|
||||
# 初始化数据库连接
|
||||
db = DBHandler(db_config, my_wxid)
|
||||
|
||||
# 时间格式转换
|
||||
def str_to_timestamp(time_str):
|
||||
if not time_str:
|
||||
return None
|
||||
try:
|
||||
dt = datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
|
||||
return int(dt.timestamp())
|
||||
except ValueError:
|
||||
raise ValueError(f"无效时间格式: {time_str},示例: 2025-04-30 16:55:01")
|
||||
|
||||
start_ts = str_to_timestamp(start_createtime)
|
||||
end_ts = str_to_timestamp(end_createtime)
|
||||
|
||||
# 获取消息数据(带时间过滤)
|
||||
all_data = []
|
||||
users = {}
|
||||
page_size = 5000 # 每次获取5000条
|
||||
start_index = 0
|
||||
|
||||
while True:
|
||||
# 获取分页数据(自动包含时间过滤条件)
|
||||
data, users_t = db.get_msgs(
|
||||
wxid,
|
||||
start_index=start_index,
|
||||
page_size=page_size,
|
||||
start_createtime=start_ts,
|
||||
end_createtime=end_ts
|
||||
)
|
||||
|
||||
if not data:
|
||||
break
|
||||
|
||||
all_data.extend(data)
|
||||
users.update(users_t)
|
||||
start_index += page_size
|
||||
|
||||
if not all_data:
|
||||
return False, "指定时间段内没有聊天记录"
|
||||
|
||||
# 构建简化数据结构
|
||||
mini_data = []
|
||||
for msg in all_data:
|
||||
talker = msg.get("talker")
|
||||
user_info = users.get(talker, {})
|
||||
|
||||
mini_msg = {
|
||||
"sender": user_info.get("remark") or user_info.get("nickname") or talker,
|
||||
"content": msg.get("msg", ""),
|
||||
"timestamp": msg.get("CreateTime")
|
||||
}
|
||||
mini_data.append(mini_msg)
|
||||
|
||||
# 生成带时间范围的文件名
|
||||
time_suffix = ""
|
||||
if start_createtime or end_createtime:
|
||||
start_part = start_createtime.replace(" ", "_").replace(":", "-") if start_createtime else "all"
|
||||
end_part = end_createtime.replace(" ", "_").replace(":", "-") if end_createtime else "now"
|
||||
time_suffix = f"_{start_part}_to_{end_part}"
|
||||
filename = f"{wxid}_mini{time_suffix}_ai.json"
|
||||
save_path = os.path.join(outpath, filename)
|
||||
with open(save_path, "w", encoding="utf-8") as f:
|
||||
json.dump(mini_data, f, ensure_ascii=False, indent=indent)
|
||||
|
||||
return True, f"导出成功: {save_path}", filename
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
|
@ -5,6 +5,8 @@
|
||||
# Author: xaoyaoo
|
||||
# Date: 2024/01/02
|
||||
# -------------------------------------------------------------------------------
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import shutil
|
||||
@ -12,6 +14,7 @@ from collections import Counter
|
||||
from urllib.parse import quote, unquote
|
||||
from typing import List, Optional
|
||||
|
||||
import fastapi.requests
|
||||
from pydantic import BaseModel
|
||||
from fastapi import APIRouter, Response, Body, Query, Request
|
||||
from starlette.responses import StreamingResponse, FileResponse
|
||||
@ -20,8 +23,10 @@ import pywxdump
|
||||
from pywxdump import decrypt_merge, get_core_db
|
||||
from pywxdump.db import DBHandler
|
||||
from pywxdump.db.utils import download_file, dat2img
|
||||
from .api_utils.html import HtmlController
|
||||
|
||||
from .export import export_csv, export_json, export_html
|
||||
from .export.exportJSON import export_json_mini, export_json_mini_time_limit
|
||||
from .rjson import ReJson, RqJson
|
||||
from .utils import error9999, gc, asyncError9999, rs_loger
|
||||
|
||||
@ -134,11 +139,17 @@ def get_msgs(wxid: str = Body(...), start: int = Body(...), limit: int = Body(..
|
||||
"""
|
||||
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = gc.get_conf(my_wxid, "db_config")
|
||||
|
||||
db = DBHandler(db_config, my_wxid=my_wxid)
|
||||
msgs, users = db.get_msgs(wxids=wxid, start_index=start, page_size=limit)
|
||||
|
||||
start_createtime = datetime.datetime.strptime("2025-04-28 00:54:33",
|
||||
"%Y-%m-%d %H:%M:%S").timestamp()
|
||||
end_createtime = datetime.datetime.now().timestamp()
|
||||
msgs, users = db.get_msgs(wxids=wxid, start_index=start, page_size=limit, ) #
|
||||
|
||||
return ReJson(0, {"msg_list": msgs, "user_list": users})
|
||||
|
||||
|
||||
@ -464,6 +475,51 @@ def get_export_json(wxid: str = Body(..., embed=True)):
|
||||
return ReJson(2001, body=ret)
|
||||
|
||||
|
||||
class ExportJsonMiniRequest(BaseModel):
|
||||
start_createtime: int
|
||||
end_createtime: int
|
||||
|
||||
|
||||
@rs_api.api_route('/export_json_mini_select_time', methods=["GET", 'POST'])
|
||||
def get_export_json(wxid: str = Body(..., embed=True), time: ExportJsonMiniRequest = Body(..., embed=True)):
|
||||
"""
|
||||
导出json,选择时间,迷你版本
|
||||
:return:
|
||||
"""
|
||||
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = gc.get_conf(my_wxid, "db_config")
|
||||
|
||||
if not wxid:
|
||||
return ReJson(1002, body=f"username is required: {wxid}")
|
||||
|
||||
outpath = os.path.join(gc.work_path, "export", my_wxid, "json", wxid)
|
||||
if not os.path.exists(outpath):
|
||||
os.makedirs(outpath)
|
||||
|
||||
start_createtime = time.start_createtime / 1000.0 # 格式为 时间戳
|
||||
end_createtime = time.end_createtime / 1000.0
|
||||
|
||||
|
||||
start_createtime = datetime.datetime.fromtimestamp(float(start_createtime)).strftime("%Y-%m-%d %H:%M:%S") #转换成日期格式
|
||||
end_createtime = datetime.datetime.fromtimestamp(float(end_createtime)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
code, ret, filename = export_json_mini_time_limit(wxid, outpath, db_config, my_wxid=my_wxid,
|
||||
start_createtime=start_createtime, end_createtime=end_createtime)
|
||||
if code:
|
||||
# 成功创建,执行生成可视化页面的逻辑
|
||||
# with open(os.path.join(gc.work_path, "export", my_wxid, "html", wxid, filename), "w", encoding="utf-8") as f:
|
||||
# f.write(
|
||||
# #现在是fake
|
||||
# HtmlController().create_html(json_data=None)
|
||||
# )
|
||||
return ReJson(0, ret)
|
||||
|
||||
else:
|
||||
return ReJson(2001, body=ret)
|
||||
|
||||
|
||||
class ExportHtmlRequest(BaseModel):
|
||||
wxid: str
|
||||
|
||||
@ -502,6 +558,251 @@ def get_export_html(wxid: str = Body(..., embed=True)):
|
||||
|
||||
# end 导出聊天记录 *******************************************************************************************************
|
||||
|
||||
|
||||
# AI可视化生成 **********************************************
|
||||
#TODO:查询当前登录用户文件夹下是否有导出数据,是否已经存在ui界面
|
||||
|
||||
def recursive_listdir(path,list:List):
|
||||
"""
|
||||
遍历文件夹获取所有文件 包括子目录
|
||||
"""
|
||||
|
||||
files = os.listdir(path)
|
||||
for file in files:
|
||||
file_path = os.path.join(path, file)
|
||||
if os.path.isdir(file_path):
|
||||
recursive_listdir(file_path,list)
|
||||
elif os.path.isfile(file_path):
|
||||
list.append(file_path)
|
||||
|
||||
|
||||
|
||||
def de_weight(l1:List,l2:List):
|
||||
"""
|
||||
列表去重,针对特定对象
|
||||
"""
|
||||
len1 = min(len(l1), len(l2))
|
||||
len1 = len1-1 if len1 > 1 else len1
|
||||
for i in range(len1):
|
||||
if l1[i]["wxid"] == l2[i]["wxid"] and l1[i]["start_time"] == l2[i]["start_time"] and l1[i]["end_time"] == l2[i][
|
||||
"end_time"]:
|
||||
l1[i]["flag"] = True
|
||||
l2.pop(i)
|
||||
|
||||
return l1+l2
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@rs_api.api_route('/ai_ui_json_list', methods=["GET", 'POST'])
|
||||
def get_ai_ui_json_list():
|
||||
"""
|
||||
获取可视化json文件列表
|
||||
"""
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
|
||||
|
||||
# 遍历json文件夹,查找最后带_ai的文件
|
||||
work_path = os.path.join(gc.work_path, "export", my_wxid, "json")
|
||||
if not os.path.exists(work_path):
|
||||
os.makedirs(work_path)
|
||||
file_list:List[str]=[]
|
||||
recursive_listdir(work_path,list=file_list)
|
||||
|
||||
# 解析文件名
|
||||
ui_list = []
|
||||
for file in file_list:
|
||||
if file.split('.')[0].split('_')[-1] == 'ai':
|
||||
# 可进行ai可视化的文件
|
||||
ui_list.append(file)
|
||||
# print(ui_list)
|
||||
|
||||
# 构造字典对象
|
||||
ui_dict_list = []
|
||||
for s in ui_list:
|
||||
wxid = s.split('\\')[-1].split('.')[0].split('_')[0] if "@" in s.split('\\')[-1] else \
|
||||
s.split('\\')[-1].split('.')[0].split('_')[1] # wxid
|
||||
time_start = " ".join(s.split('\\')[-1].split('.')[0].split('_')[2:4]) if "@" in s.split('\\')[
|
||||
-1] else " ".join(s.split('\\')[-1].split('.')[0].split('_')[3:5]) # time start
|
||||
time_end = " ".join(s.split('\\')[-1].split('.')[0].split('_')[5:7]) if "@" in s.split('\\')[-1] else " ".join(
|
||||
s.split('\\')[-1].split('.')[0].split('_')[6:8]) # time end
|
||||
ui_dict_list.append({"wxid": wxid, "start_time": time_start, "end_time": time_end, "flag": False})
|
||||
|
||||
|
||||
|
||||
# 遍历ai_json文件夹,获取所有文件名
|
||||
work_path = os.path.join(gc.work_path, "export", my_wxid, "ai_json")
|
||||
if not os.path.exists(work_path):
|
||||
os.makedirs(work_path)
|
||||
file_list:List[str]=[]
|
||||
recursive_listdir(work_path,list=file_list)
|
||||
|
||||
# 解析文件名
|
||||
ai_list = []
|
||||
for file in file_list:
|
||||
ai_list.append(file)
|
||||
|
||||
ai_dict_list = []
|
||||
|
||||
# 构造字典对象
|
||||
for s in ai_list:
|
||||
wxid = s.split('\\')[-1].split('.')[0].split('_')[0] if "@" in s.split('\\')[-1] else \
|
||||
s.split('\\')[-1].split('.')[0].split('_')[1] # wxid
|
||||
time_start = " ".join(s.split('\\')[-1].split('.')[0].split('_')[2:4]) if "@" in s.split('\\')[
|
||||
-1] else " ".join(s.split('\\')[-1].split('.')[0].split('_')[3:5]) # time start
|
||||
time_end = " ".join(s.split('\\')[-1].split('.')[0].split('_')[5:7]) if "@" in s.split('\\')[-1] else " ".join(
|
||||
s.split('\\')[-1].split('.')[0].split('_')[6:8]) # time end
|
||||
ai_dict_list.append({"wxid": wxid, "start_time": time_start, "end_time": time_end, "flag": True})
|
||||
|
||||
# # 合并两个字典列表
|
||||
# dict_list = ui_dict_list + ai_dict_list
|
||||
# print(ui_dict_list)
|
||||
# print(ai_dict_list)
|
||||
|
||||
# 去重
|
||||
dict_list = de_weight(ui_dict_list,ai_dict_list)
|
||||
|
||||
return ReJson(0,body={"items":dict_list})
|
||||
|
||||
|
||||
|
||||
|
||||
def get_file_path(work_path: str, file_name: str) -> str | None:
|
||||
"""
|
||||
获取ai_json文件路径
|
||||
"""
|
||||
# 遍历文件夹内的所有文件,找到对应文件名的文件路径
|
||||
|
||||
|
||||
path_list = os.listdir(work_path)
|
||||
for path in path_list:
|
||||
full_path = os.path.join(work_path, path)
|
||||
if os.path.isfile(full_path) and path == file_name:
|
||||
return full_path
|
||||
elif os.path.isdir(full_path):
|
||||
result = get_file_path(full_path, file_name)
|
||||
if result is not None:
|
||||
return result
|
||||
return None
|
||||
|
||||
class FileNameRequest(BaseModel):
|
||||
wxid: str
|
||||
start_time: str
|
||||
end_time: str
|
||||
|
||||
@rs_api.api_route('/db_to_ai_json', methods=["GET", 'POST'])
|
||||
def db_to_ai_json(file_name: FileNameRequest = Body(..., embed=True)):
|
||||
"""
|
||||
导出聊天记录到ai_json
|
||||
"""
|
||||
start_time = file_name.start_time
|
||||
end_time = file_name.end_time
|
||||
wxid = file_name.wxid
|
||||
|
||||
|
||||
file_name = wxid + '_mini_' + start_time.replace(' ', '_').replace(':', '-') + '_to_' + end_time.replace(' ', '_').replace(':', '-') + '_ai'
|
||||
# file_name = wxid + '_aiyes_' + start_time.replace(' ', '_').replace(':', '-') + '_' + end_time.replace(' ', '_').replace(':', '-')
|
||||
file_name = file_name + '.json'
|
||||
|
||||
|
||||
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
|
||||
|
||||
|
||||
result = get_file_path(os.path.join(gc.work_path, "export", my_wxid, "json"), file_name)
|
||||
|
||||
|
||||
if result is None:
|
||||
return ReJson(1002, body=f"file not found: {file_name}")
|
||||
|
||||
# 获取文件内容
|
||||
with open(result, "r", encoding="utf-8") as f:
|
||||
json_data = json.load(f)
|
||||
if not json_data:
|
||||
return ReJson(1002, body=f"json_data is empty: {file_name}")
|
||||
|
||||
#通过llm处理,生成ai_json
|
||||
from .api_utils.llm import DeepSeekApi
|
||||
# 获取apikey
|
||||
apikey = gc.get_conf(my_wxid, "deepseek_setting").get("API_KEY")
|
||||
if not apikey:
|
||||
return ReJson(1002, body="deepseek_setting.API_KEY is required")
|
||||
llm_api = DeepSeekApi(api_key=apikey)
|
||||
json_data = llm_api.send_msg(module=0,message=json.dumps(json_data))
|
||||
|
||||
# 保存到ai_json
|
||||
ai_json_path = os.path.join(gc.work_path, "export", my_wxid, "ai_json")
|
||||
if not os.path.exists(ai_json_path):
|
||||
os.makedirs(ai_json_path)
|
||||
|
||||
assert isinstance(ai_json_path, str)
|
||||
file_name = wxid + '_aiyes_' + start_time.replace(' ', '_').replace(':', '-') + '_to_' + end_time.replace(' ',
|
||||
'_').replace(
|
||||
':', '-')
|
||||
file_name = file_name + '.json'
|
||||
ai_json_file_path = os.path.join(ai_json_path, file_name)
|
||||
with open(ai_json_file_path, "w", encoding="utf-8") as f:
|
||||
json.dump(json_data, f, ensure_ascii=False)
|
||||
|
||||
return ReJson(0, body=f"save to {ai_json_file_path}")
|
||||
|
||||
|
||||
|
||||
class FileNameGetUiRequest(BaseModel):
|
||||
wxid: str
|
||||
start_time: str
|
||||
end_time: str
|
||||
|
||||
# 获取可视化界面json文件
|
||||
@rs_api.api_route('/get_ui_json', methods=["GET", 'POST'])
|
||||
def get_ui_json(file_name: FileNameGetUiRequest = Body(..., embed=True)):
|
||||
"""
|
||||
获取可视化界面json文件
|
||||
"""
|
||||
# print(file_name.wxid)
|
||||
|
||||
start_time = file_name.start_time
|
||||
end_time = file_name.end_time
|
||||
wxid = file_name.wxid if "@" in file_name.wxid else "wxid_" + file_name.wxid
|
||||
|
||||
|
||||
# start_time = datetime.datetime.fromtimestamp(float(start_time)).strftime("%Y-%m-%d %H:%M:%S") #转换成日期格式
|
||||
# end_time = datetime.datetime.fromtimestamp(float(end_time)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
file_name = wxid + '_aiyes_' + start_time.replace(' ', '_').replace(':', '-') + '_to_' + end_time.replace(' ', '_').replace(':', '-')
|
||||
file_name = file_name + '.json'
|
||||
|
||||
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
|
||||
result = get_file_path(os.path.join(gc.work_path, "export", my_wxid, "ai_json"), file_name)
|
||||
|
||||
if result is None:
|
||||
return ReJson(1002, body=f"file not found: {file_name}")
|
||||
|
||||
# 获取文件内容
|
||||
with open(result, "r", encoding="utf-8") as f:
|
||||
json_data = json.load(f)
|
||||
if not json_data:
|
||||
return ReJson(1002, body=f"json_data is empty: {file_name}")
|
||||
|
||||
return ReJson(0, body=json_data)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# AI可视化生成 *******************************************************************************************************
|
||||
|
||||
|
||||
|
||||
# start 聊天记录分析api **************************************************************************************************
|
||||
class DateCountRequest(BaseModel):
|
||||
wxid: str = ""
|
||||
@ -659,4 +960,62 @@ def get_readme():
|
||||
else:
|
||||
return ReJson(2001, body="status_code is not 200")
|
||||
|
||||
|
||||
class DifyApiModel(BaseModel):
|
||||
api_key: str
|
||||
base_url: str
|
||||
|
||||
|
||||
@rs_api.api_route('/dify_setting', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def dify_setting(request: Request = None, dify: DifyApiModel = Body(None, embed=True)):
|
||||
"""
|
||||
dify设置
|
||||
"""
|
||||
|
||||
if request.method == "GET":
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
gc.get_conf(my_wxid, "dify_setting")
|
||||
|
||||
return ReJson(0, body=gc.get_conf(my_wxid, "dify_setting"))
|
||||
|
||||
elif request.method == "POST":
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
if not dify.api_key and not dify.base_url:
|
||||
return ReJson(1002, body="dify_setting is required")
|
||||
|
||||
gc.set_conf(my_wxid, "dify_setting", {"API_KEY": dify.api_key, "BASE_URL": dify.base_url})
|
||||
return ReJson(0, body=gc.get_conf(my_wxid, "dify_setting"))
|
||||
return ReJson(2001, body="status_code is not 200")
|
||||
|
||||
|
||||
class DeepSeekApiModel(BaseModel):
|
||||
api_key: str
|
||||
|
||||
|
||||
@rs_api.api_route('/deepseek_setting', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def deepseek_setting(request: Request = None, deepseek: DeepSeekApiModel = Body(None, embed=True)):
|
||||
"""
|
||||
deepseek设置
|
||||
"""
|
||||
if request.method == "GET":
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
gc.get_conf(my_wxid, "deepseek_setting")
|
||||
|
||||
return ReJson(0, body=gc.get_conf(my_wxid, "deepseek_setting"))
|
||||
|
||||
elif request.method == "POST":
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
if not deepseek or not deepseek.api_key:
|
||||
return ReJson(1002, body="deepseek_setting is required")
|
||||
|
||||
gc.set_conf(my_wxid, "deepseek_setting", {"API_KEY": deepseek.api_key})
|
||||
return ReJson(0, body=gc.get_conf(my_wxid, "deepseek_setting"))
|
||||
return ReJson(2001, body="status_code is not 200")
|
||||
|
||||
# END 关于、帮助、设置 ***************************************************************************************************
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-#
|
||||
# -------------------------------------------------------------------------------
|
||||
# Name: utils.py
|
||||
# Name: api_utils.py
|
||||
# Description:
|
||||
# Author: xaoyaoo
|
||||
# Date: 2024/01/16
|
||||
|
@ -59,6 +59,8 @@ class DBHandler(MicroHandler, MediaHandler, OpenIMContactHandler, PublicMsgHandl
|
||||
"talker": talker, "room_name": StrTalker, "msg": msg, "src": src, "extra": {},
|
||||
"CreateTime": CreateTime, }
|
||||
"""
|
||||
|
||||
|
||||
msgs0, wxid_list0 = self.get_msg_list(wxids=wxids, start_index=start_index, page_size=page_size,
|
||||
msg_type=msg_type,
|
||||
msg_sub_type=msg_sub_type, start_createtime=start_createtime,
|
||||
|
@ -103,9 +103,16 @@ class MsgHandler(DatabaseBase):
|
||||
f"{sql_sub_type}"
|
||||
f"{sql_start_createtime}"
|
||||
f"{sql_end_createtime}"
|
||||
f"ORDER BY CreateTime ASC LIMIT ?,?"
|
||||
f"ORDER BY CreateTime ASC LIMIT ? OFFSET ?"
|
||||
)
|
||||
param = param + (start_index, page_size)
|
||||
|
||||
param = param + ( page_size,start_index)
|
||||
# # 测试
|
||||
# print(sql + "\n" + " ".join([str(i) for i in param]))
|
||||
# print(sql + "\n" + " ".join([str(i) for i in param]))
|
||||
# print(sql + "\n" + " ".join([str(i) for i in param]))
|
||||
|
||||
|
||||
result = self.execute(sql, param)
|
||||
if not result:
|
||||
return [], []
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-#
|
||||
# -------------------------------------------------------------------------------
|
||||
# Name: __init__.py.py
|
||||
# Description: db.utils
|
||||
# Description: db.api_utils
|
||||
# Author: xaoyaoo
|
||||
# Date: 2024/07/23
|
||||
# -------------------------------------------------------------------------------
|
||||
|
@ -1,11 +0,0 @@
|
||||
# -*- coding: utf-8 -*-#
|
||||
# -------------------------------------------------------------------------------
|
||||
# Name: __init__.py.py
|
||||
# Description:
|
||||
# Author: xaoyaoo
|
||||
# Date: 2023/12/03
|
||||
# -------------------------------------------------------------------------------
|
||||
# from .view_chat import app_show_chat, get_user_list, export
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-#
|
||||
# -------------------------------------------------------------------------------
|
||||
# Name: utils.py
|
||||
# Name: api_utils.py
|
||||
# Description:
|
||||
# Author: xaoyaoo
|
||||
# Date: 2023/12/25
|
||||
|
@ -1,5 +1,7 @@
|
||||
openai==1.77.0
|
||||
setuptools
|
||||
wheel
|
||||
pycryptodome
|
||||
pycryptodomex
|
||||
pywin32
|
||||
silk-python
|
||||
@ -15,4 +17,4 @@ pymem
|
||||
pydantic==2.7.0
|
||||
fastapi
|
||||
uvicorn
|
||||
python-dotenv
|
||||
python-dotenv
|
||||
|
5
temp.md
Normal file
5
temp.md
Normal file
@ -0,0 +1,5 @@
|
||||
- 导出json,可选时间
|
||||
- 单独导航栏 Ai可视化
|
||||
- 可以进行可视化的文件列表
|
||||
- 已经可视化的列表
|
||||
- 查看
|
864
test.html
Normal file
864
test.html
Normal file
@ -0,0 +1,864 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>恋爱报告报告 - 2025-04-29 至 2025-04-30</title>
|
||||
<style>
|
||||
/* 严格定义的CSS样式,确保风格一致性 */
|
||||
:root {
|
||||
--bg-primary: #0f0e17;
|
||||
--bg-secondary: #1a1925;
|
||||
--bg-tertiary: #252336;
|
||||
--text-primary: #fffffe;
|
||||
--text-secondary: #a7a9be;
|
||||
--accent-primary: #ff8906;
|
||||
--accent-secondary: #f25f4c;
|
||||
--accent-tertiary: #e53170;
|
||||
--accent-blue: #3da9fc;
|
||||
--accent-purple: #7209b7;
|
||||
--accent-cyan: #00b4d8;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'SF Pro Display', 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', sans-serif;
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
font-size: 16px;
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
padding: 30px 0;
|
||||
background-color: var(--bg-secondary);
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
color: var(--accent-primary);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 18px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.meta-info {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.meta-info span {
|
||||
background-color: var(--bg-tertiary);
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
section {
|
||||
background-color: var(--bg-secondary);
|
||||
margin-bottom: 30px;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-blue);
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid var(--accent-blue);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-primary);
|
||||
margin: 15px 0 10px 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-secondary);
|
||||
margin: 12px 0 8px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent-blue);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 卡片容器样式 */
|
||||
.topics-container, .tutorials-container, .messages-container,
|
||||
.dialogues-container, .qa-container, .participants-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.topic-card, .tutorial-card, .message-card,
|
||||
.dialogue-card, .qa-card, .participant-item, .night-owl-item {
|
||||
background-color: var(--bg-tertiary);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 话题卡片 */
|
||||
.topic-category {
|
||||
display: inline-block;
|
||||
background-color: var(--accent-blue);
|
||||
color: var(--text-primary);
|
||||
padding: 3px 10px;
|
||||
border-radius: 15px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.topic-keywords {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.keyword {
|
||||
background-color: rgba(61, 169, 252, 0.2);
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.topic-mentions {
|
||||
color: var(--accent-cyan);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 教程卡片 */
|
||||
.tutorial-type {
|
||||
display: inline-block;
|
||||
background-color: var(--accent-secondary);
|
||||
color: var(--text-primary);
|
||||
padding: 3px 10px;
|
||||
border-radius: 15px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tutorial-meta {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tutorial-category {
|
||||
margin-top: 10px;
|
||||
font-style: italic;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 消息卡片 */
|
||||
.message-meta {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.message-meta span {
|
||||
margin-right: 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.message-type {
|
||||
background-color: var(--accent-tertiary);
|
||||
color: var(--text-primary);
|
||||
padding: 3px 10px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.priority {
|
||||
padding: 3px 10px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.priority-high {
|
||||
background-color: var(--accent-secondary);
|
||||
}
|
||||
|
||||
.priority-medium {
|
||||
background-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.priority-low {
|
||||
background-color: var(--accent-blue);
|
||||
}
|
||||
|
||||
/* 对话卡片 */
|
||||
.dialogue-type {
|
||||
display: inline-block;
|
||||
background-color: var(--accent-purple);
|
||||
color: var(--text-primary);
|
||||
padding: 3px 10px;
|
||||
border-radius: 15px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dialogue-content {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.dialogue-highlight {
|
||||
font-style: italic;
|
||||
color: var(--accent-primary);
|
||||
margin: 10px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 问答卡片 */
|
||||
.question {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.question-meta, .answer-meta {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.question-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background-color: rgba(114, 9, 183, 0.2);
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.answer {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
padding: 15px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.accepted-badge {
|
||||
background-color: var(--accent-primary);
|
||||
color: var(--text-primary);
|
||||
padding: 3px 10px;
|
||||
border-radius: 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 热度图 */
|
||||
.heatmap-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.heat-topic {
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.heat-bar {
|
||||
height: 20px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
margin: 5px 0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.heat-fill {
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* 话唠榜 */
|
||||
.participant-rank {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--accent-primary);
|
||||
margin-right: 15px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.participant-name {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.participant-count {
|
||||
color: var(--accent-cyan);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.participant-characteristics, .participant-words {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.characteristic {
|
||||
background-color: rgba(242, 95, 76, 0.2);
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.word {
|
||||
background-color: rgba(229, 49, 112, 0.2);
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 熬夜冠军 */
|
||||
.night-owl-item {
|
||||
background: linear-gradient(135deg, #0f0e17 0%, #192064 100%);
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.owl-crown {
|
||||
font-size: 40px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.owl-name {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.owl-title {
|
||||
color: var(--accent-primary);
|
||||
font-style: italic;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.owl-time, .owl-messages {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.owl-note {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 10px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 词云 - 云朵样式 */
|
||||
.cloud-container {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.cloud-wordcloud {
|
||||
position: relative;
|
||||
width: 600px;
|
||||
height: 400px;
|
||||
margin: 0 auto;
|
||||
background-color: var(--bg-tertiary);
|
||||
border-radius: 50%;
|
||||
box-shadow:
|
||||
40px 40px 0 -5px var(--bg-tertiary),
|
||||
80px 10px 0 -10px var(--bg-tertiary),
|
||||
110px 35px 0 -5px var(--bg-tertiary),
|
||||
-40px 50px 0 -8px var(--bg-tertiary),
|
||||
-70px 20px 0 -10px var(--bg-tertiary);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.cloud-word {
|
||||
position: absolute;
|
||||
transform-origin: center;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.cloud-word:hover {
|
||||
transform: scale(1.1);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.cloud-legend {
|
||||
margin-top: 60px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* 底部 */
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
margin-top: 50px;
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
margin-top: 15px;
|
||||
font-style: italic;
|
||||
}
|
||||
/* 新增头像相关样式 */
|
||||
.user-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border: 2px solid var(--accent-primary);
|
||||
}
|
||||
|
||||
/* 头像悬停效果 */
|
||||
.user-avatar:hover {
|
||||
transform: scale(1.1) rotate(5deg);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* 头像tooltip */
|
||||
.avatar-tooltip {
|
||||
visibility: hidden;
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
text-align: center;
|
||||
padding: 5px 10px;
|
||||
border-radius: 6px;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
bottom: 125%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.user-avatar:hover .avatar-tooltip {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 热度用户专区 */
|
||||
.hot-users {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.hot-user-item {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 皇冠标识 */
|
||||
.hot-crown {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -5px;
|
||||
font-size: 24px;
|
||||
color: #ffd700;
|
||||
filter: drop-shadow(0 2px 2px rgba(0,0,0,0.3));
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>恋爱报告报告</h1>
|
||||
<p class="date">2025-04-29 至 2025-04-30</p>
|
||||
<div class="meta-info">
|
||||
<span>总消息数:500+</span>
|
||||
<span>活跃用户:2</span>
|
||||
<span>时间范围:00:05:04 至 22:20:26</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
|
||||
<!-- 1. 今日讨论热点 -->
|
||||
<section class="hot-topics">
|
||||
<h2>今日讨论热点</h2>
|
||||
<div class="topics-container">
|
||||
<!-- 在这里填充讨论热点内容,严格按照以下格式,保留3-5个话题 -->
|
||||
|
||||
<div class="topic-card">
|
||||
<h3>恋爱沟通</h3>
|
||||
<div class="topic-category">情感交流</div>
|
||||
<p class="topic-summary">双方围绕情感需求、沟通方式和相互理解展开多次对话,涉及'控制欲''陪伴需求''表达方式'等核心矛盾点。典型对话如'你就不能说话吗'-'不困了,跟我聊聊天',体现双方对沟通频率的认知差异。</p>
|
||||
<div class="topic-keywords">
|
||||
<span class="keyword">控制</span><span class="keyword">陪伴</span><span class="keyword">理解</span>
|
||||
</div>
|
||||
<div class="topic-mentions">提及次数:30+</div>
|
||||
</div>
|
||||
|
||||
<div class="topic-card">
|
||||
<h3>项目合作</h3>
|
||||
<div class="topic-category">学业协作</div>
|
||||
<p class="topic-summary">4月29日上午集中讨论服务设计项目开发事宜,涉及原型设计、比赛规划、老师对接等具体内容。昏沉沉的提出开发支持意愿,要哄宝宝开心则负责团队协调,双方就'高保真原型''互联网+大赛'等专业概念进行多次确认。</p>
|
||||
<div class="topic-keywords">
|
||||
<span class="keyword">原型</span><span class="keyword">开发</span><span class="keyword">比赛</span>
|
||||
</div>
|
||||
<div class="topic-mentions">提及次数:50+</div>
|
||||
</div>
|
||||
|
||||
<!-- 复制上述卡片结构添加更多话题 -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 2. 实用教程与资源分享 -->
|
||||
<section class="tutorials">
|
||||
<h2>实用教程与资源分享</h2>
|
||||
<div class="tutorials-container">
|
||||
<!-- 在这里填充教程和资源内容,严格按照以下格式 -->
|
||||
|
||||
<div class="tutorial-card">
|
||||
<div class="tutorial-type">RESOURCE</div>
|
||||
<h3>产品设计开发流程</h3>
|
||||
<div class="tutorial-meta">
|
||||
<span class="shared-by">分享者:昏沉沉的</span>
|
||||
<span class="share-time">时间:2025-04-29 09:42:26</span>
|
||||
</div>
|
||||
<p class="tutorial-summary">讨论软件产品开发各阶段要点,强调从调研到高保真原型的设计闭环</p>
|
||||
<div class="key-points">
|
||||
<h4>要点:</h4>
|
||||
<ul><li>功能闭环</li><li>UI完善</li><li>比赛适配</li></ul>
|
||||
</div>
|
||||
<div class="tutorial-link">
|
||||
<a href="" class="link valid">查看原文: 微信聊天</a>
|
||||
</div>
|
||||
<div class="tutorial-category">分类:产品设计</div>
|
||||
</div>
|
||||
|
||||
<!-- 复制上述卡片结构添加更多资源 -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. 重要消息汇总 -->
|
||||
<section class="important-messages">
|
||||
<h2>重要消息汇总</h2>
|
||||
<div class="messages-container">
|
||||
<!-- 在这里填充重要消息内容,严格按照以下格式 -->
|
||||
|
||||
<div class="message-card">
|
||||
<div class="message-meta">
|
||||
<span class="time">2025-04-29 00:29:04</span>
|
||||
<span class="sender">要哄宝宝开心,宝宝开心我就开心</span>
|
||||
<span class="message-type">OTHER</span>
|
||||
<span class="priority priority-高">优先级:高</span>
|
||||
</div>
|
||||
<p class="message-content">只要你做的不论是我,还是外人看来是爱我的</p>
|
||||
<div class="message-full-content">
|
||||
<p>完整情感需求表达</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="message-card">
|
||||
<div class="message-meta">
|
||||
<span class="time">2025-04-29 10:15:17</span>
|
||||
<span class="sender">昏沉沉的</span>
|
||||
<span class="message-type">EVENT</span>
|
||||
<span class="priority priority-中">优先级:中</span>
|
||||
</div>
|
||||
<p class="message-content">你说你男朋友做开发的 做出来原型的话可以让你男朋友看一下 可以开发</p>
|
||||
<div class="message-full-content">
|
||||
<p>项目合作具体提案</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 复制上述卡片结构添加更多消息 -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. 有趣对话或金句 -->
|
||||
<section class="interesting-dialogues">
|
||||
<h2>有趣对话或金句</h2>
|
||||
<div class="dialogues-container">
|
||||
<!-- 在这里填充对话内容,严格按照以下格式 -->
|
||||
|
||||
<div class="dialogue-card">
|
||||
<div class="dialogue-type">DIALOGUE</div>
|
||||
<div class="dialogue-content">
|
||||
|
||||
<div class="message">
|
||||
<div class="message-meta">
|
||||
<span class="speaker">要哄宝宝开心,宝宝开心我就开心</span>
|
||||
<span class="time">2025-04-29 00:07:44</span>
|
||||
</div>
|
||||
<p class="message-content">不困了,跟我聊聊天</p>
|
||||
</div>
|
||||
<div class="message">
|
||||
<div class="message-meta">
|
||||
<span class="speaker">昏沉沉的</span>
|
||||
<span class="time">2025-04-29 00:07:42</span>
|
||||
</div>
|
||||
<p class="message-content">不能跟你说话吗</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialogue-highlight">沟通需求 vs 陪伴需求</div>
|
||||
<div class="dialogue-topic">相关话题:情感表达差异</div>
|
||||
</div>
|
||||
|
||||
<!-- 复制上述卡片结构添加更多对话 -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. 问题与解答 -->
|
||||
<section class="questions-answers">
|
||||
<h2>问题与解答</h2>
|
||||
<div class="qa-container">
|
||||
<!-- 在这里填充问答内容,严格按照以下格式 -->
|
||||
|
||||
<div class="qa-card">
|
||||
<div class="question">
|
||||
<div class="question-meta">
|
||||
<span class="asker">要哄宝宝开心,宝宝开心我就开心</span>
|
||||
<span class="time">2025-04-29 10:33:25</span>
|
||||
</div>
|
||||
<p class="question-content">你开发要钱吗</p>
|
||||
<div class="question-tags">
|
||||
<span class="tag">项目</span><span class="tag">费用</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="answers">
|
||||
|
||||
<div class="answer">
|
||||
<div class="answer-meta">
|
||||
<span class="responder">昏沉沉的</span>
|
||||
<span class="time">2025-04-29 10:45:18</span>
|
||||
<span class='accepted-badge'>最佳回答</span>
|
||||
</div>
|
||||
<p class="answer-content">那不就完了</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 复制上述卡片结构添加更多问答 -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 6. 群内数据可视化 -->
|
||||
<section class="analytics">
|
||||
<h2>群内数据可视化</h2>
|
||||
|
||||
<!-- 话题热度 -->
|
||||
<h3>话题热度</h3>
|
||||
<div class="heatmap-container">
|
||||
<!-- 在这里填充话题热度数据,严格按照以下格式 -->
|
||||
|
||||
<!-- 复制上述结构添加更多热度项,每项使用不同颜色 -->
|
||||
|
||||
<div class="heat-item">
|
||||
<div class="heat-topic">情感交流</div>
|
||||
<div class="heat-percentage">45%%</div>
|
||||
<div class="heat-bar">
|
||||
<div class="heat-fill" style="width: 45%%; background-color: #3da9fc;"></div>
|
||||
</div>
|
||||
<div class="heat-count">200+条消息</div>
|
||||
</div>
|
||||
|
||||
<div class="heat-item">
|
||||
<div class="heat-topic">项目讨论</div>
|
||||
<div class="heat-percentage">30%%</div>
|
||||
<div class="heat-bar">
|
||||
<div class="heat-fill" style="width: 30%%; background-color: #f25f4c;"></div>
|
||||
</div>
|
||||
<div class="heat-count">150+条消息</div>
|
||||
</div>
|
||||
|
||||
<!-- 可用的颜色: #3da9fc, #f25f4c, #7209b7, #e53170, #00b4d8, #4cc9f0 -->
|
||||
</div>
|
||||
|
||||
<!-- 话唠榜 -->
|
||||
<!-- 在话唠榜添加头像 -->
|
||||
<section class="analytics">
|
||||
<h3>话唠榜</h3>
|
||||
<div class="participants-container">
|
||||
|
||||
<div class="participant-item">
|
||||
<div class="participant-rank">1</div>
|
||||
<div class="participant-info">
|
||||
<div class="participant-name">要哄宝宝开心,宝宝开心我就开心</div>
|
||||
<div class="participant-count">发言数:300+</div>
|
||||
<div class="participant-characteristics">
|
||||
<span class="characteristic">情感表达</span><span class="characteristic">细节追问</span>
|
||||
</div>
|
||||
<div class="participant-words">
|
||||
<span class="word">?</span><span class="word">OK</span><span class="word">行</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- 熬夜冠军 -->
|
||||
<h3>熬夜冠军</h3>
|
||||
<div class="night-owls-container">
|
||||
<!-- 在这里填充熬夜冠军数据,严格按照以下格式 -->
|
||||
|
||||
|
||||
<div class="night-owl-item">
|
||||
<div class="owl-crown" title="熬夜冠军">👑</div>
|
||||
<div class="owl-info">
|
||||
<div class="owl-name">昏沉沉的</div>
|
||||
<div class="owl-title">深夜程序员</div>
|
||||
<div class="owl-time">最晚活跃时间:01:42:38</div>
|
||||
<div class="owl-messages">深夜消息数:50+</div>
|
||||
<div class="owl-last-message">对不起</div>
|
||||
<div class="owl-note">注:熬夜时段定义为23:00-06:00,已考虑不同时区</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 7. 词云 -->
|
||||
<section class="word-cloud">
|
||||
<h2>热门词云</h2>
|
||||
<div class="cloud-container">
|
||||
<!-- 词云容器 - 现在是云朵样式 -->
|
||||
<div class="cloud-wordcloud" id="word-cloud">
|
||||
<!-- 为每个词创建一个span元素,使用绝对定位放置 -->
|
||||
<!-- 以下是一些示例,请根据实际内容生成40-60个词 -->
|
||||
|
||||
<span class="cloud-word" style="left: 300px; top: 120px;
|
||||
font-size: 38px; color: #00b4d8;
|
||||
transform: rotate(-15deg);">开发</span>
|
||||
|
||||
<span class="cloud-word" style="left: 300px; top: 120px;
|
||||
font-size: 32px; color: #00b4d8;
|
||||
transform: rotate(15deg);">睡觉</span>
|
||||
|
||||
<!-- 继续添加更多词 -->
|
||||
</div>
|
||||
|
||||
<div class="cloud-legend">
|
||||
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background-color: #00b4d8;"></span>
|
||||
<span class="legend-label">项目 相关词汇</span>
|
||||
</div>
|
||||
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background-color: #4361ee;"></span>
|
||||
<span class="legend-label">情感 相关词汇</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 8. 页面底部 -->
|
||||
<footer>
|
||||
<p>数据来源:恋爱私聊记录聊天记录</p>
|
||||
<p>生成时间:<span class="generation-time">2025-05-01 10:00:00</span></p>
|
||||
<p>统计周期:2025-04-29 至 2025-04-30 [时间范围]</p>
|
||||
<p class="disclaimer">免责声明:本报告内容基于群聊公开讨论,如有不当内容或侵权问题请联系管理员处理。</p>
|
||||
</footer>
|
||||
</body>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 获取所有词云元素
|
||||
const cloudWords = document.querySelectorAll('.cloud-word');
|
||||
const container = document.querySelector('.cloud-wordcloud');
|
||||
const containerWidth = container.offsetWidth;
|
||||
const containerHeight = container.offsetHeight;
|
||||
|
||||
// 为每个词云元素设置随机位置
|
||||
cloudWords.forEach(word => {
|
||||
const wordWidth = word.offsetWidth;
|
||||
const wordHeight = word.offsetHeight;
|
||||
|
||||
|
||||
// 计算随机位置,确保词云元素不会超出容器边界
|
||||
const randomLeft = Math.random() * (containerWidth - wordWidth);
|
||||
const randomTop = Math.random() * (containerHeight - wordHeight);
|
||||
|
||||
// 设置位置
|
||||
word.style.left = `${randomLeft}px`;
|
||||
word.style.top = `${randomTop}px`;
|
||||
|
||||
// 添加悬停效果
|
||||
word.addEventListener('mouseover', function() {
|
||||
this.style.transform = 'scale(1.1)';
|
||||
this.style.zIndex = '10';
|
||||
});
|
||||
|
||||
word.addEventListener('mouseout', function() {
|
||||
this.style.transform = 'scale(1)';
|
||||
this.style.zIndex = '1';
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</html>
|
||||
|
19
test2.py
Normal file
19
test2.py
Normal file
@ -0,0 +1,19 @@
|
||||
import os
|
||||
import site
|
||||
# s = r"E:\project\wx_db_ui\PyWxDump-master\pywxdump\wxdump_work\export\wxid_7l787uu0sm8e22\ai_json\48805389894@chatroom_aiyes_2025-04-30_00-00-00_2025-05-01_to_23-59-59.json"
|
||||
# wxid = s.split('\\')[-1].split('.')[0].split('_')[0] if "@" in s.split('\\')[-1] else \
|
||||
# s.split('\\')[-1].split('.')[0].split('_')[1] # wxid
|
||||
# time_start = " ".join(s.split('\\')[-1].split('.')[0].split('_')[2:4]) if "@" in s.split('\\')[
|
||||
# -1] else " ".join(s.split('\\')[-1].split('.')[0].split('_')[3:5]) # time start
|
||||
# time_end = " ".join(s.split('\\')[-1].split('.')[0].split('_')[5:7]) if "@" in s.split('\\')[-1] else " ".join(
|
||||
# s.split('\\')[-1].split('.')[0].split('_')[6:8]) # time end
|
||||
#
|
||||
# print(wxid)
|
||||
# print(time_start)
|
||||
# print(time_end)
|
||||
|
||||
print(site.getsitepackages())
|
||||
|
||||
|
||||
|
||||
|
@ -145,7 +145,8 @@ with open("dist/wxdump_version_info.txt", "w", encoding="utf-8") as f:
|
||||
# 获取安装包的路径
|
||||
package_path = site.getsitepackages()
|
||||
if package_path:
|
||||
package_path = package_path[1] # 假设取第一个安装包的路径
|
||||
# package_path = site.getsitepackages()[0] # 假设取第一个安装包的路径
|
||||
package_path = r"E:\project\wx_db_ui\PyWxDump-master"
|
||||
|
||||
current_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在路径
|
||||
require_path = os.path.join(os.path.dirname(current_path), "requirements.txt") # requirements.txt 路径
|
||||
@ -153,7 +154,7 @@ if package_path:
|
||||
hidden_imports = f.read().splitlines()
|
||||
hidden_imports = [i.replace('-', '_').split("=")[0].split("~")[0] for i in hidden_imports if
|
||||
i and i not in ["setuptools", "wheel"]] # 去掉setuptools、wheel
|
||||
hidden_imports += ["pywxdump", "pywxdump.db", "pywxdump.db.__init__.utils"]
|
||||
hidden_imports += ["win32com",'Crypto' ,"pywxdump", "pywxdump.db", "pywxdump.db.__init__.utils"]
|
||||
|
||||
# 获取 ui 文件夹下的所有文件 用于打包
|
||||
root_path = os.path.join(package_path, 'pywxdump')
|
||||
|
762
微信聊天记录可视化prompt.md
Normal file
762
微信聊天记录可视化prompt.md
Normal file
@ -0,0 +1,762 @@
|
||||
任务:根据 提供的微信群聊天记录(json格式)生成今日群/好友日报,输出为风格固定、一致的HTML页面,适合截图分享
|
||||
## 日报模式选择
|
||||
- 日报模式:[完整版/简化版] (默认为完整版)
|
||||
- 如果需要简化版,请在提交时注明"生成简化版"
|
||||
|
||||
## 简化版说明
|
||||
如选择"简化版",将只生成以下核心部分:
|
||||
- 今日讨论热点(最多3个)
|
||||
- 重要消息汇总
|
||||
- 话唠榜(仅前3名)
|
||||
- 简化版词云
|
||||
日报内容更精简,适合快速浏览和分享。
|
||||
|
||||
## 聊天记录格式
|
||||
``` json
|
||||
[
|
||||
{
|
||||
"nickname": "昏沉沉的", # 发消息人昵称
|
||||
"message": "XXX", # 消息内容
|
||||
"time": "2025-04-27 11:33:20" #发消息时间
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
如未能识别消息格式或未找到有效记录,将显示提示信息并尝试按最佳猜测处理。
|
||||
|
||||
## 输出要求
|
||||
必须使用以下固定的HTML模板和CSS样式,仅更新内容部分,确保每次生成的页面风格完全一致。使用严格定义的深色科技风格。
|
||||
|
||||
|
||||
|
||||
## HTML结构模板
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>[群名称]报告 - [日期]</title>
|
||||
<style>
|
||||
/* 严格定义的CSS样式,确保风格一致性 */
|
||||
:root {
|
||||
--bg-primary: #0f0e17;
|
||||
--bg-secondary: #1a1925;
|
||||
--bg-tertiary: #252336;
|
||||
--text-primary: #fffffe;
|
||||
--text-secondary: #a7a9be;
|
||||
--accent-primary: #ff8906;
|
||||
--accent-secondary: #f25f4c;
|
||||
--accent-tertiary: #e53170;
|
||||
--accent-blue: #3da9fc;
|
||||
--accent-purple: #7209b7;
|
||||
--accent-cyan: #00b4d8;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'SF Pro Display', 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', sans-serif;
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
font-size: 16px;
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
padding: 30px 0;
|
||||
background-color: var(--bg-secondary);
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
color: var(--accent-primary);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 18px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.meta-info {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.meta-info span {
|
||||
background-color: var(--bg-tertiary);
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
section {
|
||||
background-color: var(--bg-secondary);
|
||||
margin-bottom: 30px;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-blue);
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid var(--accent-blue);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-primary);
|
||||
margin: 15px 0 10px 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-secondary);
|
||||
margin: 12px 0 8px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent-blue);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 卡片容器样式 */
|
||||
.topics-container, .tutorials-container, .messages-container,
|
||||
.dialogues-container, .qa-container, .participants-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.topic-card, .tutorial-card, .message-card,
|
||||
.dialogue-card, .qa-card, .participant-item, .night-owl-item {
|
||||
background-color: var(--bg-tertiary);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 话题卡片 */
|
||||
.topic-category {
|
||||
display: inline-block;
|
||||
background-color: var(--accent-blue);
|
||||
color: var(--text-primary);
|
||||
padding: 3px 10px;
|
||||
border-radius: 15px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.topic-keywords {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.keyword {
|
||||
background-color: rgba(61, 169, 252, 0.2);
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.topic-mentions {
|
||||
color: var(--accent-cyan);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 教程卡片 */
|
||||
.tutorial-type {
|
||||
display: inline-block;
|
||||
background-color: var(--accent-secondary);
|
||||
color: var(--text-primary);
|
||||
padding: 3px 10px;
|
||||
border-radius: 15px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tutorial-meta {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tutorial-category {
|
||||
margin-top: 10px;
|
||||
font-style: italic;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 消息卡片 */
|
||||
.message-meta {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.message-meta span {
|
||||
margin-right: 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.message-type {
|
||||
background-color: var(--accent-tertiary);
|
||||
color: var(--text-primary);
|
||||
padding: 3px 10px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.priority {
|
||||
padding: 3px 10px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.priority-high {
|
||||
background-color: var(--accent-secondary);
|
||||
}
|
||||
|
||||
.priority-medium {
|
||||
background-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.priority-low {
|
||||
background-color: var(--accent-blue);
|
||||
}
|
||||
|
||||
/* 对话卡片 */
|
||||
.dialogue-type {
|
||||
display: inline-block;
|
||||
background-color: var(--accent-purple);
|
||||
color: var(--text-primary);
|
||||
padding: 3px 10px;
|
||||
border-radius: 15px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dialogue-content {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.dialogue-highlight {
|
||||
font-style: italic;
|
||||
color: var(--accent-primary);
|
||||
margin: 10px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 问答卡片 */
|
||||
.question {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.question-meta, .answer-meta {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.question-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background-color: rgba(114, 9, 183, 0.2);
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.answer {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
padding: 15px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.accepted-badge {
|
||||
background-color: var(--accent-primary);
|
||||
color: var(--text-primary);
|
||||
padding: 3px 10px;
|
||||
border-radius: 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 热度图 */
|
||||
.heatmap-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.heat-topic {
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.heat-bar {
|
||||
height: 20px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
margin: 5px 0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.heat-fill {
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* 话唠榜 */
|
||||
.participant-rank {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--accent-primary);
|
||||
margin-right: 15px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.participant-name {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.participant-count {
|
||||
color: var(--accent-cyan);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.participant-characteristics, .participant-words {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.characteristic {
|
||||
background-color: rgba(242, 95, 76, 0.2);
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.word {
|
||||
background-color: rgba(229, 49, 112, 0.2);
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 熬夜冠军 */
|
||||
.night-owl-item {
|
||||
background: linear-gradient(135deg, #0f0e17 0%, #192064 100%);
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.owl-crown {
|
||||
font-size: 40px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.owl-name {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.owl-title {
|
||||
color: var(--accent-primary);
|
||||
font-style: italic;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.owl-time, .owl-messages {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.owl-note {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 10px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 词云 - 云朵样式 */
|
||||
.cloud-container {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.cloud-wordcloud {
|
||||
position: relative;
|
||||
width: 600px;
|
||||
height: 400px;
|
||||
margin: 0 auto;
|
||||
background-color: var(--bg-tertiary);
|
||||
border-radius: 50%;
|
||||
box-shadow:
|
||||
40px 40px 0 -5px var(--bg-tertiary),
|
||||
80px 10px 0 -10px var(--bg-tertiary),
|
||||
110px 35px 0 -5px var(--bg-tertiary),
|
||||
-40px 50px 0 -8px var(--bg-tertiary),
|
||||
-70px 20px 0 -10px var(--bg-tertiary);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.cloud-word {
|
||||
position: absolute;
|
||||
transform-origin: center;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.cloud-word:hover {
|
||||
transform: scale(1.1);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.cloud-legend {
|
||||
margin-top: 60px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* 底部 */
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
margin-top: 50px;
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
margin-top: 15px;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>[群名称]报告</h1>
|
||||
<p class="date">[日期]</p>
|
||||
<div class="meta-info">
|
||||
<span>总消息数:[数量]</span>
|
||||
<span>活跃用户:[数量]</span>
|
||||
<span>时间范围:[时间范围]</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 1. 今日讨论热点 -->
|
||||
<section class="hot-topics">
|
||||
<h2>今日讨论热点</h2>
|
||||
<div class="topics-container">
|
||||
<!-- 在这里填充讨论热点内容,严格按照以下格式,保留3-5个话题 -->
|
||||
<div class="topic-card">
|
||||
<h3>[热点话题名称]</h3>
|
||||
<div class="topic-category">[话题分类]</div>
|
||||
<p class="topic-summary">[简要总结(50-100字)]</p>
|
||||
<div class="topic-keywords">
|
||||
<span class="keyword">[关键词1]</span>
|
||||
<span class="keyword">[关键词2]</span>
|
||||
<!-- 添加更多关键词 -->
|
||||
</div>
|
||||
<div class="topic-mentions">提及次数:[次数]</div>
|
||||
</div>
|
||||
<!-- 复制上述卡片结构添加更多话题 -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 2. 实用教程与资源分享 -->
|
||||
<section class="tutorials">
|
||||
<h2>实用教程与资源分享</h2>
|
||||
<div class="tutorials-container">
|
||||
<!-- 在这里填充教程和资源内容,严格按照以下格式 -->
|
||||
<div class="tutorial-card">
|
||||
<div class="tutorial-type">[TUTORIAL | NEWS | RESOURCE]</div>
|
||||
<h3>[分享的教程或资源标题]</h3>
|
||||
<div class="tutorial-meta">
|
||||
<span class="shared-by">分享者:[昵称]</span>
|
||||
<span class="share-time">时间:[时间]</span>
|
||||
</div>
|
||||
<p class="tutorial-summary">[内容简介]</p>
|
||||
<div class="key-points">
|
||||
<h4>要点:</h4>
|
||||
<ul>
|
||||
<li>[要点1]</li>
|
||||
<li>[要点2]</li>
|
||||
<!-- 添加更多要点 -->
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tutorial-link">
|
||||
<a href="[URL]" class="link valid">查看原文: [域名]</a>
|
||||
</div>
|
||||
<div class="tutorial-category">分类:[分类]</div>
|
||||
</div>
|
||||
<!-- 复制上述卡片结构添加更多资源 -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. 重要消息汇总 -->
|
||||
<section class="important-messages">
|
||||
<h2>重要消息汇总</h2>
|
||||
<div class="messages-container">
|
||||
<!-- 在这里填充重要消息内容,严格按照以下格式 -->
|
||||
<div class="message-card">
|
||||
<div class="message-meta">
|
||||
<span class="time">[消息时间]</span>
|
||||
<span class="sender">[发送者昵称]</span>
|
||||
<span class="message-type">[NOTICE | EVENT | ANNOUNCEMENT | OTHER]</span>
|
||||
<span class="priority priority-high">优先级:[高|中|低]</span>
|
||||
</div>
|
||||
<p class="message-content">[消息内容]</p>
|
||||
<div class="message-full-content">
|
||||
<p>[完整通知内容]</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 复制上述卡片结构添加更多消息 -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. 有趣对话或金句 -->
|
||||
<section class="interesting-dialogues">
|
||||
<h2>有趣对话或金句</h2>
|
||||
<div class="dialogues-container">
|
||||
<!-- 在这里填充对话内容,严格按照以下格式 -->
|
||||
<div class="dialogue-card">
|
||||
<div class="dialogue-type">[DIALOGUE | QUOTE]</div>
|
||||
<div class="dialogue-content">
|
||||
<div class="message">
|
||||
<div class="message-meta">
|
||||
<span class="speaker">[说话者昵称]</span>
|
||||
<span class="time">[发言时间]</span>
|
||||
</div>
|
||||
<p class="message-content">[消息内容]</p>
|
||||
</div>
|
||||
<div class="message">
|
||||
<div class="message-meta">
|
||||
<span class="speaker">[说话者昵称]</span>
|
||||
<span class="time">[发言时间]</span>
|
||||
</div>
|
||||
<p class="message-content">[消息内容]</p>
|
||||
</div>
|
||||
<!-- 添加更多对话消息 -->
|
||||
</div>
|
||||
<div class="dialogue-highlight">[对话中的金句或亮点]</div>
|
||||
<div class="dialogue-topic">相关话题:[某某话题]</div>
|
||||
</div>
|
||||
<!-- 复制上述卡片结构添加更多对话 -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. 问题与解答 -->
|
||||
<section class="questions-answers">
|
||||
<h2>问题与解答</h2>
|
||||
<div class="qa-container">
|
||||
<!-- 在这里填充问答内容,严格按照以下格式 -->
|
||||
<div class="qa-card">
|
||||
<div class="question">
|
||||
<div class="question-meta">
|
||||
<span class="asker">[提问者昵称]</span>
|
||||
<span class="time">[提问时间]</span>
|
||||
</div>
|
||||
<p class="question-content">[问题内容]</p>
|
||||
<div class="question-tags">
|
||||
<span class="tag">[相关标签1]</span>
|
||||
<span class="tag">[相关标签2]</span>
|
||||
<!-- 添加更多标签 -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="answers">
|
||||
<div class="answer">
|
||||
<div class="answer-meta">
|
||||
<span class="responder">[回答者昵称]</span>
|
||||
<span class="time">[回答时间]</span>
|
||||
<span class="accepted-badge">最佳回答</span>
|
||||
</div>
|
||||
<p class="answer-content">[回答内容]</p>
|
||||
</div>
|
||||
<!-- 添加更多回答 -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- 复制上述卡片结构添加更多问答 -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 6. 群内数据可视化 -->
|
||||
<section class="analytics">
|
||||
<h2>群内数据可视化</h2>
|
||||
|
||||
<!-- 话题热度 -->
|
||||
<h3>话题热度</h3>
|
||||
<div class="heatmap-container">
|
||||
<!-- 在这里填充话题热度数据,严格按照以下格式 -->
|
||||
<div class="heat-item">
|
||||
<div class="heat-topic">[话题名称]</div>
|
||||
<div class="heat-percentage">[百分比]%</div>
|
||||
<div class="heat-bar">
|
||||
<div class="heat-fill" style="width: [百分比]%; background-color: #3da9fc;"></div>
|
||||
</div>
|
||||
<div class="heat-count">[数量]条消息</div>
|
||||
</div>
|
||||
<!-- 复制上述结构添加更多热度项,每项使用不同颜色 -->
|
||||
<div class="heat-item">
|
||||
<div class="heat-topic">[话题名称]</div>
|
||||
<div class="heat-percentage">[百分比]%</div>
|
||||
<div class="heat-bar">
|
||||
<div class="heat-fill" style="width: [百分比]%; background-color: #f25f4c;"></div>
|
||||
</div>
|
||||
<div class="heat-count">[数量]条消息</div>
|
||||
</div>
|
||||
<!-- 可用的颜色: #3da9fc, #f25f4c, #7209b7, #e53170, #00b4d8, #4cc9f0 -->
|
||||
</div>
|
||||
|
||||
<!-- 话唠榜 -->
|
||||
<h3>话唠榜</h3>
|
||||
<div class="participants-container">
|
||||
<!-- 在这里填充话唠榜数据,严格按照以下格式 -->
|
||||
<div class="participant-item">
|
||||
<div class="participant-rank">1</div>
|
||||
<div class="participant-info">
|
||||
<div class="participant-name">[群友昵称]</div>
|
||||
<div class="participant-count">[数量]条消息</div>
|
||||
<div class="participant-characteristics">
|
||||
<span class="characteristic">[特点1]</span>
|
||||
<span class="characteristic">[特点2]</span>
|
||||
<!-- 添加更多特点 -->
|
||||
</div>
|
||||
<div class="participant-words">
|
||||
<span class="word">[常用词1]</span>
|
||||
<span class="word">[常用词2]</span>
|
||||
<!-- 添加更多常用词 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 复制上述结构添加更多参与者 -->
|
||||
</div>
|
||||
|
||||
<!-- 熬夜冠军 -->
|
||||
<h3>熬夜冠军</h3>
|
||||
<div class="night-owls-container">
|
||||
<!-- 在这里填充熬夜冠军数据,严格按照以下格式 -->
|
||||
<div class="night-owl-item">
|
||||
<div class="owl-crown" title="熬夜冠军">👑</div>
|
||||
<div class="owl-info">
|
||||
<div class="owl-name">[熬夜冠军昵称]</div>
|
||||
<div class="owl-title">[熬夜冠军称号]</div>
|
||||
<div class="owl-time">最晚活跃时间:[时间]</div>
|
||||
<div class="owl-messages">深夜消息数:[数量]</div>
|
||||
<div class="owl-last-message">[最后一条深夜消息内容]</div>
|
||||
<div class="owl-note">注:熬夜时段定义为23:00-06:00,已考虑不同时区</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 7. 词云 -->
|
||||
<section class="word-cloud">
|
||||
<h2>热门词云</h2>
|
||||
<div class="cloud-container">
|
||||
<!-- 词云容器 - 现在是云朵样式 -->
|
||||
<div class="cloud-wordcloud" id="word-cloud">
|
||||
<!-- 为每个词创建一个span元素,使用绝对定位放置 -->
|
||||
<!-- 以下是一些示例,请根据实际内容生成40-60个词 -->
|
||||
<span class="cloud-word" style="left: 300px; top: 120px; font-size: 38px; color: #00b4d8; transform: rotate(-15deg); font-weight: bold;">[关键词1]</span>
|
||||
|
||||
<span class="cloud-word" style="left: 180px; top: 150px; font-size: 32px; color: #4cc9f0; transform: rotate(5deg); font-weight: bold;">[关键词2]</span>
|
||||
|
||||
<span class="cloud-word" style="left: 400px; top: 180px; font-size: 28px; color: #f25f4c; transform: rotate(-5deg);">[关键词3]</span>
|
||||
|
||||
<span class="cloud-word" style="left: 250px; top: 220px; font-size: 24px; color: #ff8906; transform: rotate(10deg);">[关键词4]</span>
|
||||
|
||||
<span class="cloud-word" style="left: 350px; top: 90px; font-size: 22px; color: #e53170; transform: rotate(-10deg);">[关键词5]</span>
|
||||
|
||||
<!-- 继续添加更多词 -->
|
||||
</div>
|
||||
|
||||
<div class="cloud-legend">
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background-color: #00b4d8;"></span>
|
||||
<span class="legend-label">[分类1] 相关词汇</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background-color: #4361ee;"></span>
|
||||
<span class="legend-label">[分类2] 相关词汇</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background-color: #7209b7;"></span>
|
||||
<span class="legend-label">[分类3] 相关词汇</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 8. 页面底部 -->
|
||||
<footer>
|
||||
<p>数据来源:[群名称]聊天记录</p>
|
||||
<p>生成时间:<span class="generation-time">[当前时间]</span></p>
|
||||
<p>统计周期:[日期] [时间范围]</p>
|
||||
<p class="disclaimer">免责声明:本报告内容基于群聊公开讨论,如有不当内容或侵权问题请联系管理员处理。</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user