feat: Tree+dataurl workspace (RAG pattern); shortcut files
This commit is contained in:
parent
fa9fc76bef
commit
8908112a65
75
wwwroot/api/workspace_files.dspy
Normal file
75
wwwroot/api/workspace_files.dspy
Normal file
@ -0,0 +1,75 @@
|
||||
# workspace_files.dspy - 返回目录下文件列表(Bricks widget 格式)
|
||||
|
||||
import os
|
||||
|
||||
folder_id = (params_kw or {}).get('id', '').strip()
|
||||
|
||||
if not folder_id or folder_id == '__root__':
|
||||
return {"widgettype": "Text", "options": {"text": "请从左侧选择目录", "cfontsize": 0.9}}
|
||||
|
||||
if not os.path.isdir(folder_id):
|
||||
return {"widgettype": "Text", "options": {"text": "目录不可用"}}
|
||||
|
||||
# 文件列表
|
||||
files = []
|
||||
try:
|
||||
entries = sorted(os.listdir(folder_id))
|
||||
except Exception as e:
|
||||
return {"widgettype": "Text", "options": {"text": "读取错误: " + str(e)}}
|
||||
|
||||
for name in entries:
|
||||
full = os.path.join(folder_id, name)
|
||||
if name.startswith('.'):
|
||||
continue
|
||||
if os.path.isfile(full):
|
||||
size = os.path.getsize(full)
|
||||
ext = os.path.splitext(name)[1].lower()
|
||||
is_text = ext in ['.md','.py','.js','.html','.css','.json','.txt','.xml','.yaml','.yml','.toml','.cfg','.sh','.sql','.dspy','.ui']
|
||||
icon = "📄" if is_text else "🎬" if ext in ['.png','.jpg','.mp4'] else "📁"
|
||||
fpath_escaped = full.replace("\\", "\\\\").replace("'", "\\'")
|
||||
fname_escaped = name.replace("'", "\\'")
|
||||
row = {
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"padding": "6px 10px",
|
||||
"alignItems": "center",
|
||||
"gap": "8px",
|
||||
"borderRadius": "4px",
|
||||
"style": {"cursor": "pointer"},
|
||||
},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": icon, "cfontsize": 1.2}},
|
||||
{"widgettype": "Text", "options": {"text": name, "cfontsize": 0.9, "color": "#1e293b", "css": "filler"}},
|
||||
{"widgettype": "Text", "options": {"text": f"{size/1024:.1f}KB", "cfontsize": 0.75, "color": "#94a3b8"}},
|
||||
],
|
||||
"binds": [{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "script",
|
||||
"target": "self",
|
||||
"script": f"var pw=new bricks.PopupWindow({{title:'{fname_escaped}',cwidth:70,cheight:32,auto_open:true}});var wt=new bricks.Wterm({{css:'filler',padding:'0'}});pw.content_w.add_widget(wt);var sb=new bricks.Button({{label:'保存'}});pw.content_w.add_widget(sb);fetch('/pipeline-sdlc/api/workspace_file.dspy?action=read&path='+encodeURIComponent('{fpath_escaped}')).then(function(r){{return r.json()}}).then(function(d){{if(d.error){{wt.write('Error: '+d.error);return}};wt.write(d.content||'');sb.bind('click',function(){{var c=wt.get_value();fetch('/pipeline-sdlc/api/workspace_file.dspy',{{method:'POST',headers:{{'Content-Type':'application/x-www-form-urlencoded'}},body:'action=save&path='+encodeURIComponent('{fpath_escaped}')+'&content='+encodeURIComponent(c)}}).then(function(r){{return r.json()}}).then(function(d){{if(d.success){{sb.set_text('已保存');setTimeout(function(){{sb.set_text('保存')}},2000)}}}})}});}})"
|
||||
}] if is_text else []
|
||||
} if is_text else {
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"padding": "6px 10px",
|
||||
"alignItems": "center",
|
||||
"gap": "8px",
|
||||
"borderRadius": "4px",
|
||||
},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": icon, "cfontsize": 1.2}},
|
||||
{"widgettype": "Text", "options": {"text": name, "cfontsize": 0.9, "color": "#94a3b8", "css": "filler"}},
|
||||
{"widgettype": "Text", "options": {"text": f"{size/1024:.1f}KB", "cfontsize": 0.75, "color": "#94a3b8"}},
|
||||
],
|
||||
}
|
||||
files.append(row)
|
||||
|
||||
if not files:
|
||||
return {"widgettype": "Text", "options": {"text": "(空目录)", "cfontsize": 0.85, "color": "#94a3b8"}}
|
||||
|
||||
return {
|
||||
"widgettype": "VScrollPanel",
|
||||
"options": {"css": "filler", "padding": "8px", "gap": "4px"},
|
||||
"subwidgets": files
|
||||
}
|
||||
49
wwwroot/api/workspace_tree.dspy
Normal file
49
wwwroot/api/workspace_tree.dspy
Normal file
@ -0,0 +1,49 @@
|
||||
# workspace_tree.dspy - 返回 Tree widget 需要的 {id, parentid, label} 格式
|
||||
|
||||
uid = await get_user()
|
||||
if not uid:
|
||||
uid = 'user-01'
|
||||
|
||||
dbname = get_module_dbname('pipeline-sdlc')
|
||||
workspace_base = '/d/pipeline/workspaces'
|
||||
import os
|
||||
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT current_project_id FROM pipeline_agent_settings WHERE user_id=${u}$",
|
||||
{"u": uid})
|
||||
pid = getattr(recs[0], 'current_project_id', '') if recs else ''
|
||||
|
||||
ws_dir = ''
|
||||
if pid:
|
||||
proj = await sor.sqlExe(
|
||||
"SELECT name, org_id, workspace_dir FROM sd_projects WHERE id=${p}$",
|
||||
{"p": pid})
|
||||
if proj:
|
||||
pname = getattr(proj[0], 'name', '')
|
||||
org_id = getattr(proj[0], 'org_id', '0') or '0'
|
||||
ws = getattr(proj[0], 'workspace_dir', '') or ''
|
||||
ws_dir = ws if ws.startswith('/') else workspace_base + '/' + org_id + '/' + pname
|
||||
|
||||
if not ws_dir or not os.path.isdir(ws_dir):
|
||||
return [{"id": "__root__", "parentid": "", "label": "📁 工作空间(不可用)"}]
|
||||
|
||||
# 递归扫描目录,生成 {id, parentid, label}
|
||||
items = [{"id": "__root__", "parentid": "", "label": "📁 " + os.path.basename(ws_dir)}]
|
||||
|
||||
def scan_dir(path, parent_id):
|
||||
try:
|
||||
entries = sorted(os.listdir(path))
|
||||
except Exception:
|
||||
return
|
||||
for name in entries:
|
||||
full = os.path.join(path, name)
|
||||
if name.startswith('.') or name == '__pycache__':
|
||||
continue
|
||||
if os.path.isdir(full):
|
||||
dir_id = full # 用全路径做 id
|
||||
items.append({"id": dir_id, "parentid": parent_id, "label": "📁 " + name})
|
||||
scan_dir(full, dir_id)
|
||||
|
||||
scan_dir(ws_dir, "__root__")
|
||||
return items
|
||||
@ -115,7 +115,7 @@
|
||||
"event": "click",
|
||||
"actiontype": "script",
|
||||
"target": "self",
|
||||
"script": "fetch('/pipeline-sdlc/api/cockpit_context.dspy').then(function(r){return r.json()}).then(function(d){var pid=d.project_id||'';var pn=d.project_name||'';if(!pid){var pw=new bricks.PopupWindow({title:'提示',cwidth:30,cheight:8,auto_open:true});pw.content_w.add_widget(new bricks.Text({text:'请先选择项目',cfontsize:1,color:'#64748b',padding:'20px'}));return;}var pw=new bricks.PopupWindow({title:pn+' - 工作空间',cwidth:80,cheight:36,auto_open:true});var hsplit=new bricks.Splitter({direction:'horizontal',split:'25%'});var left=new bricks.VScrollPanel({css:'filler',padding:'4px'});var right=new bricks.VScrollPanel({css:'filler',padding:'8px',gap:'4px'});var btnBar=new bricks.HBox({padding:'4px 8px',gap:'8px',cheight:2});var refreshBtn=new bricks.Button({label:'刷新',css:'small'});btnBar.add_widget(refreshBtn);var pathLabel=new bricks.Text({text:'',cfontsize:0.8,color:'#94a3b8'});btnBar.add_widget(pathLabel);var rightBox=new bricks.VBox({css:'filler',gap:'0'});rightBox.add_widget(btnBar);rightBox.add_widget(right);hsplit.add_widget(left);hsplit.add_widget(rightBox);pw.content_w.add_widget(hsplit);function loadTree(){fetch('/pipeline-sdlc/api/workspace_browse.dspy').then(function(r){return r.json()}).then(function(data){if(data.error){right.add_widget(new bricks.Text({text:data.error,color:'#f44336'}));return;}left.clear();function renderDir(dir,parent,level){var indent=level*16;var row=new bricks.HBox({padding:'3px 6px 3px '+(4+indent)+'px',alignItems:'center',gap:'4px',cursor:'pointer'});row.add_widget(new bricks.Text({text:'📁',cfontsize:1}));row.add_widget(new bricks.Text({text:dir.name,cfontsize:0.85}));row.dom_element.addEventListener('click',function(){pathLabel.set_text(dir.path);showFiles(dir);});(parent||left).add_widget(row);var children=dir.children||[];for(var i=0;i<children.length;i++) renderDir(children[i],parent||left,level+1);}renderDir(data.tree,null,0);showFiles(data.tree);refreshBtn.bind('click',function(){left.clear();loadTree();});});}function showFiles(node){right.clear();var files=node.files||[];if(files.length===0){right.add_widget(new bricks.Text({text:'(空目录)',cfontsize:0.85,color:'#94a3b8'}));return;}for(var i=0;i<files.length;i++){var f=files[i];var row=new bricks.HBox({bgcolor:i%2?'#f8fafc':'#fff',padding:'6px 10px',alignItems:'center',gap:'8px',borderRadius:'4px'});var icon=f.is_text?'📄':f.is_media?'🎬':'📁';row.add_widget(new bricks.Text({text:icon,cfontsize:1.2}));var nameW=new bricks.Text({text:f.name,cfontsize:0.9,color:'#1e293b',css:'filler'});row.add_widget(nameW);var sizeW=new bricks.Text({text:(f.size/1024).toFixed(1)+'KB',cfontsize:0.75,color:'#94a3b8'});row.add_widget(sizeW);(function(fp,fn,ft){row.dom_element.style.cursor='pointer';row.dom_element.addEventListener('click',function(){if(ft){openFile(fp,fn);}});})(f.path,f.name,f.is_text);right.add_widget(row);}}function openFile(path,name){var fpw=new bricks.PopupWindow({title:name,cwidth:70,cheight:32,auto_open:true});var wterm=new bricks.Wterm({css:'filler',padding:'0'});var saveBtn=new bricks.Button({label:'保存'});fpw.content_w.add_widget(wterm);fpw.content_w.add_widget(saveBtn);fetch('/pipeline-sdlc/api/workspace_file.dspy?action=read&path='+encodeURIComponent(path)).then(function(r){return r.json()}).then(function(d){if(d.error){wterm.write('Error: '+d.error);return;}wterm.write(d.content||'');saveBtn.bind('click',function(){var content=wterm.get_value();fetch('/pipeline-sdlc/api/workspace_file.dspy',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'action=save&path='+encodeURIComponent(path)+'&content='+encodeURIComponent(content)}).then(function(r){return r.json()}).then(function(d){if(d.success){saveBtn.set_text('已保存');setTimeout(function(){saveBtn.set_text('保存');},2000);}});});});}loadTree();});"
|
||||
"script": "fetch('/pipeline-sdlc/api/cockpit_context.dspy').then(function(r){return r.json()}).then(function(d){var pid=d.project_id||'';var pn=d.project_name||'';if(!pid){var pw=new bricks.PopupWindow({title:'提示',cwidth:30,cheight:8,auto_open:true});pw.content_w.add_widget(new bricks.Text({text:'请先选择项目',cfontsize:1,color:'#64748b',padding:'20px'}));return;}var pw=new bricks.PopupWindow({title:pn+' - 工作空间',cwidth:80,cheight:36,auto_open:true});var tv={'widgettype':'Tree','id':'ws_tree','options':{'dataurl':'/pipeline-sdlc/api/workspace_tree.dspy','css':'filler','padding':'4px','cheight':'100%'},'binds':[{'wid':'self','event':'selected','actiontype':'urlwidget','target':'ws_files','options':{'url':'/pipeline-sdlc/api/workspace_files.dspy'}}]};var fv={'widgettype':'VScrollPanel','id':'ws_files','options':{'css':'filler','padding':'8px','gap':'4px'}};var hsplit={'widgettype':'Splitter','options':{'direction':'horizontal','split':'30%'},'subwidgets':[tv,fv]};bricks.widgetBuild(hsplit,pw.content_w).then(function(w){if(w)pw.content_w.add_widget(w)});});"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user