Compare commits

...

121 Commits

Author SHA1 Message Date
mannaandpoem 3a1065f8cc Merge pull request #996 from Feige-cn/front-end
Pre-commit checks / pre-commit-check (push) Failing after 1s
Add multilingual switchingFront end
2025-03-31 21:29:50 +08:00
Feige-cn f9b36d8406 Merge branch 'front-end' of https://github.com/Feige-cn/OpenManus into front-end 2025-03-31 21:21:31 +08:00
Feige-cn d75a59aeb8 Add multilingual switching 2025-03-31 21:21:15 +08:00
Sheng Fan 456868c01c Merge pull request #974 from Feige-cn/front-end
Set configuration button for Front end
2025-03-29 13:39:37 +08:00
Feige-cn 594b38f905 Set configuration button 2025-03-29 13:28:46 +08:00
Feige-cn 7bd742eafc Merge branch 'front-end' of https://github.com/Feige-cn/OpenManus into front-end 2025-03-29 13:25:26 +08:00
Feige-cn 5ea799d939 Set configuration button 2025-03-29 13:24:56 +08:00
mannaandpoem 6160a5d05b Merge pull request #782 from Feige-cn/front-end
Front endAdd page LLM configuration settings
2025-03-19 16:22:30 +08:00
Feige-cn b63a0301dc Add page LLM configuration settings 2025-03-18 01:00:01 +08:00
Feige-cn d0a62e65c7 Add page LLM configuration settings 2025-03-18 00:57:52 +08:00
Feige-cn 6243591e7f Add page LLM configuration settings 2025-03-18 00:57:02 +08:00
Feige-cn 8439fdc7ab Add page LLM configuration settings 2025-03-18 00:54:47 +08:00
Isaac 38e34219d3 Merge pull request #565 from Feige-cn/front-end
fix download bug
2025-03-13 16:34:57 +08:00
Feige-cn 693f6a90ad fix download bug 2025-03-13 11:06:49 +08:00
xiangjinyu d847b550d6 add max_steps 2025-03-12 19:22:32 +08:00
xiangjinyu 5bba31db3e update terminate prompt 2025-03-12 19:21:13 +08:00
xiangjinyu 548c402316 add max_observe in toolcall and manus 2025-03-12 17:23:08 +08:00
xiangjinyu c96c016feb update browser-use window size 2025-03-12 17:21:06 +08:00
Isaac 728fdaffd6 Merge pull request #520 from Feige-cn/front-end
Optimize front-end display
2025-03-12 15:53:22 +08:00
Feige-cn fa2b05b658 Optimize front-end display 2025-03-12 15:45:02 +08:00
Isaac 7b96e12858 Merge pull request #456 from Feige-cn/front-end
Synchronize built-in environment
2025-03-11 13:58:00 +08:00
Feige-cn 62f937c1ca Synchronize built-in environment 2025-03-11 13:48:00 +08:00
liangxinbing 6e8de4e35e update README 2025-03-11 10:56:57 +08:00
mannaandpoem 8280b68462 Merge pull request #432 from Feige-cn/main
modified by hook
2025-03-11 10:55:03 +08:00
Feige-cn 8018124af2 modified by hook 2025-03-11 08:39:16 +08:00
liangxinbing 229d6706a4 format code 2025-03-11 07:31:18 +08:00
rxdaozhang d9418c1668 last try 2025-03-11 02:36:50 +08:00
rxdaozhang c37c6c19b5 Revert "fix pre commit hook bug"
This reverts commit 41308f3aca.
2025-03-11 02:28:00 +08:00
rxdaozhang a256d5589a Revert "try fix pre commit hook bug in app.py"
This reverts commit 2fcad5f097.
2025-03-11 02:27:24 +08:00
rxdaozhang 2fcad5f097 try fix pre commit hook bug in app.py 2025-03-11 02:20:50 +08:00
rxdaozhang 41308f3aca fix pre commit hook bug 2025-03-11 02:18:40 +08:00
rxdaozhang 77df9772fe Merge branch 'refactor-planning' of github.com:rxdaozhang/OpenManus into refactor-planning 2025-03-11 02:15:01 +08:00
rxdaozhang 69325c701b fix multiline string SyntaxError another 2025-03-11 02:14:15 +08:00
rxdaozhang f67af3580a fix multiline string SyntaxError 2025-03-11 02:07:04 +08:00
rxdaozhang 34ede1c082 重构:使用PlanStepStatus枚举替代硬编码status 2025-03-11 01:23:16 +08:00
liangxinbing b3c4f5ce22 add Official Website 2025-03-10 23:43:44 +08:00
liangxinbing bfa5a672c2 add Official Website 2025-03-10 23:36:14 +08:00
Isaac 576090439b Merge pull request #398 from Ailerx/main
add fastapi version  in requirements.txt
2025-03-10 20:57:32 +08:00
xiangjinyu 479b47a7d6 update cite in README.md 2025-03-10 20:49:29 +08:00
xiangjinyu 9fc1ea3ee4 update cite in README.md 2025-03-10 20:48:42 +08:00
xiangjinyu 444fc91230 update cite in README.md 2025-03-10 20:46:56 +08:00
liangxinbing b23ab9a42a update Roadmap and format code 2025-03-10 20:13:06 +08:00
Ailerx e38e35d2ab add fastapi version in requirements.txt 2025-03-10 17:26:53 +08:00
xiangjinyu 110213a29f modify main.js to English 2025-03-10 16:24:07 +08:00
xiangjinyu 81b9bb3660 modify app.py to English 2025-03-10 16:13:24 +08:00
xiangjinyu f2ddc63a6f modify index.html to English 2025-03-10 16:03:55 +08:00
Isaac d661578044 Merge pull request #377 from Feige-cn/main
add manus web-ui
2025-03-10 15:45:06 +08:00
Zhaoyang Yu 8b955c7336 Update ReadMe 2025-03-10 14:55:48 +08:00
Zhaoyang Yu 1a3ae5d06b Fix H1 2025-03-10 14:39:21 +08:00
Zhaoyang Yu c3322ee72e Fix Discord link 2025-03-10 14:36:07 +08:00
Zhaoyang Yu a8ee4d1617 Fix discord link 2025-03-10 14:32:25 +08:00
Zhaoyang Yu d19e7d5acd Update official discord 2025-03-10 14:22:00 +08:00
Zhaoyang Yu 186ce2d1c5 Update 2025-03-10 14:13:12 +08:00
Zhaoyang Yu 8ae3a6c77b udpate 2025-03-10 13:45:18 +08:00
飞戈 825855d13a Delete config/config.toml 2025-03-10 12:20:21 +08:00
Feige-cn ecdbb3b26a OpenManus简易web界面 2025-03-10 12:17:07 +08:00
xyuzh c10600d36e update readme 2025-03-09 21:17:06 -07:00
xyuzh 60bb2ce00f update readme 2025-03-09 14:01:57 -07:00
xyuzh 7501b74030 update readme 2025-03-09 13:53:22 -07:00
liangxinbing 0345aa0e95 update Roadmap 2025-03-09 23:58:29 +08:00
liangxinbing 5703502fab update README 2025-03-09 22:28:29 +08:00
liangxinbing 915aed17d1 update logo and format code 2025-03-09 22:27:20 +08:00
liangxinbing 38bb658031 update logo 2025-03-09 21:59:17 +08:00
liangxinbing 2fe2f75c21 add author 2025-03-09 21:57:52 +08:00
xyuzh 1dfc2c41ec [test] add pytest framework prototype 2025-03-09 00:08:49 -08:00
xyuzh 06e2239b70 update readme 2025-03-09 00:06:30 -08:00
liangxinbing b7c11a5db0 add CODE_OF_CONDUCT.md 2025-03-09 14:49:37 +08:00
mannaandpoem abb2516bc3 Merge pull request #318 from K-tang-mkv/tx_dev
feat: improve input handling
2025-03-09 13:40:24 +08:00
gantnocap 52b67090a0 docs: improve installation guide, add uv for fast installation 2025-03-09 13:36:51 +08:00
gantnocap 85bd75d236 feat: improve input handling
- Add support for 'quit' command alongside 'exit'

- Update input prompt to show both exit options

- Enhance empty input handling to catch both empty strings and whitespace

- Replace isspace() check with more robust empty string validation

- Optimize command checking by storing lowercase result

- Improve code readability and user experience
2025-03-09 13:11:44 +08:00
xyuzh e6ed98e53c [fix] fix overlong steps of thinking and avoid redundant steps after task completion && [add timer] set max time 1h to run the agent 2025-03-08 19:46:51 -08:00
mannaandpoem 7a9e22d093 Merge pull request #265 from fred913/main
feat(browser_use_tool): add 'get_text' action to browser use tool
2025-03-09 11:40:20 +08:00
mannaandpoem c71ee143e2 Merge pull request #312 from Kunlun-Zhu/main
Update README.md with OpenManus-RL information
2025-03-09 11:39:26 +08:00
Kunlun Zhu 0027af217c Update README_zh.md with OpenManus-RL information 2025-03-08 21:37:33 -06:00
Sheng Fan 0d0f8ab233 chore(browser_use_tool): fix code style according to pre-commit 2025-03-09 11:30:41 +08:00
Kunlun Zhu cd86cda61e Update README.md with OpenManus-RL information 2025-03-08 21:29:59 -06:00
Zhaoyang Yu 5694a5e89c update 2025-03-08 21:40:09 +08:00
Zhaoyang Yu 6164ac7bb8 Update Roadmap 2025-03-08 20:30:17 +08:00
Sheng Fan 7090490f75 feat(browser_use_tool): add 'read_links' action 2025-03-08 18:23:40 +08:00
XBsleepy 7bcc104aff Merge pull request #272 from XBsleepy/add-test
Update example preview
2025-03-08 18:05:13 +08:00
XBsleepy e47d2eeb02 merge updated example 2025-03-08 18:01:08 +08:00
XBsleepy ee3938dae5 update preview directory 2025-03-08 17:54:11 +08:00
XBsleepy 3024753e35 update japan travel plan example 2025-03-08 17:52:44 +08:00
Sheng Fan cf3603dfcc Merge branch 'mannaandpoem:main' into main 2025-03-08 17:47:30 +08:00
liangxinbing f04beec486 format examples 2025-03-08 17:20:29 +08:00
liangxinbing 56d138220a fix typo in README;
log full arguments on parse json err;
remove empty prompt for main.py and run_flow.py;
2025-03-08 17:17:40 +08:00
mannaandpoem 41063bcd2b Merge pull request #185 from XBsleepy/main
add the first example
2025-03-08 17:12:05 +08:00
Sheng Fan 6b77a99448 feat(browser_use_tool): add 'get_text' action to browser use tool 2025-03-08 16:45:18 +08:00
mannaandpoem e76055b436 Merge pull request #229 from appleboy/docs
docs: enhance README with multilingual support and community engagement
2025-03-08 16:32:12 +08:00
liangxinbing 6716a47823 remove unittest.yaml 2025-03-08 16:02:53 +08:00
liangxinbing dc28e9187b adjust code format 2025-03-08 15:54:23 +08:00
liangxinbing 35e3b5d94b add .github 2025-03-08 15:51:28 +08:00
appleboy a4340aa3b9 docs: enhance README with multilingual support and community engagement
- Update language links in the README to include Traditional and Simplified Chinese options
- Remove outdated HTML paragraph tags from the README
- Add a new README file in Traditional Chinese
- Include installation instructions and configuration steps in the new README
- Add a video demonstration section in the new README
- Enhance the roadmap section with additional planning items in both README files
- Encourage community engagement and contributions in both README files

Signed-off-by: appleboy <appleboy.tw@gmail.com>
2025-03-08 13:07:50 +08:00
Zhaoyang Yu 0abdd6f9da Update Feishu group 2025-03-08 11:49:00 +08:00
XBsleepy e70332bfa2 add examples dir and the first example 2025-03-08 00:01:29 +08:00
XBsleepy d921fc4248 update logger name to seconds level 2025-03-07 23:27:31 +08:00
Aria F d9e6e9a8e5 #refactor: Update config.example.toml with blur AZURE_OPENAI 2025-03-07 21:00:22 +08:00
Aria F ecac3382ec # feat: AzureOpenaiAPI support 2025-03-07 20:55:02 +08:00
Zhaoyang Yu 5650d3170b Update star history. 2025-03-07 17:13:06 +08:00
Zhaoyang Yu 612fb4964b Update wechat groups 2025-03-07 14:03:46 +08:00
Zhaoyang Yu ab0c243850 Update discord 2025-03-07 13:58:14 +08:00
Zhaoyang Yu 29415b7393 Merge branch 'main' of https://github.com/mannaandpoem/OpenManus 2025-03-07 13:47:18 +08:00
Zhaoyang Yu af65f1f754 Update wechat group 2025-03-07 13:47:16 +08:00
liangxinbing 5226628c3e update requirements.txt 2025-03-07 13:39:29 +08:00
Zhaoyang Yu 5eed101f78 Update wechat group 6 2025-03-07 13:34:18 +08:00
Zhaoyang Yu 5eb5ea1f09 Delete demo directory 2025-03-07 13:32:08 +08:00
liangxinbing d3fb8c440c add assets and update README 2025-03-07 13:06:57 +08:00
liangxinbing 20a08b6e26 add assets and update README 2025-03-07 12:34:55 +08:00
liangxinbing 1e2559a0d1 update README 2025-03-07 12:30:11 +08:00
mannaandpoem a18cf3855d Merge pull request #18 from hugsun/main
📝 updated readme video for easy viewing
2025-03-07 12:28:57 +08:00
mannaandpoem a41e37fd1f Merge branch 'main' into main 2025-03-07 12:28:29 +08:00
liangxinbing 295da18c5d update README 2025-03-07 12:21:07 +08:00
liangxinbing ddbc58e67a add assets and update README 2025-03-07 12:13:43 +08:00
liangxinbing fa89ba33ce add assets and update README 2025-03-07 11:41:54 +08:00
wangfu 247034d90f 📝 updated readme video for easy viewing 2025-03-07 11:37:53 +08:00
liangxinbing 77efc59021 add setup.py 2025-03-07 11:15:30 +08:00
Zhaoyang Yu e55206b7ee Update README.md 2025-03-07 10:56:48 +08:00
Zhaoyang Yu 4df621c5a1 Update README_zh.md 2025-03-07 10:56:01 +08:00
Zhaoyang Yu 240a4a0f0e Update and rename README_zh to README_zh.md 2025-03-07 10:54:40 +08:00
Zhaoyang Yu 90624ecaa2 Create README_zh 2025-03-07 10:54:03 +08:00
liangxinbing e370ae6f93 update README.md 2025-03-07 10:44:50 +08:00
43 changed files with 3674 additions and 82 deletions
+4
View File
@@ -0,0 +1,4 @@
blank_issues_enabled: false
contact_links:
- name: "📑 Read online docs"
about: Find tutorials, use cases, and guides in the OpenManus documentation.
@@ -0,0 +1,14 @@
---
name: "🤔 Request new features"
about: Suggest ideas or features youd like to see implemented in OpenManus.
title: ''
labels: kind/features
assignees: ''
---
**Feature description**
<!-- Provide a clear and concise description of the proposed feature -->
**Your Feature**
<!-- Explain your idea or implementation process. Optionally, include a Pull Request URL. -->
<!-- Ensure accompanying docs/tests/examples are provided for review. -->
+25
View File
@@ -0,0 +1,25 @@
---
name: "🪲 Show me the Bug"
about: Report a bug encountered while using OpenManus and seek assistance.
title: ''
labels: kind/bug
assignees: ''
---
**Bug description**
<!-- Clearly describe the bug you encountered -->
**Bug solved method**
<!-- If resolved, explain the solution. Optionally, include a Pull Request URL. -->
<!-- If unresolved, provide additional details to aid investigation -->
**Environment information**
<!-- System: e.g., Ubuntu 22.04, Python: e.g., 3.12, OpenManus version: e.g., 0.1.0 -->
- System version:
- Python version:
- OpenManus version or branch:
- Installation method (e.g., `pip install -r requirements.txt` or `pip install -e .`):
**Screenshots or logs**
<!-- Attach screenshots or logs to help diagnose the issue -->
+17
View File
@@ -0,0 +1,17 @@
**Features**
<!-- Describe the features or bug fixes in this PR. For bug fixes, link to the issue. -->
- Feature 1
- Feature 2
**Feature Docs**
<!-- Provide RFC, tutorial, or use case links for significant updates. Optional for minor changes. -->
**Influence**
<!-- Explain the impact of these changes for reviewer focus. -->
**Result**
<!-- Include screenshots or logs of unit tests or running results. -->
**Other**
<!-- Additional notes about this PR. -->
+33
View File
@@ -0,0 +1,33 @@
name: Build and upload Python package
on:
workflow_dispatch:
release:
types: [created, published]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install setuptools wheel twine
- name: Set package version
run: |
export VERSION="${GITHUB_REF#refs/tags/v}"
sed -i "s/version=.*/version=\"${VERSION}\",/" setup.py
- name: Build and publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
python setup.py bdist_wheel sdist
twine upload dist/*
+26
View File
@@ -0,0 +1,26 @@
name: Pre-commit checks
on:
pull_request:
branches:
- '**'
push:
branches:
- '**'
jobs:
pre-commit-check:
runs-on: ubuntu-latest
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install pre-commit and tools
run: |
python -m pip install --upgrade pip
pip install pre-commit black==23.1.0 isort==5.12.0 autoflake==2.0.1
- name: Run pre-commit hooks
run: pre-commit run --all-files
+23
View File
@@ -0,0 +1,23 @@
name: Close inactive issues
on:
schedule:
- cron: "5 0 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
days-before-issue-stale: 30
days-before-issue-close: 14
stale-issue-label: "inactive"
stale-issue-message: "This issue has been inactive for 30 days. Please comment if you have updates."
close-issue-message: "This issue was closed due to 45 days of inactivity. Reopen if still relevant."
days-before-pr-stale: -1
days-before-pr-close: -1
repo-token: ${{ secrets.GITHUB_TOKEN }}
+4 -4
View File
@@ -18,22 +18,22 @@ repos:
- id: autoflake
args: [
--remove-all-unused-imports,
--ignore-init-module-imports, # 忽略 __init__.py 中的导入
--ignore-init-module-imports,
--expand-star-imports,
--remove-duplicate-keys,
--remove-unused-variables,
--recursive,
--in-place,
--exclude=__init__.py, # 排除 __init__.py 文件
--exclude=__init__.py,
]
files: \.py$ # 只处理 Python 文件
files: \.py$
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
args: [
"--profile", "black", # 使用 black 兼容的配置
"--profile", "black",
"--filter-files",
"--lines-after-imports=2",
]
+162
View File
@@ -0,0 +1,162 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people.
* Being respectful of differing opinions, viewpoints, and experiences.
* Giving and gracefully accepting constructive feedback.
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience.
* Focusing on what is best not just for us as individuals, but for the overall
community.
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind.
* Trolling, insulting or derogatory comments, and personal or political attacks.
* Public or private harassment.
* Publishing others' private information, such as a physical or email address,
without their explicit permission.
* Other conduct which could reasonably be considered inappropriate in a
professional setting.
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official email address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
mannaandpoem@gmail.com
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
### Slack and Discord Etiquettes
These Slack and Discord etiquette guidelines are designed to foster an inclusive, respectful, and productive environment
for all community members. By following these best practices, we ensure effective communication and collaboration while
minimizing disruptions. Lets work together to build a supportive and welcoming community!
- Communicate respectfully and professionally, avoiding sarcasm or harsh language, and remember that tone can be
difficult to interpret in text.
- Use threads for specific discussions to keep channels organized and easier to follow.
- Tag others only when their input is critical or urgent, and use @here, @channel or @everyone sparingly to minimize
disruptions.
- Be patient, as open-source contributors and maintainers often have other commitments and may need time to respond.
- Post questions or discussions in the most relevant
channel ([discord - #general](https://discord.com/channels/1125308739348594758/1138430348557025341)).
- When asking for help or raising issues, include necessary details like links, screenshots, or clear explanations to
provide context.
- Keep discussions in public channels whenever possible to allow others to benefit from the conversation, unless the
matter is sensitive or private.
- Always adhere to [our standards](https://github.com/mannaandpoem/OpenManus/blob/main/CODE_OF_CONDUCT.md#our-standards)
to ensure a welcoming and collaborative environment.
- If you choose to mute a channel, consider setting up alerts for topics that still interest you to stay engaged. For
Slack, Go to Settings → Notifications → My Keywords to add specific keywords that will notify you when mentioned. For
example, if you're here for discussions about LLMs, mute the channel if its too busy, but set notifications to alert
you only when “LLMs” appears in messages. Also for Discord, go to the channel notifications and choose the option that
best describes your need.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
+76 -12
View File
@@ -1,18 +1,33 @@
# OpenManus 🙋
Manus is incredible, but OpenManus can achieve any ideas without an Invite Code 🛫!
English | [中文](README_zh.md)
Our team members @mannaandpoem @XiangJinyu @MoshiQAQ @didiforgithub from @MetaGPT built it within 3 hours!
[![GitHub stars](https://img.shields.io/github/stars/mannaandpoem/OpenManus?style=social)](https://github.com/mannaandpoem/OpenManus/stargazers)
&ensp;
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) &ensp;
[![Discord Follow](https://dcbadge.vercel.app/api/server/DYn29wFk9z?style=flat)](https://discord.gg/DYn29wFk9z)
# 👋 OpenManus
Manus is incredible, but OpenManus can achieve any idea without an *Invite Code* 🛫!
Our team
members [@mannaandpoem](https://github.com/mannaandpoem) [@XiangJinyu](https://github.com/XiangJinyu) [@MoshiQAQ](https://github.com/MoshiQAQ) [@didiforgithub](https://github.com/didiforgithub) [@stellaHSR](https://github.com/stellaHSR), we are from [@MetaGPT](https://github.com/geekan/MetaGPT). The prototype is launched within 3 hours and we are keeping building!
It's a simple implementation, so we welcome any suggestions, contributions, and feedback!
Enjoy your own agent with OpenManus!
## Project Demo
[Demo Video](https://github.com/mannaandpoem/OpenManus/blob/main/demo/seo_website.mp4)
We're also excited to introduce [OpenManus-RL](https://github.com/OpenManus/OpenManus-RL), an open-source project dedicated to reinforcement learning (RL)- based (such as GRPO) tuning methods for LLM agents, developed collaboratively by researchers from UIUC and OpenManus.
## Project Demo
<video src="https://private-user-images.githubusercontent.com/61239030/420168772-6dcfd0d2-9142-45d9-b74e-d10aa75073c6.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDEzMTgwNTksIm5iZiI6MTc0MTMxNzc1OSwicGF0aCI6Ii82MTIzOTAzMC80MjAxNjg3NzItNmRjZmQwZDItOTE0Mi00NWQ5LWI3NGUtZDEwYWE3NTA3M2M2Lm1wND9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAzMDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMzA3VDAzMjIzOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTdiZjFkNjlmYWNjMmEzOTliM2Y3M2VlYjgyNDRlZDJmOWE3NWZhZjE1MzhiZWY4YmQ3NjdkNTYwYTU5ZDA2MzYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.UuHQCgWYkh0OQq9qsUWqGsUbhG3i9jcZDAMeHjLt5T4" data-canonical-src="https://private-user-images.githubusercontent.com/61239030/420168772-6dcfd0d2-9142-45d9-b74e-d10aa75073c6.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDEzMTgwNTksIm5iZiI6MTc0MTMxNzc1OSwicGF0aCI6Ii82MTIzOTAzMC80MjAxNjg3NzItNmRjZmQwZDItOTE0Mi00NWQ5LWI3NGUtZDEwYWE3NTA3M2M2Lm1wND9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAzMDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMzA3VDAzMjIzOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTdiZjFkNjlmYWNjMmEzOTliM2Y3M2VlYjgyNDRlZDJmOWE3NWZhZjE1MzhiZWY4YmQ3NjdkNTYwYTU5ZDA2MzYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.UuHQCgWYkh0OQq9qsUWqGsUbhG3i9jcZDAMeHjLt5T4" controls="controls" muted="muted" class="d-block rounded-bottom-2 border-top width-fit" style="max-height:640px; min-height: 200px"></video>
## Installation
We provide two installation methods. Method 2 (using uv) is recommended for faster installation and better dependency management.
### Method 1: Using conda
1. Create a new conda environment:
```bash
@@ -33,6 +48,36 @@ cd OpenManus
pip install -r requirements.txt
```
### Method 2: Using uv (Recommended)
1. Install uv (A fast Python package installer and resolver):
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
2. Clone the repository:
```bash
git clone https://github.com/mannaandpoem/OpenManus.git
cd OpenManus
```
3. Create a new virtual environment and activate it:
```bash
uv venv
source .venv/bin/activate # On Unix/macOS
# Or on Windows:
# .venv\Scripts\activate
```
4. Install dependencies:
```bash
uv pip install -r requirements.txt
```
## Configuration
OpenManus requires configuration for the LLM APIs it uses. Follow these steps to set up your configuration:
@@ -62,6 +107,7 @@ api_key = "sk-..." # Replace with your actual API key
```
## Quick Start
One line for run OpenManus:
```bash
@@ -77,19 +123,37 @@ python run_flow.py
```
## How to contribute
We welcome any friendly suggestions and helpful contributions! Just create issues or submit pull requests.
Or contact @mannaandpoem via 📧email: mannaandpoem@gmail.com
## Roadmap
- [ ] Better Planning
- [ ] Live Demos
- [ ] Replay
- [ ] RL Fine-tuned Models
- [ ] Comprehensive Benchmarks
## Community Group
Join our networking group on Feishu and share your experience with other developers!
<div align="center" style="display: flex; gap: 20px;">
<img src="assets/community_group.jpg" alt="OpenManus 交流群" width="300" />
</div>
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=mannaandpoem/OpenManus&type=Date)](https://star-history.com/#mannaandpoem/OpenManus&Date)
## Acknowledgement
Thanks to [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo) and [broswer-use](https://github.com/browser-use/browser-use) for providing basic support for this project!
Thanks to [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo)
and [browser-use](https://github.com/browser-use/browser-use) for providing basic support for this project!
OpenManus is built by contributors from MetaGPT. Huge thanks to this agent community!
## Cite
```bibtex
@misc{openmanus2025,
author = {Xinbin Liang and Jinyu Xiang and Zhaoyang Yu and Jiayi Zhang and Sirui Hong},
title = {OpenManus: An open-source framework for building general AI agents},
year = {2025},
publisher = {GitHub},
journal = {GitHub repository},
howpublished = {\url{https://github.com/mannaandpoem/OpenManus}},
}
```
+148
View File
@@ -0,0 +1,148 @@
[English](README.md) | 中文
[![GitHub stars](https://img.shields.io/github/stars/mannaandpoem/OpenManus?style=social)](https://github.com/mannaandpoem/OpenManus/stargazers)
&ensp;
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) &ensp;
[![Discord Follow](https://dcbadge.vercel.app/api/server/DYn29wFk9z?style=flat)](https://discord.gg/DYn29wFk9z)
# 👋 OpenManus
Manus 非常棒,但 OpenManus 无需邀请码即可实现任何创意 🛫!
我们的团队成员 [@mannaandpoem](https://github.com/mannaandpoem) [@XiangJinyu](https://github.com/XiangJinyu) [@MoshiQAQ](https://github.com/MoshiQAQ) [@didiforgithub](https://github.com/didiforgithub) https://github.com/stellaHSR 来自 [@MetaGPT](https://github.com/geekan/MetaGPT) 组织,我们在 3
小时内完成了原型开发并持续迭代中!
这是一个简洁的实现方案,欢迎任何建议、贡献和反馈!
用 OpenManus 开启你的智能体之旅吧!
我们也非常高兴地向大家介绍 [OpenManus-RL](https://github.com/OpenManus/OpenManus-RL),这是一个专注于基于强化学习(RL,例如 GRPO)的方法来优化大语言模型(LLM)智能体的开源项目,由来自UIUC 和 OpenManus 的研究人员合作开发。
## 项目演示
<video src="https://private-user-images.githubusercontent.com/61239030/420168772-6dcfd0d2-9142-45d9-b74e-d10aa75073c6.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDEzMTgwNTksIm5iZiI6MTc0MTMxNzc1OSwicGF0aCI6Ii82MTIzOTAzMC80MjAxNjg3NzItNmRjZmQwZDItOTE0Mi00NWQ5LWI3NGUtZDEwYWE3NTA3M2M2Lm1wND9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAzMDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMzA3VDAzMjIzOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTdiZjFkNjlmYWNjMmEzOTliM2Y3M2VlYjgyNDRlZDJmOWE3NWZhZjE1MzhiZWY4YmQ3NjdkNTYwYTU5ZDA2MzYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.UuHQCgWYkh0OQq9qsUWqGsUbhG3i9jcZDAMeHjLt5T4" data-canonical-src="https://private-user-images.githubusercontent.com/61239030/420168772-6dcfd0d2-9142-45d9-b74e-d10aa75073c6.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDEzMTgwNTksIm5iZiI6MTc0MTMxNzc1OSwicGF0aCI6Ii82MTIzOTAzMC80MjAxNjg3NzItNmRjZmQwZDItOTE0Mi00NWQ5LWI3NGUtZDEwYWE3NTA3M2M2Lm1wND9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAzMDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMzA3VDAzMjIzOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTdiZjFkNjlmYWNjMmEzOTliM2Y3M2VlYjgyNDRlZDJmOWE3NWZhZjE1MzhiZWY4YmQ3NjdkNTYwYTU5ZDA2MzYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.UuHQCgWYkh0OQq9qsUWqGsUbhG3i9jcZDAMeHjLt5T4" controls="controls" muted="muted" class="d-block rounded-bottom-2 border-top width-fit" style="max-height:640px; min-height: 200px"></video>
## 安装指南
我们提供两种安装方式。推荐使用方式二(uv),因为它能提供更快的安装速度和更好的依赖管理。
### 方式一:使用 conda
1. 创建新的 conda 环境:
```bash
conda create -n open_manus python=3.12
conda activate open_manus
```
2. 克隆仓库:
```bash
git clone https://github.com/mannaandpoem/OpenManus.git
cd OpenManus
```
3. 安装依赖:
```bash
pip install -r requirements.txt
```
### 方式二:使用 uv(推荐)
1. 安装 uv(一个快速的 Python 包管理器):
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
2. 克隆仓库:
```bash
git clone https://github.com/mannaandpoem/OpenManus.git
cd OpenManus
```
3. 创建并激活虚拟环境:
```bash
uv venv
source .venv/bin/activate # Unix/macOS 系统
# Windows 系统使用:
# .venv\Scripts\activate
```
4. 安装依赖:
```bash
uv pip install -r requirements.txt
```
## 配置说明
OpenManus 需要配置使用的 LLM API,请按以下步骤设置:
1. 在 `config` 目录创建 `config.toml` 文件(可从示例复制):
```bash
cp config/config.example.toml config/config.toml
```
2. 编辑 `config/config.toml` 添加 API 密钥和自定义设置:
```toml
# 全局 LLM 配置
[llm]
model = "gpt-4o"
base_url = "https://api.openai.com/v1"
api_key = "sk-..." # 替换为真实 API 密钥
max_tokens = 4096
temperature = 0.0
# 可选特定 LLM 模型配置
[llm.vision]
model = "gpt-4o"
base_url = "https://api.openai.com/v1"
api_key = "sk-..." # 替换为真实 API 密钥
```
## 快速启动
一行命令运行 OpenManus
```bash
python main.py
```
然后通过终端输入你的创意!
如需体验开发中版本,可运行:
```bash
python run_flow.py
```
## 贡献指南
我们欢迎任何友好的建议和有价值的贡献!可以直接创建 issue 或提交 pull request。
或通过 📧 邮件联系 @mannaandpoemmannaandpoem@gmail.com
## 交流群
加入我们的飞书交流群,与其他开发者分享经验!
<div align="center" style="display: flex; gap: 20px;">
<img src="assets/community_group.jpg" alt="OpenManus 交流群" width="300" />
</div>
## Star 数量
[![Star History Chart](https://api.star-history.com/svg?repos=mannaandpoem/OpenManus&type=Date)](https://star-history.com/#mannaandpoem/OpenManus&Date)
## 致谢
特别感谢 [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo)
和 [browser-use](https://github.com/browser-use/browser-use) 为本项目提供的基础支持!
OpenManus 由 MetaGPT 社区的贡献者共同构建,感谢这个充满活力的智能体开发者社区!
+353
View File
@@ -0,0 +1,353 @@
import asyncio
import os
import threading
import tomllib
import uuid
import webbrowser
from datetime import datetime
from functools import partial
from json import dumps
from pathlib import Path
from fastapi import Body, FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import (
FileResponse,
HTMLResponse,
JSONResponse,
StreamingResponse,
)
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class Task(BaseModel):
id: str
prompt: str
created_at: datetime
status: str
steps: list = []
def model_dump(self, *args, **kwargs):
data = super().model_dump(*args, **kwargs)
data["created_at"] = self.created_at.isoformat()
return data
class TaskManager:
def __init__(self):
self.tasks = {}
self.queues = {}
def create_task(self, prompt: str) -> Task:
task_id = str(uuid.uuid4())
task = Task(
id=task_id, prompt=prompt, created_at=datetime.now(), status="pending"
)
self.tasks[task_id] = task
self.queues[task_id] = asyncio.Queue()
return task
async def update_task_step(
self, task_id: str, step: int, result: str, step_type: str = "step"
):
if task_id in self.tasks:
task = self.tasks[task_id]
task.steps.append({"step": step, "result": result, "type": step_type})
await self.queues[task_id].put(
{"type": step_type, "step": step, "result": result}
)
await self.queues[task_id].put(
{"type": "status", "status": task.status, "steps": task.steps}
)
async def complete_task(self, task_id: str):
if task_id in self.tasks:
task = self.tasks[task_id]
task.status = "completed"
await self.queues[task_id].put(
{"type": "status", "status": task.status, "steps": task.steps}
)
await self.queues[task_id].put({"type": "complete"})
async def fail_task(self, task_id: str, error: str):
if task_id in self.tasks:
self.tasks[task_id].status = f"failed: {error}"
await self.queues[task_id].put({"type": "error", "message": error})
task_manager = TaskManager()
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
@app.get("/download")
async def download_file(file_path: str):
if not os.path.exists(file_path):
raise HTTPException(status_code=404, detail="File not found")
return FileResponse(file_path, filename=os.path.basename(file_path))
@app.post("/tasks")
async def create_task(prompt: str = Body(..., embed=True)):
task = task_manager.create_task(prompt)
asyncio.create_task(run_task(task.id, prompt))
return {"task_id": task.id}
from app.agent.manus import Manus
async def run_task(task_id: str, prompt: str):
try:
task_manager.tasks[task_id].status = "running"
agent = Manus(
name="Manus",
description="A versatile agent that can solve various tasks using multiple tools",
)
async def on_think(thought):
await task_manager.update_task_step(task_id, 0, thought, "think")
async def on_tool_execute(tool, input):
await task_manager.update_task_step(
task_id, 0, f"Executing tool: {tool}\nInput: {input}", "tool"
)
async def on_action(action):
await task_manager.update_task_step(
task_id, 0, f"Executing action: {action}", "act"
)
async def on_run(step, result):
await task_manager.update_task_step(task_id, step, result, "run")
from app.logger import logger
class SSELogHandler:
def __init__(self, task_id):
self.task_id = task_id
async def __call__(self, message):
import re
# Extract - Subsequent Content
cleaned_message = re.sub(r"^.*? - ", "", message)
event_type = "log"
if "✨ Manus's thoughts:" in cleaned_message:
event_type = "think"
elif "🛠️ Manus selected" in cleaned_message:
event_type = "tool"
elif "🎯 Tool" in cleaned_message:
event_type = "act"
elif "📝 Oops!" in cleaned_message:
event_type = "error"
elif "🏁 Special tool" in cleaned_message:
event_type = "complete"
await task_manager.update_task_step(
self.task_id, 0, cleaned_message, event_type
)
sse_handler = SSELogHandler(task_id)
logger.add(sse_handler)
result = await agent.run(prompt)
await task_manager.update_task_step(task_id, 1, result, "result")
await task_manager.complete_task(task_id)
except Exception as e:
await task_manager.fail_task(task_id, str(e))
@app.get("/tasks/{task_id}/events")
async def task_events(task_id: str):
async def event_generator():
if task_id not in task_manager.queues:
yield f"event: error\ndata: {dumps({'message': 'Task not found'})}\n\n"
return
queue = task_manager.queues[task_id]
task = task_manager.tasks.get(task_id)
if task:
yield f"event: status\ndata: {dumps({'type': 'status', 'status': task.status, 'steps': task.steps})}\n\n"
while True:
try:
event = await queue.get()
formatted_event = dumps(event)
yield ": heartbeat\n\n"
if event["type"] == "complete":
yield f"event: complete\ndata: {formatted_event}\n\n"
break
elif event["type"] == "error":
yield f"event: error\ndata: {formatted_event}\n\n"
break
elif event["type"] == "step":
task = task_manager.tasks.get(task_id)
if task:
yield f"event: status\ndata: {dumps({'type': 'status', 'status': task.status, 'steps': task.steps})}\n\n"
yield f"event: {event['type']}\ndata: {formatted_event}\n\n"
elif event["type"] in ["think", "tool", "act", "run"]:
yield f"event: {event['type']}\ndata: {formatted_event}\n\n"
else:
yield f"event: {event['type']}\ndata: {formatted_event}\n\n"
except asyncio.CancelledError:
print(f"Client disconnected for task {task_id}")
break
except Exception as e:
print(f"Error in event stream: {str(e)}")
yield f"event: error\ndata: {dumps({'message': str(e)})}\n\n"
break
return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no",
},
)
@app.get("/tasks")
async def get_tasks():
sorted_tasks = sorted(
task_manager.tasks.values(), key=lambda task: task.created_at, reverse=True
)
return JSONResponse(
content=[task.model_dump() for task in sorted_tasks],
headers={"Content-Type": "application/json"},
)
@app.get("/tasks/{task_id}")
async def get_task(task_id: str):
if task_id not in task_manager.tasks:
raise HTTPException(status_code=404, detail="Task not found")
return task_manager.tasks[task_id]
@app.get("/config/status")
async def check_config_status():
config_path = Path(__file__).parent / "config" / "config.toml"
example_config_path = Path(__file__).parent / "config" / "config.example.toml"
if config_path.exists():
try:
with open(config_path, "rb") as f:
current_config = tomllib.load(f)
return {"status": "exists", "config": current_config}
except Exception as e:
return {"status": "error", "message": str(e)}
elif example_config_path.exists():
try:
with open(example_config_path, "rb") as f:
example_config = tomllib.load(f)
return {"status": "missing", "example_config": example_config}
except Exception as e:
return {"status": "error", "message": str(e)}
else:
return {"status": "no_example"}
@app.post("/config/save")
async def save_config(config_data: dict = Body(...)):
try:
config_dir = Path(__file__).parent / "config"
config_dir.mkdir(exist_ok=True)
config_path = config_dir / "config.toml"
toml_content = ""
if "llm" in config_data:
toml_content += "# Global LLM configuration\n[llm]\n"
llm_config = config_data["llm"]
for key, value in llm_config.items():
if key != "vision":
if isinstance(value, str):
toml_content += f'{key} = "{value}"\n'
else:
toml_content += f"{key} = {value}\n"
if "server" in config_data:
toml_content += "\n# Server configuration\n[server]\n"
server_config = config_data["server"]
for key, value in server_config.items():
if isinstance(value, str):
toml_content += f'{key} = "{value}"\n'
else:
toml_content += f"{key} = {value}\n"
with open(config_path, "w", encoding="utf-8") as f:
f.write(toml_content)
return {"status": "success"}
except Exception as e:
return {"status": "error", "message": str(e)}
@app.exception_handler(Exception)
async def generic_exception_handler(request: Request, exc: Exception):
return JSONResponse(
status_code=500, content={"message": f"Server error: {str(exc)}"}
)
def open_local_browser(config):
webbrowser.open_new_tab(f"http://{config['host']}:{config['port']}")
def load_config():
try:
config_path = Path(__file__).parent / "config" / "config.toml"
if not config_path.exists():
return {"host": "localhost", "port": 5172}
with open(config_path, "rb") as f:
config = tomllib.load(f)
return {"host": config["server"]["host"], "port": config["server"]["port"]}
except FileNotFoundError:
return {"host": "localhost", "port": 5172}
except KeyError as e:
print(
f"The configuration file is missing necessary fields: {str(e)}, use default configuration"
)
return {"host": "localhost", "port": 5172}
if __name__ == "__main__":
import uvicorn
config = load_config()
open_with_config = partial(open_local_browser, config)
threading.Timer(3, open_with_config).start()
uvicorn.run(app, host=config["host"], port=config["port"])
+3
View File
@@ -26,6 +26,9 @@ class Manus(ToolCallAgent):
system_prompt: str = SYSTEM_PROMPT
next_step_prompt: str = NEXT_STEP_PROMPT
max_observe: int = 2000
max_steps: int = 20
# Add general-purpose tools to the tool collection
available_tools: ToolCollection = Field(
default_factory=lambda: ToolCollection(
+6 -2
View File
@@ -1,5 +1,5 @@
import json
from typing import Any, List, Literal
from typing import Any, List, Literal, Optional, Union
from pydantic import Field
@@ -31,6 +31,7 @@ class ToolCallAgent(ReActAgent):
tool_calls: List[ToolCall] = Field(default_factory=list)
max_steps: int = 30
max_observe: Optional[Union[int, bool]] = None
async def think(self) -> bool:
"""Process current state and decide next actions using tools"""
@@ -114,6 +115,9 @@ class ToolCallAgent(ReActAgent):
f"🎯 Tool '{command.function.name}' completed its mission! Result: {result}"
)
if self.max_observe:
result = result[: self.max_observe]
# Add tool response to memory
tool_msg = Message.tool_message(
content=result, tool_call_id=command.id, name=command.function.name
@@ -154,7 +158,7 @@ class ToolCallAgent(ReActAgent):
except json.JSONDecodeError:
error_msg = f"Error parsing arguments for {name}: Invalid JSON format"
logger.error(
f"📝 Oops! The arguments for '{name}' don't make sense - invalid JSON"
f"📝 Oops! The arguments for '{name}' don't make sense - invalid JSON, arguments:{command.function.arguments}"
)
return f"Error: {error_msg}"
except Exception as e:
+4
View File
@@ -21,6 +21,8 @@ class LLMSettings(BaseModel):
api_key: str = Field(..., description="API key")
max_tokens: int = Field(4096, description="Maximum number of tokens per request")
temperature: float = Field(1.0, description="Sampling temperature")
api_type: str = Field(..., description="AzureOpenai or Openai")
api_version: str = Field(..., description="Azure Openai version if AzureOpenai")
class AppConfig(BaseModel):
@@ -76,6 +78,8 @@ class Config:
"api_key": base_llm.get("api_key"),
"max_tokens": base_llm.get("max_tokens", 4096),
"temperature": base_llm.get("temperature", 1.0),
"api_type": base_llm.get("api_type", ""),
"api_version": base_llm.get("api_version", ""),
}
config_dict = {
+29
View File
@@ -60,3 +60,32 @@ class BaseFlow(BaseModel, ABC):
@abstractmethod
async def execute(self, input_text: str) -> str:
"""Execute the flow with given input"""
class PlanStepStatus(str, Enum):
"""Enum class defining possible statuses of a plan step"""
NOT_STARTED = "not_started"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
BLOCKED = "blocked"
@classmethod
def get_all_statuses(cls) -> list[str]:
"""Return a list of all possible step status values"""
return [status.value for status in cls]
@classmethod
def get_active_statuses(cls) -> list[str]:
"""Return a list of values representing active statuses (not started or in progress)"""
return [cls.NOT_STARTED.value, cls.IN_PROGRESS.value]
@classmethod
def get_status_marks(cls) -> Dict[str, str]:
"""Return a mapping of statuses to their marker symbols"""
return {
cls.COMPLETED.value: "[✓]",
cls.IN_PROGRESS.value: "[→]",
cls.BLOCKED.value: "[!]",
cls.NOT_STARTED.value: "[ ]",
}
+25 -30
View File
@@ -5,7 +5,7 @@ from typing import Dict, List, Optional, Union
from pydantic import Field
from app.agent.base import BaseAgent
from app.flow.base import BaseFlow
from app.flow.base import BaseFlow, PlanStepStatus
from app.llm import LLM
from app.logger import logger
from app.schema import AgentState, Message
@@ -109,12 +109,14 @@ class PlanningFlow(BaseFlow):
# Create a system message for plan creation
system_message = Message.system_message(
"You are a planning assistant. Your task is to create a detailed plan with clear steps."
"You are a planning assistant. Create a concise, actionable plan with clear steps. "
"Focus on key milestones rather than detailed sub-steps. "
"Optimize for clarity and efficiency."
)
# Create a user message with the request
user_message = Message.user_message(
f"Create a detailed plan to accomplish this task: {request}"
f"Create a reasonable plan with clear steps to accomplish the task: {request}"
)
# Call LLM with PlanningTool
@@ -181,11 +183,11 @@ class PlanningFlow(BaseFlow):
# Find first non-completed step
for i, step in enumerate(steps):
if i >= len(step_statuses):
status = "not_started"
status = PlanStepStatus.NOT_STARTED.value
else:
status = step_statuses[i]
if status in ["not_started", "in_progress"]:
if status in PlanStepStatus.get_active_statuses():
# Extract step type/category if available
step_info = {"text": step}
@@ -202,17 +204,17 @@ class PlanningFlow(BaseFlow):
command="mark_step",
plan_id=self.active_plan_id,
step_index=i,
step_status="in_progress",
step_status=PlanStepStatus.IN_PROGRESS.value,
)
except Exception as e:
logger.warning(f"Error marking step as in_progress: {e}")
# Update step status directly if needed
if i < len(step_statuses):
step_statuses[i] = "in_progress"
step_statuses[i] = PlanStepStatus.IN_PROGRESS.value
else:
while len(step_statuses) < i:
step_statuses.append("not_started")
step_statuses.append("in_progress")
step_statuses.append(PlanStepStatus.NOT_STARTED.value)
step_statuses.append(PlanStepStatus.IN_PROGRESS.value)
plan_data["step_statuses"] = step_statuses
@@ -264,7 +266,7 @@ class PlanningFlow(BaseFlow):
command="mark_step",
plan_id=self.active_plan_id,
step_index=self.current_step_index,
step_status="completed",
step_status=PlanStepStatus.COMPLETED.value,
)
logger.info(
f"Marked step {self.current_step_index} as completed in plan {self.active_plan_id}"
@@ -278,10 +280,10 @@ class PlanningFlow(BaseFlow):
# Ensure the step_statuses list is long enough
while len(step_statuses) <= self.current_step_index:
step_statuses.append("not_started")
step_statuses.append(PlanStepStatus.NOT_STARTED.value)
# Update the status
step_statuses[self.current_step_index] = "completed"
step_statuses[self.current_step_index] = PlanStepStatus.COMPLETED.value
plan_data["step_statuses"] = step_statuses
async def _get_plan_text(self) -> str:
@@ -309,23 +311,18 @@ class PlanningFlow(BaseFlow):
# Ensure step_statuses and step_notes match the number of steps
while len(step_statuses) < len(steps):
step_statuses.append("not_started")
step_statuses.append(PlanStepStatus.NOT_STARTED.value)
while len(step_notes) < len(steps):
step_notes.append("")
# Count steps by status
status_counts = {
"completed": 0,
"in_progress": 0,
"blocked": 0,
"not_started": 0,
}
status_counts = {status: 0 for status in PlanStepStatus.get_all_statuses()}
for status in step_statuses:
if status in status_counts:
status_counts[status] += 1
completed = status_counts["completed"]
completed = status_counts[PlanStepStatus.COMPLETED.value]
total = len(steps)
progress = (completed / total) * 100 if total > 0 else 0
@@ -335,21 +332,19 @@ class PlanningFlow(BaseFlow):
plan_text += (
f"Progress: {completed}/{total} steps completed ({progress:.1f}%)\n"
)
plan_text += f"Status: {status_counts['completed']} completed, {status_counts['in_progress']} in progress, "
plan_text += f"{status_counts['blocked']} blocked, {status_counts['not_started']} not started\n\n"
plan_text += f"Status: {status_counts[PlanStepStatus.COMPLETED.value]} completed, {status_counts[PlanStepStatus.IN_PROGRESS.value]} in progress, "
plan_text += f"{status_counts[PlanStepStatus.BLOCKED.value]} blocked, {status_counts[PlanStepStatus.NOT_STARTED.value]} not started\n\n"
plan_text += "Steps:\n"
status_marks = PlanStepStatus.get_status_marks()
for i, (step, status, notes) in enumerate(
zip(steps, step_statuses, step_notes)
):
if status == "completed":
status_mark = "[✓]"
elif status == "in_progress":
status_mark = "[→]"
elif status == "blocked":
status_mark = "[!]"
else: # not_started
status_mark = "[ ]"
# Use status marks to indicate step status
status_mark = status_marks.get(
status, status_marks[PlanStepStatus.NOT_STARTED.value]
)
plan_text += f"{i}. {status_mark} {step}\n"
if notes:
+13 -3
View File
@@ -2,6 +2,7 @@ from typing import Dict, List, Literal, Optional, Union
from openai import (
APIError,
AsyncAzureOpenAI,
AsyncOpenAI,
AuthenticationError,
OpenAIError,
@@ -35,9 +36,18 @@ class LLM:
self.model = llm_config.model
self.max_tokens = llm_config.max_tokens
self.temperature = llm_config.temperature
self.client = AsyncOpenAI(
api_key=llm_config.api_key, base_url=llm_config.base_url
)
self.api_type = llm_config.api_type
self.api_key = llm_config.api_key
self.api_version = llm_config.api_version
self.base_url = llm_config.base_url
if self.api_type == "azure":
self.client = AsyncAzureOpenAI(
base_url=self.base_url,
api_key=self.api_key,
api_version=self.api_version,
)
else:
self.client = AsyncOpenAI(api_key=self.api_key, base_url=self.base_url)
@staticmethod
def format_messages(messages: List[Union[dict, Message]]) -> List[dict]:
+1 -1
View File
@@ -15,7 +15,7 @@ def define_log_level(print_level="INFO", logfile_level="DEBUG", name: str = None
_print_level = print_level
current_date = datetime.now()
formatted_date = current_date.strftime("%Y%m%d")
formatted_date = current_date.strftime("%Y%m%d%H%M%S")
log_name = (
f"{name}_{formatted_date}" if name else formatted_date
) # name a log with prefix name
+14 -12
View File
@@ -1,25 +1,27 @@
PLANNING_SYSTEM_PROMPT = """
You are an expert Planning Agent tasked with solving complex problems by creating and managing structured plans.
You are an expert Planning Agent tasked with solving problems efficiently through structured plans.
Your job is:
1. Analyze requests to understand the task scope
2. Create clear, actionable plans with the `planning` tool
2. Create a clear, actionable plan that makes meaningful progress with the `planning` tool
3. Execute steps using available tools as needed
4. Track progress and adapt plans dynamically
5. Use `finish` to conclude when the task is complete
4. Track progress and adapt plans when necessary
5. Use `finish` to conclude immediately when the task is complete
Available tools will vary by task but may include:
- `planning`: Create, update, and track plans (commands: create, update, mark_step, etc.)
- `finish`: End the task when complete
Break tasks into logical, sequential steps. Think about dependencies and verification methods.
Break tasks into logical steps with clear outcomes. Avoid excessive detail or sub-steps.
Think about dependencies and verification methods.
Know when to conclude - don't continue thinking once objectives are met.
"""
NEXT_STEP_PROMPT = """
Based on the current state, what's your next step?
Consider:
1. Do you need to create or refine a plan?
2. Are you ready to execute a specific step?
3. Have you completed the task?
Based on the current state, what's your next action?
Choose the most efficient path forward:
1. Is the plan sufficient, or does it need refinement?
2. Can you execute the next step immediately?
3. Is the task complete? If so, use `finish` right away.
Provide reasoning, then select the appropriate tool or action.
Be concise in your reasoning, then select the appropriate tool or action.
"""
+19 -1
View File
@@ -20,6 +20,8 @@ content extraction, and tab management. Supported actions include:
- 'input_text': Input text into an element
- 'screenshot': Capture a screenshot
- 'get_html': Get page HTML content
- 'get_text': Get text content of the page
- 'read_links': Get all links on the page
- 'execute_js': Execute JavaScript code
- 'scroll': Scroll the page
- 'switch_tab': Switch to a specific tab
@@ -43,6 +45,7 @@ class BrowserUseTool(BaseTool):
"input_text",
"screenshot",
"get_html",
"get_text",
"execute_js",
"scroll",
"switch_tab",
@@ -100,7 +103,12 @@ class BrowserUseTool(BaseTool):
async def _ensure_browser_initialized(self) -> BrowserContext:
"""Ensure browser and context are initialized."""
if self.browser is None:
self.browser = BrowserUseBrowser(BrowserConfig(headless=False))
# 使用Chrome命令行参数设置窗口大小和位置
browser_config = BrowserConfig(
headless=False,
disable_security=True,
)
self.browser = BrowserUseBrowser(browser_config)
if self.context is None:
self.context = await self.browser.new_context()
self.dom_service = DomService(await self.context.get_current_page())
@@ -180,6 +188,16 @@ class BrowserUseTool(BaseTool):
truncated = html[:2000] + "..." if len(html) > 2000 else html
return ToolResult(output=truncated)
elif action == "get_text":
text = await context.execute_javascript("document.body.innerText")
return ToolResult(output=text)
elif action == "read_links":
links = await context.execute_javascript(
"document.querySelectorAll('a[href]').forEach((elem) => {if (elem.innerText) {console.log(elem.innerText, elem.href)}})"
)
return ToolResult(output=links)
elif action == "execute_js":
if not script:
return ToolResult(
-2
View File
@@ -1,4 +1,3 @@
import asyncio
import os
import aiofiles
@@ -58,4 +57,3 @@ The tool accepts content and a file path, and saves the content to that location
return f"Content successfully saved to {file_path}"
except Exception as e:
return f"Error saving file: {str(e)}"
+2 -1
View File
@@ -1,7 +1,8 @@
from app.tool.base import BaseTool
_TERMINATE_DESCRIPTION = """Terminate the interaction when the request is met OR if the assistant cannot proceed further with the task."""
_TERMINATE_DESCRIPTION = """Terminate the interaction when the request is met OR if the assistant cannot proceed further with the task.
When you have finished all the tasks, call this tool to end the work."""
class Terminate(BaseTool):
Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

+14
View File
@@ -6,8 +6,22 @@ api_key = "sk-..."
max_tokens = 4096
temperature = 0.0
# [llm] #AZURE OPENAI:
# api_type= 'azure'
# model = "YOUR_MODEL_NAME" #"gpt-4o-mini"
# base_url = "{YOUR_AZURE_ENDPOINT.rstrip('/')}/openai/deployments/{AZURE_DEPOLYMENT_ID}"
# api_key = "AZURE API KEY"
# max_tokens = 8096
# temperature = 0.0
# api_version="AZURE API VERSION" #"2024-08-01-preview"
# Optional configuration for specific LLM models
[llm.vision]
model = "claude-3-5-sonnet"
base_url = "https://api.openai.com/v1"
api_key = "sk-..."
# Server configuration
[server]
host = "localhost"
port = 5172
BIN
View File
Binary file not shown.
@@ -0,0 +1,62 @@
JAPAN TRAVEL HANDBOOK - GUIDE TO VERSIONS
Location: D:/OpenManus/
1. DETAILED DIGITAL VERSION
File: japan_travel_handbook.html
Best for: Desktop/laptop viewing
Features:
- Complete comprehensive guide
- Detailed itinerary
- Full proposal planning section
- All hotel recommendations
- Comprehensive budget breakdown
Usage: Open in web browser for trip planning and detailed reference
2. PRINT-FRIENDLY VERSION
File: japan_travel_handbook_print.html
Best for: Physical reference during travel
Features:
- Condensed essential information
- Optimized for paper printing
- Clear, printer-friendly formatting
- Quick reference tables
Usage: Print and keep in travel documents folder
3. MOBILE-OPTIMIZED VERSION
File: japan_travel_handbook_mobile.html
Best for: On-the-go reference during trip
Features:
- Touch-friendly interface
- Collapsible sections
- Quick access emergency buttons
- Dark mode support
- Responsive design
Usage: Save to phone's browser bookmarks for quick access
RECOMMENDED SETUP:
1. Before Trip:
- Use detailed version for planning
- Print the print-friendly version
- Save mobile version to phone
2. During Trip:
- Keep printed version with travel documents
- Use mobile version for daily reference
- Access detailed version when needed for specific information
3. Emergency Access:
- Mobile version has quick-access emergency information
- Keep printed version as backup
- All emergency numbers and contacts in both versions
Note: All versions contain the same core information but are formatted differently for optimal use in different situations.
IMPORTANT DATES:
- Trip Duration: April 15-23, 2024
- Proposal Day: April 19, 2024
- Key Reservation Deadlines:
* Flights: Book by January 2024
* Hotels: Book by February 2024
* Restaurant Reservations: Book by January 2024
* JR Pass: Purchase by March 2024
@@ -0,0 +1,124 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Japan Travel Handbook - April 15-23, 2024</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; margin: 0; padding: 20px; }
.container { max-width: 1000px; margin: 0 auto; }
h1, h2, h3 { color: #333; }
.day-item { background: #f9f9f9; padding: 15px; margin: 10px 0; border-radius: 5px; }
.important-note { background: #ffe6e6; padding: 10px; border-radius: 5px; }
.phrase-table { width: 100%; border-collapse: collapse; }
.phrase-table td, .phrase-table th { border: 1px solid #ddd; padding: 8px; }
.proposal-spot { background: #e6ffe6; padding: 15px; margin: 10px 0; border-radius: 5px; }
.flight-info { background: #e6f3ff; padding: 15px; margin: 10px 0; border-radius: 5px; }
.checklist { background: #fff3e6; padding: 15px; margin: 10px 0; border-radius: 5px; }
.hotels { background: #e6e6ff; padding: 15px; margin: 10px 0; border-radius: 5px; }
.proposal-plan { background: #ffe6ff; padding: 15px; margin: 10px 0; border-radius: 5px; }
.checkbox-list li { list-style-type: none; margin-bottom: 8px; }
.checkbox-list li:before { content: "☐ "; }
.warning { color: #ff4444; }
</style>
</head>
<body>
<div class="container">
[Previous content remains the same...]
<div class="proposal-plan">
<h2>🌸 Proposal Planning Guide 🌸</h2>
<h3>Ring Security & Transport</h3>
<ul>
<li><strong>Carrying the Ring:</strong>
<ul>
<li>Always keep the ring in your carry-on luggage, never in checked bags</li>
<li>Use a discrete, non-branded box or case</li>
<li>Consider travel insurance that covers jewelry</li>
<li>Keep receipt/appraisal documentation separate from the ring</li>
</ul>
</li>
<li><strong>Airport Security Tips:</strong>
<ul>
<li>No need to declare the ring unless value exceeds ¥1,000,000 (~$6,700)</li>
<li>If asked, simply state it's "personal jewelry"</li>
<li>Consider requesting private screening to maintain surprise</li>
<li>Keep ring in original box until through security, then transfer to more discrete case</li>
</ul>
</li>
</ul>
<h3>Proposal Location Details - Maruyama Park</h3>
<ul>
<li><strong>Best Timing:</strong>
<ul>
<li>Date: April 19 (Day 5)</li>
<li>Time: 5:30 PM (30 minutes before sunset)</li>
<li>Park closes at 8:00 PM in April</li>
</ul>
</li>
<li><strong>Specific Spot Recommendations:</strong>
<ul>
<li>Primary Location: Near the famous weeping cherry tree
<br>- Less crowded in early evening
<br>- Beautiful illumination starts at dusk
<br>- Iconic Kyoto backdrop
</li>
<li>Backup Location: Gion Shirakawa area
<br>- Atmospheric stone-paved street
<br>- Traditional buildings and cherry trees
<br>- Beautiful in light rain
</li>
</ul>
</li>
</ul>
<h3>Proposal Day Planning</h3>
<ul>
<li><strong>Morning Preparation:</strong>
<ul>
<li>Confirm weather forecast</li>
<li>Transfer ring to secure pocket/bag</li>
<li>Have backup indoor location details ready</li>
</ul>
</li>
<li><strong>Suggested Timeline:</strong>
<ul>
<li>4:00 PM: Start heading to Maruyama Park area</li>
<li>4:30 PM: Light refreshments at nearby tea house</li>
<li>5:15 PM: Begin walk through park</li>
<li>5:30 PM: Arrive at proposal spot</li>
<li>6:00 PM: Sunset and illumination begins</li>
<li>7:00 PM: Celebratory dinner reservation</li>
</ul>
</li>
</ul>
<h3>Celebration Dinner Options</h3>
<ul>
<li><strong>Traditional Japanese:</strong> Kikunoi Roan
<br>- Intimate 2-star Michelin restaurant
<br>- Advance reservation required (3 months)
<br>- Price: ¥15,000-20,000 per person
</li>
<li><strong>Modern Fusion:</strong> The Sodoh
<br>- Beautiful garden views
<br>- Western-style seating available
<br>- Price: ¥12,000-15,000 per person
</li>
</ul>
<div class="warning">
<h3>Important Notes:</h3>
<ul>
<li>Keep proposal plans in separate notes from shared itinerary</li>
<li>Have a backup plan in case of rain (indoor locations listed above)</li>
<li>Consider hiring a local photographer to capture the moment</li>
<li>Save restaurant staff contact info in case of timing changes</li>
</ul>
</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,255 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Japan Travel Guide (Mobile)</title>
<style>
* { box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
margin: 0;
padding: 10px;
line-height: 1.6;
font-size: 16px;
}
.container {
max-width: 100%;
margin: 0 auto;
}
h1 { font-size: 1.5em; margin: 10px 0; }
h2 { font-size: 1.3em; margin: 8px 0; }
h3 { font-size: 1.1em; margin: 6px 0; }
/* Mobile-friendly cards */
.card {
background: #fff;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
margin: 10px 0;
padding: 15px;
}
/* Collapsible sections */
.collapsible {
background: #f8f9fa;
border: none;
border-radius: 8px;
width: 100%;
padding: 15px;
text-align: left;
font-size: 1.1em;
font-weight: bold;
cursor: pointer;
margin: 5px 0;
}
.content {
display: none;
padding: 10px;
}
.active {
background: #e9ecef;
}
/* Mobile-friendly tables */
.table-wrapper {
overflow-x: auto;
margin: 10px 0;
}
table {
width: 100%;
border-collapse: collapse;
min-width: 300px;
}
th, td {
padding: 10px;
border: 1px solid #ddd;
text-align: left;
}
th {
background: #f8f9fa;
}
/* Touch-friendly lists */
ul, ol {
padding-left: 20px;
margin: 10px 0;
}
li {
margin: 8px 0;
padding: 5px 0;
}
/* Emergency info styling */
.emergency {
background: #ffe6e6;
border-left: 4px solid #ff4444;
padding: 10px;
margin: 10px 0;
}
/* Quick access buttons */
.quick-access {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 10px 0;
}
.quick-btn {
background: #007bff;
color: white;
border: none;
border-radius: 20px;
padding: 10px 20px;
font-size: 0.9em;
cursor: pointer;
flex: 1 1 auto;
text-align: center;
min-width: 120px;
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
body {
background: #1a1a1a;
color: #fff;
}
.card {
background: #2d2d2d;
}
.collapsible {
background: #333;
color: #fff;
}
.active {
background: #404040;
}
th {
background: #333;
}
td, th {
border-color: #404040;
}
}
</style>
</head>
<body>
<div class="container">
<h1>Japan Travel Guide</h1>
<p><strong>April 15-23, 2024</strong></p>
<div class="quick-access">
<button class="quick-btn" onclick="showSection('emergency')">Emergency</button>
<button class="quick-btn" onclick="showSection('phrases')">Phrases</button>
<button class="quick-btn" onclick="showSection('transport')">Transport</button>
<button class="quick-btn" onclick="showSection('proposal')">Proposal</button>
</div>
<div class="emergency card" id="emergency">
<h2>Emergency Contacts</h2>
<ul>
<li>🚑 Emergency: 119</li>
<li>👮 Police: 110</li>
<li>🏢 US Embassy: +81-3-3224-5000</li>
<li>️ Tourist Info: 03-3201-3331</li>
</ul>
</div>
<button class="collapsible">📅 Daily Itinerary</button>
<div class="content">
<div class="table-wrapper">
<table>
<tr><th>Date</th><th>Location</th><th>Activities</th></tr>
<tr><td>Apr 15</td><td>Tokyo</td><td>Arrival, Shinjuku</td></tr>
<tr><td>Apr 16</td><td>Tokyo</td><td>Meiji, Harajuku, Senso-ji</td></tr>
<tr><td>Apr 17</td><td>Tokyo</td><td>Tea Ceremony, Budokan</td></tr>
<tr><td>Apr 18</td><td>Kyoto</td><td>Travel, Kinkaku-ji</td></tr>
<tr><td>Apr 19</td><td>Kyoto</td><td>Fushimi Inari, Proposal</td></tr>
<tr><td>Apr 20</td><td>Nara</td><td>Deer Park, Temples</td></tr>
<tr><td>Apr 21</td><td>Tokyo</td><td>Return, Bay Cruise</td></tr>
</table>
</div>
</div>
<button class="collapsible">🗣️ Essential Phrases</button>
<div class="content">
<div class="table-wrapper">
<table>
<tr><th>English</th><th>Japanese</th></tr>
<tr><td>Thank you</td><td>ありがとう</td></tr>
<tr><td>Excuse me</td><td>すみません</td></tr>
<tr><td>Please</td><td>お願いします</td></tr>
<tr><td>Where is...</td><td>...はどこですか</td></tr>
<tr><td>Help!</td><td>助けて!</td></tr>
</table>
</div>
</div>
<button class="collapsible">🚅 Transportation</button>
<div class="content">
<div class="card">
<h3>Key Routes</h3>
<ul>
<li>Tokyo-Kyoto: 2h15m</li>
<li>Kyoto-Nara: 45m</li>
<li>Last trains: ~midnight</li>
</ul>
<p><strong>JR Pass:</strong> Activate April 15</p>
</div>
</div>
<button class="collapsible">💍 Proposal Plan</button>
<div class="content">
<div class="card">
<h3>April 19 Timeline</h3>
<ul>
<li>4:00 PM: Head to Maruyama Park</li>
<li>5:30 PM: Arrive at spot</li>
<li>7:00 PM: Dinner at Kikunoi Roan</li>
</ul>
<p><strong>Backup:</strong> Gion Shirakawa area</p>
</div>
</div>
<button class="collapsible">💰 Budget Tracker</button>
<div class="content">
<div class="table-wrapper">
<table>
<tr><th>Item</th><th>Budget</th></tr>
<tr><td>Hotels</td><td>$1500-2000</td></tr>
<tr><td>Transport</td><td>$600-800</td></tr>
<tr><td>Food</td><td>$800-1000</td></tr>
<tr><td>Activities</td><td>$600-800</td></tr>
<tr><td>Shopping</td><td>$500-400</td></tr>
</table>
</div>
</div>
</div>
<script>
// Add click handlers for collapsible sections
var coll = document.getElementsByClassName("collapsible");
for (var i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function() {
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.display === "block") {
content.style.display = "none";
} else {
content.style.display = "block";
}
});
}
// Function to show specific section
function showSection(id) {
document.getElementById(id).scrollIntoView({
behavior: 'smooth'
});
}
</script>
</body>
</html>
@@ -0,0 +1,162 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Japan Travel Handbook (Print Version) - April 15-23, 2024</title>
<style>
@media print {
body {
font-family: Arial, sans-serif;
font-size: 11pt;
line-height: 1.4;
margin: 0.5in;
}
h1 { font-size: 16pt; }
h2 { font-size: 14pt; }
h3 { font-size: 12pt; }
.section {
margin: 10px 0;
padding: 5px;
border: 1px solid #ccc;
page-break-inside: avoid;
}
.no-break {
page-break-inside: avoid;
}
table {
border-collapse: collapse;
width: 100%;
margin: 10px 0;
}
td, th {
border: 1px solid #000;
padding: 4px;
font-size: 10pt;
}
ul, ol {
margin: 5px 0;
padding-left: 20px;
}
li {
margin: 3px 0;
}
.page-break {
page-break-before: always;
}
}
/* Screen styles */
body {
font-family: Arial, sans-serif;
line-height: 1.4;
margin: 20px;
max-width: 800px;
margin: 0 auto;
}
.section {
margin: 15px 0;
padding: 15px;
border: 1px solid #ccc;
border-radius: 5px;
}
table {
border-collapse: collapse;
width: 100%;
margin: 10px 0;
}
td, th {
border: 1px solid #000;
padding: 8px;
}
@media screen {
.page-break {
margin: 30px 0;
border-top: 2px dashed #ccc;
}
}
</style>
</head>
<body>
<h1>Japan Travel Handbook (Print Version)</h1>
<p><strong>Trip Dates:</strong> April 15-23, 2024</p>
<div class="section">
<h2>Emergency Contacts & Important Information</h2>
<ul>
<li>Emergency in Japan: 119 (Ambulance/Fire) / 110 (Police)</li>
<li>US Embassy Tokyo: +81-3-3224-5000</li>
<li>Tourist Information Hotline: 03-3201-3331</li>
<li>Your Travel Insurance: [Write number here]</li>
</ul>
</div>
<div class="section">
<h2>Daily Itinerary Summary</h2>
<table>
<tr><th>Date</th><th>Location</th><th>Key Activities</th></tr>
<tr><td>Apr 15</td><td>Tokyo</td><td>Arrival, Shinjuku area exploration</td></tr>
<tr><td>Apr 16</td><td>Tokyo</td><td>Meiji Shrine, Harajuku, Senso-ji, Skytree</td></tr>
<tr><td>Apr 17</td><td>Tokyo</td><td>Tea Ceremony, Budokan, Yanaka Ginza</td></tr>
<tr><td>Apr 18</td><td>Kyoto</td><td>Travel to Kyoto, Kinkaku-ji, Gion</td></tr>
<tr><td>Apr 19</td><td>Kyoto</td><td>Fushimi Inari, Arashiyama, Evening Proposal</td></tr>
<tr><td>Apr 20</td><td>Nara/Kyoto</td><td>Nara Park day trip, deer feeding</td></tr>
<tr><td>Apr 21</td><td>Tokyo</td><td>Return to Tokyo, bay cruise</td></tr>
</table>
</div>
<div class="page-break"></div>
<div class="section">
<h2>Essential Japanese Phrases</h2>
<table>
<tr><th>English</th><th>Japanese</th><th>When to Use</th></tr>
<tr><td>Arigatou gozaimasu</td><td>ありがとうございます</td><td>Thank you (formal)</td></tr>
<tr><td>Sumimasen</td><td>すみません</td><td>Excuse me/Sorry</td></tr>
<tr><td>Onegaishimasu</td><td>お願いします</td><td>Please</td></tr>
<tr><td>Toire wa doko desu ka?</td><td>トイレはどこですか?</td><td>Where is the bathroom?</td></tr>
<tr><td>Eigo ga hanasemasu ka?</td><td>英語が話せますか?</td><td>Do you speak English?</td></tr>
</table>
</div>
<div class="section">
<h2>Transportation Notes</h2>
<ul>
<li>JR Pass: Activate on April 15</li>
<li>Tokyo-Kyoto Shinkansen: ~2h15m</li>
<li>Kyoto-Nara Local Train: ~45m</li>
<li>Last trains: Usually around midnight</li>
<li>Keep ¥3000 for unexpected taxi rides</li>
</ul>
</div>
<div class="page-break"></div>
<div class="section no-break">
<h2>Proposal Day Timeline (April 19)</h2>
<table>
<tr><th>Time</th><th>Activity</th><th>Notes</th></tr>
<tr><td>4:00 PM</td><td>Head to Maruyama Park</td><td>Check weather first</td></tr>
<tr><td>4:30 PM</td><td>Tea house visit</td><td>Light refreshments</td></tr>
<tr><td>5:15 PM</td><td>Park walk begins</td><td>Head to weeping cherry tree</td></tr>
<tr><td>5:30 PM</td><td>Arrive at spot</td><td>Find quiet area</td></tr>
<tr><td>7:00 PM</td><td>Dinner reservation</td><td>Kikunoi Roan</td></tr>
</table>
<p><strong>Backup Location:</strong> Gion Shirakawa area (in case of rain)</p>
</div>
<div class="section">
<h2>Quick Reference Budget</h2>
<table>
<tr><th>Item</th><th>Budget (USD)</th><th>Notes</th></tr>
<tr><td>Hotels</td><td>1500-2000</td><td>Pre-booked</td></tr>
<tr><td>Transport</td><td>600-800</td><td>Including JR Pass</td></tr>
<tr><td>Food</td><td>800-1000</td><td>~$60/person/day</td></tr>
<tr><td>Activities</td><td>600-800</td><td>Including tea ceremony</td></tr>
<tr><td>Shopping</td><td>500-400</td><td>Souvenirs/gifts</td></tr>
</table>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

+16
View File
@@ -0,0 +1,16 @@
# Examples
We put some examples in the `examples` directory. All the examples use the same prompt
as [Manus](https://manus.im/?utm_source=ai-bot.cn).
The Model we use is `claude3.5`.
## Japan Travel Plan
**Prompt**
```
I need a 7-day Japan itinerary for April 15-23 from Seattle, with a $2500-5000 budget for my fiancée and me. We love historical sites, hidden gems, and Japanese culture (kendo, tea ceremonies, Zen meditation). We want to see Nara's deer and explore cities on foot. I plan to propose during this trip and need a special location recommendation. Please provide a detailed itinerary and a simple HTML travel handbook with maps, attraction descriptions, essential Japanese phrases, and travel tips we can reference throughout our journey.
```
**preview**
![alt text](./pictures/japan-travel-plan-1.png)
![alt text](./pictures/japan-travel-plan-2.png)
+6 -2
View File
@@ -8,10 +8,14 @@ async def main():
agent = Manus()
while True:
try:
prompt = input("Enter your prompt (or 'exit' to quit): ")
if prompt.lower() == "exit":
prompt = input("Enter your prompt (or 'exit'/'quit' to quit): ")
prompt_lower = prompt.lower()
if prompt_lower in ["exit", "quit"]:
logger.info("Goodbye!")
break
if not prompt.strip():
logger.warning("Skipping empty prompt.")
continue
logger.warning("Processing your request...")
await agent.run(prompt)
except KeyboardInterrupt:
+14
View File
@@ -0,0 +1,14 @@
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
# Log settings
log_cli = true
log_cli_level = INFO
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
log_cli_date_format = %Y-%m-%d %H:%M:%S
# Make sure asyncio works properly
asyncio_mode = auto
+3 -1
View File
@@ -5,6 +5,7 @@ pyyaml~=6.0.2
loguru~=0.7.3
numpy
datasets~=3.2.0
fastapi~=0.115.11
html2text~=2024.2.26
gymnasium~=1.0.0
@@ -17,4 +18,5 @@ googlesearch-python~=1.3.0
aiofiles~=24.1.0
pydantic_core~=2.27.2
colorama~=0.4.6
colorama~=0.4.6
playwright~=1.49.1
+42
View File
@@ -0,0 +1,42 @@
@echo off
setlocal
cd /d %~dp0
set "VENV_DIR=%~dp0venv"
set "PYTHON_PATH=%VENV_DIR%\python.exe"
where git >nul 2>&1
if %errorlevel% == 0 (
echo Trying to sync with GitHub repository...
git pull origin front-end 2>&1 || echo Failed to sync with GitHub, skipping update...
) else (
echo Git not detected, skipping code synchronization
)
if not exist "%VENV_DIR%\" (
echo Virtual environment not found, initializing installation...
python -m venv "%VENV_DIR%" || (
echo Failed to create virtual environment, please install Python 3.12 first
pause
exit /b 1
)
call "%VENV_DIR%\Scripts\activate.bat"
pip install -r requirements.txt || (
echo Dependency installation failed, please check requirements. txt
pause
exit /b 1
)
)
echo Starting Python application...
if not exist "%PYTHON_PATH%" (
echo Error: Python executable file does not exist in %PYTHON_PATH%
echo Please try deleting the venv folder and running the script again
pause
exit /b 1
)
"%PYTHON_PATH%" "%~dp0app.py"
pause
endlocal
+28 -8
View File
@@ -1,32 +1,52 @@
import asyncio
import time
from app.agent.manus import Manus
from app.flow.base import FlowType
from app.flow.flow_factory import FlowFactory
from app.logger import logger
async def run_flow():
agent = Manus()
agents = {
"manus": Manus(),
}
while True:
try:
prompt = input("Enter your prompt (or 'exit' to quit): ")
if prompt.lower() == "exit":
print("Goodbye!")
logger.info("Goodbye!")
break
flow = FlowFactory.create_flow(
flow_type=FlowType.PLANNING,
agents=agent,
agents=agents,
)
if prompt.strip().isspace():
logger.warning("Skipping empty prompt.")
continue
logger.warning("Processing your request...")
print("Processing your request...")
result = await flow.execute(prompt)
print(result)
try:
start_time = time.time()
result = await asyncio.wait_for(
flow.execute(prompt),
timeout=3600, # 60 minute timeout for the entire execution
)
elapsed_time = time.time() - start_time
logger.info(f"Request processed in {elapsed_time:.2f} seconds")
logger.info(result)
except asyncio.TimeoutError:
logger.error("Request processing timed out after 1 hour")
logger.info(
"Operation terminated due to timeout. Please try a simpler request."
)
except KeyboardInterrupt:
print("Goodbye!")
break
logger.info("Operation cancelled by user.")
except Exception as e:
logger.error(f"Error: {str(e)}")
if __name__ == "__main__":
+49
View File
@@ -0,0 +1,49 @@
from setuptools import find_packages, setup
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setup(
name="openmanus",
version="0.1.0",
author="mannaandpoem and OpenManus Team",
author_email="mannaandpoem@gmail.com",
description="A versatile agent that can solve various tasks using multiple tools",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/mannaandpoem/OpenManus",
packages=find_packages(),
install_requires=[
"pydantic~=2.10.4",
"openai~=1.58.1",
"tenacity~=9.0.0",
"pyyaml~=6.0.2",
"loguru~=0.7.3",
"numpy",
"datasets~=3.2.0",
"html2text~=2024.2.26",
"gymnasium~=1.0.0",
"pillow~=10.4.0",
"browsergym~=0.13.3",
"uvicorn~=0.34.0",
"unidiff~=0.7.5",
"browser-use~=0.1.40",
"googlesearch-python~=1.3.0",
"aiofiles~=24.1.0",
"pydantic_core~=2.27.2",
"colorama~=0.4.6",
],
classifiers=[
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.12",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires=">=3.12",
entry_points={
"console_scripts": [
"openmanus=main:main",
],
},
)
+221
View File
@@ -0,0 +1,221 @@
const i18n = {
en: {
welcome: {
title: "Welcome to OpenManus Local Version",
subtitle: "Please enter a task prompt to start a new task"
},
config: {
title: "System Configuration",
subtitle: "Please fill in the necessary configuration information to continue using the system",
note: "Please ensure that the following configuration information is correct.",
apiKeyNote: "If you do not have an API key, please obtain one from the corresponding AI service provider.",
llmConfig: "LLM Configuration",
serverConfig: "Server Configuration",
save: "Save Configuration",
cancel: "Cancel",
required: "Required"
},
input: {
placeholder: "Enter task prompt...",
button: "Create Task"
},
history: {
title: "History Tasks"
},
status: {
running: "Task running",
completed: "Task completed",
failed: "Task failed",
error: "Error"
},
buttons: {
configSetting: "Configuration"
}
},
zh: {
welcome: {
title: "欢迎使用 OpenManus 本地版",
subtitle: "请输入任务提示以开始新任务"
},
config: {
title: "系统配置",
subtitle: "请填写必要的配置信息以继续使用系统",
note: "请确保以下配置信息正确无误。",
apiKeyNote: "如果您没有API密钥,请从相应的AI服务提供商获取。",
llmConfig: "LLM 配置",
serverConfig: "服务器配置",
save: "保存配置",
cancel: "取消",
required: "必填"
},
input: {
placeholder: "输入任务提示...",
button: "创建任务"
},
history: {
title: "历史任务"
},
status: {
running: "任务运行中",
completed: "任务完成",
failed: "任务失败",
error: "错误"
},
buttons: {
configSetting: "系统配置"
}
},
ja: {
welcome: {
title: "OpenManus ローカル版へようこそ",
subtitle: "タスクを開始するにはプロンプトを入力してください"
},
config: {
title: "システム設定",
subtitle: "システムを使用するために必要な設定情報を入力してください",
note: "以下の設定情報が正しいことを確認してください。",
apiKeyNote: "APIキーをお持ちでない場合は、対応するAIサービスプロバイダーから取得してください。",
llmConfig: "LLM 設定",
serverConfig: "サーバー設定",
save: "設定を保存",
cancel: "キャンセル",
required: "必須"
},
input: {
placeholder: "タスクプロンプトを入力...",
button: "タスク作成"
},
history: {
title: "タスク履歴"
},
status: {
running: "タスク実行中",
completed: "タスク完了",
failed: "タスク失敗",
error: "エラー"
},
buttons: {
configSetting: "システム設定"
}
},
ko: {
welcome: {
title: "OpenManus 로컬 버전에 오신 것을 환영합니다",
subtitle: "새 작업을 시작하려면 프롬프트를 입력하세요"
},
config: {
title: "시스템 설정",
subtitle: "시스템을 계속 사용하려면 필요한 구성 정보를 입력하세요",
note: "다음 구성 정보가 올바른지 확인하세요.",
apiKeyNote: "API 키가 없는 경우 해당 AI 서비스 제공업체에서 얻으세요.",
llmConfig: "LLM 설정",
serverConfig: "서버 설정",
save: "설정 저장",
cancel: "취소",
required: "필수"
},
input: {
placeholder: "작업 프롬프트 입력...",
button: "작업 생성"
},
history: {
title: "작업 기록"
},
status: {
running: "작업 실행 중",
completed: "작업 완료",
failed: "작업 실패",
error: "오류"
},
buttons: {
configSetting: "시스템 설정"
}
},
de: {
welcome: {
title: "Willkommen bei OpenManus Lokale Version",
subtitle: "Bitte geben Sie eine Aufgabenanweisung ein, um eine neue Aufgabe zu starten"
},
config: {
title: "Systemkonfiguration",
subtitle: "Bitte füllen Sie die erforderlichen Konfigurationsinformationen aus",
note: "Bitte stellen Sie sicher, dass die folgenden Konfigurationsinformationen korrekt sind.",
apiKeyNote: "Wenn Sie keinen API-Schlüssel haben, erhalten Sie diesen vom entsprechenden KI-Dienstanbieter.",
llmConfig: "LLM Konfiguration",
serverConfig: "Server Konfiguration",
save: "Konfiguration speichern",
cancel: "Abbrechen",
required: "Erforderlich"
},
input: {
placeholder: "Aufgabenanweisung eingeben...",
button: "Aufgabe erstellen"
},
history: {
title: "Aufgabenverlauf"
},
status: {
running: "Aufgabe wird ausgeführt",
completed: "Aufgabe abgeschlossen",
failed: "Aufgabe fehlgeschlagen",
error: "Fehler"
},
buttons: {
configSetting: "Systemeinstellungen"
}
}
};
function getCurrentLanguage() {
return localStorage.getItem('language') || 'en';
}
function setLanguage(lang) {
localStorage.setItem('language', lang);
updatePageContent(lang);
}
function updatePageContent(lang) {
const texts = i18n[lang];
document.querySelector('.welcome-message h1').textContent = texts.welcome.title;
document.querySelector('.welcome-message p').textContent = texts.welcome.subtitle;
document.getElementById('config-button').value = texts.buttons.configSetting;
document.getElementById('config-button').title = texts.buttons.configSetting;
document.querySelector('.config-modal-header h2').textContent = texts.config.title;
document.querySelector('.config-modal-header p').textContent = texts.config.subtitle;
document.querySelector('.note-box p:first-child').textContent = texts.config.note;
document.querySelector('.note-box p:last-child').textContent = texts.config.apiKeyNote;
document.querySelectorAll('.config-section h3').forEach(h3 => {
if (h3.textContent.includes('LLM')) {
h3.textContent = texts.config.llmConfig;
} else if (h3.textContent.includes('Server')) {
h3.textContent = texts.config.serverConfig;
}
});
document.getElementById('save-config-btn').textContent = texts.config.save;
document.getElementById('cancel-config-btn').textContent = texts.config.cancel;
document.getElementById('prompt-input').placeholder = texts.input.placeholder;
document.querySelector('#input-container button').textContent = texts.input.button;
document.querySelector('.history-panel h2').textContent = texts.history.title;
document.querySelectorAll('.required-mark').forEach(mark => {
mark.textContent = texts.config.required;
});
}
document.addEventListener('DOMContentLoaded', () => {
const currentLang = getCurrentLanguage();
document.getElementById('language-select').value = currentLang;
updatePageContent(currentLang);
document.getElementById('language-select').addEventListener('change', (e) => {
setLanguage(e.target.value);
});
});
+752
View File
@@ -0,0 +1,752 @@
let currentEventSource = null;
let exampleApiKey = '';
function checkConfigStatus() {
fetch('/config/status')
.then(response => response.json())
.then(data => {
const inputContainer = document.getElementById('input-container');
if (data.status === 'missing') {
showConfigModal(data.example_config);
inputContainer.classList.add('disabled');
} else if (data.status === 'no_example') {
alert('Error: Missing configuration example file! Please ensure that the config/config.example.toml file exists.');
inputContainer.classList.add('disabled');
} else {
inputContainer.classList.remove('disabled');
}
})
.catch(error => {
console.error('Configuration check failed:', error);
document.getElementById('input-container').classList.add('disabled');
});
}
// Display configuration pop-up and fill in sample configurations
function showConfigModal(config) {
const configModal = document.getElementById('config-modal');
if (!configModal) return;
configModal.classList.add('active');
if (config) {
fillConfigForm(config);
}
const closeBtn = configModal.querySelector('.close-modal');
const cancelBtn = document.getElementById('cancel-config-btn');
function closeConfigModal() {
configModal.classList.remove('active');
document.getElementById('config-error').textContent = '';
document.querySelectorAll('.form-group.error').forEach(group => {
group.classList.remove('error');
});
}
if (closeBtn) {
closeBtn.onclick = closeConfigModal;
}
if (cancelBtn) {
cancelBtn.onclick = closeConfigModal;
}
const saveButton = document.getElementById('save-config-btn');
if (saveButton) {
saveButton.onclick = saveConfig;
}
}
// Use example configuration to fill in the form
function fillConfigForm(exampleConfig) {
if (exampleConfig.llm) {
const llm = exampleConfig.llm;
setInputValue('llm-model', llm.model);
setInputValue('llm-base-url', llm.base_url);
setInputValue('llm-api-key', llm.api_key);
exampleApiKey = llm.api_key || '';
setInputValue('llm-max-tokens', llm.max_tokens);
setInputValue('llm-temperature', llm.temperature);
}
if (exampleConfig.server) {
setInputValue('server-host', exampleConfig.server.host);
setInputValue('server-port', exampleConfig.server.port);
}
}
function setInputValue(id, value) {
const input = document.getElementById(id);
if (input && value !== undefined) {
input.value = value;
}
}
function saveConfig() {
const configData = collectFormData();
const requiredFields = [
{ id: 'llm-model', name: 'Model Name' },
{ id: 'llm-base-url', name: 'API Base URL' },
{ id: 'llm-api-key', name: 'API Key' },
{ id: 'server-host', name: 'Server Host' },
{ id: 'server-port', name: 'Server Port' }
];
let missingFields = [];
requiredFields.forEach(field => {
if (!document.getElementById(field.id).value.trim()) {
missingFields.push(field.name);
}
});
if (missingFields.length > 0) {
document.getElementById('config-error').textContent =
`Please fill in the necessary configuration information: ${missingFields.join(', ')}`;
return;
}
// Check if the API key is the same as the example configuration
const apiKey = document.getElementById('llm-api-key').value.trim();
if (apiKey === exampleApiKey && exampleApiKey.includes('sk-')) {
document.getElementById('config-error').textContent =
`Please enter your own API key`;
document.getElementById('llm-api-key').parentElement.classList.add('error');
return;
} else {
document.getElementById('llm-api-key').parentElement.classList.remove('error');
}
fetch('/config/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(configData)
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
document.getElementById('config-modal').classList.remove('active');
document.getElementById('input-container').classList.remove('disabled');
alert('Configuration saved successfully! The application will use the new configuration on next startup.');
window.location.reload();
} else {
document.getElementById('config-error').textContent =
`Save failed: ${data.message}`;
}
})
.catch(error => {
document.getElementById('config-error').textContent =
`Request error: ${error.message}`;
});
}
// Collect form data
function collectFormData() {
const configData = {
llm: {
model: document.getElementById('llm-model').value,
base_url: document.getElementById('llm-base-url').value,
api_key: document.getElementById('llm-api-key').value
},
server: {
host: document.getElementById('server-host').value,
port: parseInt(document.getElementById('server-port').value || '5172')
}
};
const maxTokens = document.getElementById('llm-max-tokens').value;
if (maxTokens) {
configData.llm.max_tokens = parseInt(maxTokens);
}
const temperature = document.getElementById('llm-temperature').value;
if (temperature) {
configData.llm.temperature = parseFloat(temperature);
}
return configData;
}
function createTask() {
const promptInput = document.getElementById('prompt-input');
const prompt = promptInput.value.trim();
if (!prompt) {
alert("Please enter a valid prompt");
promptInput.focus();
return;
}
if (currentEventSource) {
currentEventSource.close();
currentEventSource = null;
}
const container = document.getElementById('task-container');
container.innerHTML = '<div class="loading">Initializing task...</div>';
document.getElementById('input-container').classList.add('bottom');
fetch('/tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ prompt })
})
.then(response => {
if (!response.ok) {
return response.json().then(err => { throw new Error(err.detail || 'Request failed') });
}
return response.json();
})
.then(data => {
if (!data.task_id) {
throw new Error('Invalid task ID');
}
setupSSE(data.task_id);
loadHistory();
promptInput.value = '';
})
.catch(error => {
container.innerHTML = `<div class="error">Error: ${error.message}</div>`;
console.error('Failed to create task:', error);
});
}
function setupSSE(taskId) {
let retryCount = 0;
const maxRetries = 3;
const retryDelay = 2000;
let lastResultContent = '';
const container = document.getElementById('task-container');
function connect() {
const eventSource = new EventSource(`/tasks/${taskId}/events`);
currentEventSource = eventSource;
let heartbeatTimer = setInterval(() => {
container.innerHTML += '<div class="ping">·</div>';
}, 5000);
// Initial polling
fetch(`/tasks/${taskId}`)
.then(response => response.json())
.then(task => {
updateTaskStatus(task);
})
.catch(error => {
console.error('Initial status fetch failed:', error);
});
const handleEvent = (event, type) => {
clearInterval(heartbeatTimer);
try {
const data = JSON.parse(event.data);
container.querySelector('.loading')?.remove();
container.classList.add('active');
const stepContainer = ensureStepContainer(container);
const { formattedContent, timestamp } = formatStepContent(data, type);
const step = createStepElement(type, formattedContent, timestamp);
stepContainer.appendChild(step);
autoScroll(stepContainer);
fetch(`/tasks/${taskId}`)
.then(response => response.json())
.then(task => {
updateTaskStatus(task);
})
.catch(error => {
console.error('Status update failed:', error);
});
} catch (e) {
console.error(`Error handling ${type} event:`, e);
}
};
const eventTypes = ['think', 'tool', 'act', 'log', 'run', 'message'];
eventTypes.forEach(type => {
eventSource.addEventListener(type, (event) => handleEvent(event, type));
});
eventSource.addEventListener('complete', (event) => {
clearInterval(heartbeatTimer);
try {
const data = JSON.parse(event.data);
lastResultContent = data.result || '';
container.innerHTML += `
<div class="complete">
<div>✅ Task completed</div>
<pre>${lastResultContent}</pre>
</div>
`;
fetch(`/tasks/${taskId}`)
.then(response => response.json())
.then(task => {
updateTaskStatus(task);
})
.catch(error => {
console.error('Final status update failed:', error);
});
eventSource.close();
currentEventSource = null;
} catch (e) {
console.error('Error handling complete event:', e);
}
});
eventSource.addEventListener('error', (event) => {
clearInterval(heartbeatTimer);
try {
const data = JSON.parse(event.data);
container.innerHTML += `
<div class="error">
❌ Error: ${data.message}
</div>
`;
eventSource.close();
currentEventSource = null;
} catch (e) {
console.error('Error handling failed:', e);
}
});
eventSource.onerror = (err) => {
if (eventSource.readyState === EventSource.CLOSED) return;
console.error('SSE connection error:', err);
clearInterval(heartbeatTimer);
eventSource.close();
fetch(`/tasks/${taskId}`)
.then(response => response.json())
.then(task => {
if (task.status === 'completed' || task.status === 'failed') {
updateTaskStatus(task);
if (task.status === 'completed') {
container.innerHTML += `
<div class="complete">
<div>✅ Task completed</div>
</div>
`;
} else {
container.innerHTML += `
<div class="error">
❌ Error: ${task.error || 'Task failed'}
</div>
`;
}
} else if (retryCount < maxRetries) {
retryCount++;
container.innerHTML += `
<div class="warning">
⚠ Connection lost, retrying in ${retryDelay/1000} seconds (${retryCount}/${maxRetries})...
</div>
`;
setTimeout(connect, retryDelay);
} else {
container.innerHTML += `
<div class="error">
⚠ Connection lost, please try refreshing the page
</div>
`;
}
})
.catch(error => {
console.error('Task status check failed:', error);
if (retryCount < maxRetries) {
retryCount++;
setTimeout(connect, retryDelay);
}
});
};
}
connect();
}
function loadHistory() {
fetch('/tasks')
.then(response => {
if (!response.ok) {
return response.text().then(text => {
throw new Error(`request failure: ${response.status} - ${text.substring(0, 100)}`);
});
}
return response.json();
})
.then(tasks => {
const listContainer = document.getElementById('task-list');
listContainer.innerHTML = tasks.map(task => `
<div class="task-card" data-task-id="${task.id}">
<div>${task.prompt}</div>
<div class="task-meta">
${new Date(task.created_at).toLocaleString()} -
<span class="status status-${task.status ? task.status.toLowerCase() : 'unknown'}">
${task.status || 'Unknown state'}
</span>
</div>
</div>
`).join('');
})
.catch(error => {
console.error('Failed to load history records:', error);
const listContainer = document.getElementById('task-list');
listContainer.innerHTML = `<div class="error">Load Fail: ${error.message}</div>`;
});
}
function ensureStepContainer(container) {
let stepContainer = container.querySelector('.step-container');
if (!stepContainer) {
container.innerHTML = '<div class="step-container"></div>';
stepContainer = container.querySelector('.step-container');
}
return stepContainer;
}
function formatStepContent(data, eventType) {
return {
formattedContent: data.result,
timestamp: new Date().toLocaleTimeString()
};
}
function createStepElement(type, content, timestamp) {
const step = document.createElement('div');
// Executing step
const stepRegex = /Executing step (\d+)\/(\d+)/;
if (type === 'log' && stepRegex.test(content)) {
const match = content.match(stepRegex);
const currentStep = parseInt(match[1]);
const totalSteps = parseInt(match[2]);
step.className = 'step-divider';
step.innerHTML = `
<div class="step-circle">${currentStep}</div>
<div class="step-line"></div>
<div class="step-info">${currentStep}/${totalSteps}</div>
`;
} else if (type === 'act') {
// Check if it contains information about file saving
const saveRegex = /Content successfully saved to (.+)/;
const match = content.match(saveRegex);
step.className = `step-item ${type}`;
if (match && match[1]) {
const filePath = match[1].trim();
const fileName = filePath.split('/').pop();
const fileExtension = fileName.split('.').pop().toLowerCase();
// Handling different types of files
let fileInteractionHtml = '';
if (['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'].includes(fileExtension)) {
fileInteractionHtml = `
<div class="file-interaction image-preview">
<img src="${filePath}" alt="${fileName}" class="preview-image" onclick="showFullImage('${filePath}')">
<a href="/download?file_path=${filePath}" download="${fileName}" class="download-link">⬇️ 下载图片</a>
</div>
`;
} else if (['mp3', 'wav', 'ogg'].includes(fileExtension)) {
fileInteractionHtml = `
<div class="file-interaction audio-player">
<audio controls src="${filePath}"></audio>
<a href="/download?file_path=${filePath}" download="${fileName}" class="download-link">⬇️ 下载音频</a>
</div>
`;
} else if (['html', 'js', 'py'].includes(fileExtension)) {
fileInteractionHtml = `
<div class="file-interaction code-file">
<button onclick="simulateRunPython('${filePath}')" class="run-button">▶️ 模拟运行</button>
<a href="/download?file_path=${filePath}" download="${fileName}" class="download-link">⬇️ 下载文件</a>
</div>
`;
} else {
fileInteractionHtml = `
<div class="file-interaction">
<a href="/download?file_path=${filePath}" download="${fileName}" class="download-link">⬇️ 下载文件: ${fileName}</a>
</div>
`;
}
step.innerHTML = `
<div class="log-line">
<span class="log-prefix">${getEventIcon(type)} [${timestamp}] ${getEventLabel(type)}:</span>
<pre>${content}</pre>
${fileInteractionHtml}
</div>
`;
} else {
step.innerHTML = `
<div class="log-line">
<span class="log-prefix">${getEventIcon(type)} [${timestamp}] ${getEventLabel(type)}:</span>
<pre>${content}</pre>
</div>
`;
}
} else {
step.className = `step-item ${type}`;
step.innerHTML = `
<div class="log-line">
<span class="log-prefix">${getEventIcon(type)} [${timestamp}] ${getEventLabel(type)}:</span>
<pre>${content}</pre>
</div>
`;
}
return step;
}
function autoScroll(element) {
requestAnimationFrame(() => {
element.scrollTo({
top: element.scrollHeight,
behavior: 'smooth'
});
});
setTimeout(() => {
element.scrollTop = element.scrollHeight;
}, 100);
}
function getEventIcon(eventType) {
const icons = {
'think': '🤔',
'tool': '🛠️',
'act': '🚀',
'result': '🏁',
'error': '❌',
'complete': '✅',
'log': '📝',
'run': '⚙️'
};
return icons[eventType] || '️';
}
function getEventLabel(eventType) {
const labels = {
'think': 'Thinking',
'tool': 'Using Tool',
'act': 'Action',
'result': 'Result',
'error': 'Error',
'complete': 'Complete',
'log': 'Log',
'run': 'Running'
};
return labels[eventType] || 'Info';
}
function updateTaskStatus(task) {
const statusBar = document.getElementById('status-bar');
if (!statusBar) return;
if (task.status === 'completed') {
statusBar.innerHTML = `<span class="status-complete">✅ Task completed</span>`;
if (currentEventSource) {
currentEventSource.close();
currentEventSource = null;
}
} else if (task.status === 'failed') {
statusBar.innerHTML = `<span class="status-error">❌ Task failed: ${task.error || 'Unknown error'}</span>`;
if (currentEventSource) {
currentEventSource.close();
currentEventSource = null;
}
} else {
statusBar.innerHTML = `<span class="status-running">⚙️ Task running: ${task.status}</span>`;
}
}
// Display full screen image
function showFullImage(imageSrc) {
const modal = document.getElementById('image-modal');
if (!modal) {
const modalDiv = document.createElement('div');
modalDiv.id = 'image-modal';
modalDiv.className = 'image-modal';
modalDiv.innerHTML = `
<span class="close-modal">&times;</span>
<img src="${imageSrc}" class="modal-content" id="full-image">
`;
document.body.appendChild(modalDiv);
const closeBtn = modalDiv.querySelector('.close-modal');
closeBtn.addEventListener('click', () => {
modalDiv.classList.remove('active');
});
modalDiv.addEventListener('click', (e) => {
if (e.target === modalDiv) {
modalDiv.classList.remove('active');
}
});
setTimeout(() => modalDiv.classList.add('active'), 10);
} else {
document.getElementById('full-image').src = imageSrc;
modal.classList.add('active');
}
}
// Simulate running Python files
function simulateRunPython(filePath) {
let modal = document.getElementById('python-modal');
if (!modal) {
modal = document.createElement('div');
modal.id = 'python-modal';
modal.className = 'python-modal';
modal.innerHTML = `
<div class="python-console">
<div class="close-modal">&times;</div>
<div class="python-output">Loading Python file contents...</div>
</div>
`;
document.body.appendChild(modal);
const closeBtn = modal.querySelector('.close-modal');
closeBtn.addEventListener('click', () => {
modal.classList.remove('active');
});
}
modal.classList.add('active');
// Load Python file content
fetch(filePath)
.then(response => response.text())
.then(code => {
const outputDiv = modal.querySelector('.python-output');
outputDiv.innerHTML = '';
const codeElement = document.createElement('pre');
codeElement.textContent = code;
codeElement.style.marginBottom = '20px';
codeElement.style.padding = '10px';
codeElement.style.borderBottom = '1px solid #444';
outputDiv.appendChild(codeElement);
// Add simulation run results
const resultElement = document.createElement('div');
resultElement.innerHTML = `
<div style="color: #4CAF50; margin-top: 10px; margin-bottom: 10px;">
> Simulated operation output:</div>
<pre style="color: #f8f8f8;">
#This is the result of Python code simulation run
#The actual operational results may vary
# Running ${filePath.split('/').pop()}...
print("Hello from Python Simulated environment!")
# Code execution completed
</pre>
`;
outputDiv.appendChild(resultElement);
})
.catch(error => {
console.error('Error loading Python file:', error);
const outputDiv = modal.querySelector('.python-output');
outputDiv.innerHTML = `Error loading file: ${error.message}`;
});
}
document.addEventListener('DOMContentLoaded', () => {
// Check configuration status on startup
checkConfigStatus();
loadHistory();
const configButton = document.getElementById('config-button');
if (configButton) {
configButton.addEventListener('click', () => {
fetch('/config/status')
.then(response => response.json())
.then(data => {
console.log(data);
if (data.status === 'exists' && data.config) {
showConfigModal(data.config);
} else if (data.status === 'missing' && data.example_config) {
showConfigModal(data.example_config);
} else if (data.status === 'no_example') {
alert('Error: Missing configuration example file! Please ensure that the config/config.example.toml file exists.');
} else if (data.status === 'error') {
alert('Configuration error: ' + data.message);
} else {
alert('Unable to load configuration. Please check if config.example.toml exists.');
}
})
.catch(error => {
console.error('Failed to fetch configuration:', error);
alert('Failed to load configuration. Please check if the server is running and try again.');
});
});
}
document.getElementById('prompt-input').addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
createTask();
}
});
const historyToggle = document.getElementById('history-toggle');
if (historyToggle) {
historyToggle.addEventListener('click', () => {
const historyPanel = document.getElementById('history-panel');
if (historyPanel) {
historyPanel.classList.toggle('open');
historyToggle.classList.toggle('active');
}
});
}
const clearButton = document.getElementById('clear-btn');
if (clearButton) {
clearButton.addEventListener('click', () => {
document.getElementById('prompt-input').value = '';
document.getElementById('prompt-input').focus();
});
}
// Add keyboard event listener to close modal boxes
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
const imageModal = document.getElementById('image-modal');
if (imageModal && imageModal.classList.contains('active')) {
imageModal.classList.remove('active');
}
const pythonModal = document.getElementById('python-modal');
if (pythonModal && pythonModal.classList.contains('active')) {
pythonModal.classList.remove('active');
}
const configModal = document.getElementById('config-modal');
if (configModal && configModal.classList.contains('active') && !isConfigRequired()) {
configModal.classList.remove('active');
}
}
});
});
function isConfigRequired() {
return false;
}
+812
View File
@@ -0,0 +1,812 @@
:root {
--primary-color: #007bff;
--primary-hover: #0056b3;
--success-color: #28a745;
--error-color: #dc3545;
--warning-color: #ff9800;
--info-color: #2196f3;
--text-color: #333;
--text-light: #666;
--bg-color: #f8f9fa;
--border-color: #ddd;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
margin: 0;
padding: 0;
background-color: var(--bg-color);
color: var(--text-color);
}
.container {
display: flex;
min-height: 100vh;
min-width: 0;
width: 90%;
margin: 0 auto;
padding: 20px;
gap: 20px;
}
.card {
background-color: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.side-panel {
width: 300px;
display: flex;
flex-direction: column;
gap: 20px;
min-height: calc(100vh - 40px);
}
.history-panel {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.set-config-panel {
display: flex;
gap: 10px;
align-items: center;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
#language-select {
flex: 0 0 120px;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 1rem;
background-color: var(--bg-color);
cursor: pointer;
transition: border-color 0.3s ease;
}
#language-select:hover {
border-color: var(--primary-color);
}
#language-select:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
#config-button {
flex: 1;
white-space: nowrap;
padding: 12px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s;
}
#config-button:hover {
background-color: var(--primary-hover);
}
.main-panel {
flex: 1;
display: flex;
flex-direction: column;
gap: 20px;
}
.task-list {
margin-top: 10px;
max-height: calc(100vh - 160px);
overflow-y: auto;
overflow-x: hidden;
}
.task-container {
@extend .card;
width: 100%;
max-width: 100%;
position: relative;
min-height: 300px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: auto;
height: 100%;
}
.welcome-message {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
color: var(--text-light);
background: white;
z-index: 1;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
width: 100%;
height: 100%;
}
.welcome-message h1 {
font-size: 2rem;
margin-bottom: 10px;
color: var(--text-color);
}
.input-container {
@extend .card;
display: flex;
gap: 10px;
}
.input-container.disabled {
opacity: 0.6;
pointer-events: none;
}
.input-container.disabled::after {
content: "Please configure the system first";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 8px 16px;
border-radius: 4px;
font-size: 0.9rem;
white-space: nowrap;
}
#prompt-input {
flex: 1;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
button {
padding: 12px 24px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s;
}
button:hover {
background-color: var(--primary-hover);
}
.task-item {
padding: 10px;
margin-bottom: 10px;
background-color: #f8f9fa;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.task-item:hover {
background-color: #e9ecef;
}
.task-item.active {
background-color: var(--primary-color);
color: white;
}
#input-container.bottom {
margin-top: auto;
}
.task-card {
background: #fff;
padding: 15px;
margin-bottom: 10px;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.task-card:hover {
transform: translateX(5px);
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.status-pending {
color: var(--text-light);
}
.status-running {
color: var(--primary-color);
}
.status-completed {
color: var(--success-color);
}
.status-failed {
color: var(--error-color);
}
.step-container {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
max-height: calc(100vh - 200px);
overflow-y: auto;
max-width: 100%;
overflow-x: hidden;
}
.step-item {
padding: 15px;
background: white;
border-radius: 8px;
width: 100%;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin-bottom: 10px;
opacity: 1;
transform: none;
}
.step-item .log-line:not(.result) {
opacity: 0.7;
color: #666;
font-size: 0.9em;
}
.step-item .log-line.result {
opacity: 1;
color: #333;
font-size: 1em;
background: #e8f5e9;
border-left: 4px solid #4caf50;
padding: 10px;
border-radius: 4px;
}
.step-item.show {
opacity: 1;
transform: none;
}
.log-line {
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
display: flex;
flex-direction: column;
gap: 4px;
}
.log-line.think,
.step-item pre.think {
background: var(--info-color-light);
border-left: 4px solid var(--info-color);
}
.log-line.tool,
.step-item pre.tool {
background: var(--warning-color-light);
border-left: 4px solid var(--warning-color);
}
.log-line.result,
.step-item pre.result {
background: var(--success-color-light);
border-left: 4px solid var(--success-color);
}
.log-line.error,
.step-item pre.error {
background: var(--error-color-light);
border-left: 4px solid var(--error-color);
}
.log-line.info,
.step-item pre.info {
background: var(--bg-color);
border-left: 4px solid var(--text-light);
}
.log-prefix {
font-weight: bold;
white-space: nowrap;
margin-bottom: 5px;
color: #666;
}
.step-item pre {
padding: 10px;
border-radius: 4px;
margin-left: 20px;
overflow-x: hidden;
font-family: 'Courier New', monospace;
font-size: 0.9em;
line-height: 1.4;
white-space: pre-wrap;
word-wrap: break-word;
word-break: break-all;
max-width: 100%;
color: var(--text-color);
background: var(--bg-color);
&.log {
background: var(--bg-color);
border-left: 4px solid var(--text-light);
}
&.think {
background: var(--info-color-light);
border-left: 4px solid var(--info-color);
}
&.tool {
background: var(--warning-color-light);
border-left: 4px solid var(--warning-color);
}
&.result {
background: var(--success-color-light);
border-left: 4px solid var(--success-color);
}
}
/* Step division line style */
.step-divider {
display: flex;
align-items: center;
width: 100%;
margin: 15px 0;
position: relative;
}
.step-circle {
width: 36px;
height: 36px;
border-radius: 50%;
background-color: var(--primary-color);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 1.2em;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
z-index: 2;
flex-shrink: 0;
}
/* File interaction style */
.file-interaction {
margin-top: 15px;
padding: 10px;
border-radius: 6px;
background-color: #f5f7fa;
border: 1px solid var(--border-color);
}
.download-link {
display: inline-block;
padding: 8px 16px;
background-color: var(--primary-color);
color: white;
text-decoration: none;
border-radius: 4px;
font-size: 0.9em;
transition: background-color 0.2s;
}
.download-link:hover {
background-color: var(--primary-hover);
}
.preview-image {
max-width: 100%;
max-height: 200px;
margin-bottom: 10px;
border-radius: 4px;
cursor: pointer;
transition: transform 0.2s;
}
.preview-image:hover {
transform: scale(1.02);
}
.audio-player {
display: flex;
flex-direction: column;
gap: 10px;
}
.audio-player audio {
width: 100%;
margin-bottom: 10px;
}
.run-button {
display: inline-block;
padding: 8px 16px;
background-color: var(--success-color);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
margin-right: 10px;
transition: background-color 0.2s;
}
.run-button:hover {
background-color: #218838;
}
/* Full screen image modal box */
.image-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000;
justify-content: center;
align-items: center;
}
.image-modal.active {
display: flex;
}
.modal-content {
max-width: 90%;
max-height: 90%;
}
.close-modal {
position: absolute;
top: 20px;
right: 30px;
color: white;
font-size: 30px;
cursor: pointer;
}
/* Python runs simulation modal boxes */
.python-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000;
justify-content: center;
align-items: center;
}
.python-modal.active {
display: flex;
}
.python-console {
width: 80%;
height: 80%;
background-color: #1e1e1e;
color: #f8f8f8;
border-radius: 8px;
padding: 15px;
font-family: 'Courier New', monospace;
overflow: auto;
}
.python-output {
white-space: pre-wrap;
line-height: 1.5;
}
.step-line {
flex-grow: 1;
height: 2px;
background-color: var(--border-color);
margin-left: 10px;
}
.step-info {
margin-left: 15px;
font-weight: bold;
color: var(--text-light);
font-size: 0.9em;
}
.step-item strong {
display: block;
margin-bottom: 8px;
color: #007bff;
font-size: 0.9em;
}
.step-item div {
color: #444;
line-height: 1.6;
}
.loading {
padding: 15px;
color: #666;
text-align: center;
}
.ping {
color: #ccc;
text-align: center;
margin: 5px 0;
}
.error {
color: #dc3545;
padding: 10px;
background: #ffe6e6;
border-radius: 4px;
margin: 10px 0;
}
.complete {
color: #28a745;
padding: 10px;
background: #e6ffe6;
border-radius: 4px;
margin: 10px 0;
}
pre {
margin: 0;
white-space: pre-wrap;
word-break: break-word;
}
.complete pre {
max-width: 100%;
white-space: pre-wrap;
word-break: break-word;
}
.config-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2000;
justify-content: center;
align-items: center;
}
.config-modal.active {
display: flex;
}
.config-modal-content {
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
width: 80%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
display: flex;
flex-direction: column;
}
.config-modal-header {
padding: 20px;
border-bottom: 1px solid var(--border-color);
background-color: var(--info-color);
color: white;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
position: relative;
}
.config-modal-header h2 {
margin-bottom: 10px;
font-size: 1.8rem;
}
.config-modal-header .close-modal {
position: absolute;
right: 20px;
top: 20px;
font-size: 24px;
color: white;
cursor: pointer;
transition: color 0.2s;
}
.config-modal-header .close-modal:hover {
color: #ddd;
}
.config-modal-body {
padding: 20px;
overflow-y: auto;
max-height: calc(90vh - 140px);
}
.config-modal-footer {
padding: 20px;
border-top: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.config-section {
margin-bottom: 25px;
padding-bottom: 20px;
border-bottom: 1px solid var(--border-color);
}
.config-section:last-child {
border-bottom: none;
}
.config-section h3 {
margin-bottom: 15px;
color: var(--info-color);
}
.form-group {
margin-bottom: 15px;
transition: all 0.3s ease;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: var(--text-color);
}
.form-group input {
width: 100%;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 0.9rem;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.form-group input:focus {
outline: none;
border-color: var(--info-color);
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.config-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
}
.primary-btn {
background-color: var(--info-color);
transition: background-color 0.3s ease;
}
.primary-btn:hover {
background-color: #0069d9;
}
.secondary-btn {
background-color: var(--text-light);
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s;
}
.secondary-btn:hover {
background-color: #555;
}
.config-error {
color: var(--error-color);
margin-right: 15px;
font-weight: bold;
padding: 8px 0;
transition: all 0.3s ease;
}
.form-group.error input {
border-color: var(--error-color);
box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.25);
background-color: rgba(220, 53, 69, 0.05);
}
.form-group.error label {
color: var(--error-color);
}
.form-group .error-message {
color: var(--error-color);
font-size: 0.8rem;
margin-top: 5px;
display: block;
animation: errorAppear 0.3s ease;
}
@keyframes errorAppear {
from {
opacity: 0;
transform: translateY(-5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.note-box {
background-color: #fff8e1;
border-left: 4px solid #ffc107;
padding: 15px;
margin-bottom: 20px;
border-radius: 4px;
}
.note-box p {
margin: 0 0 8px 0;
color: #856404;
}
.note-box p:last-child {
margin-bottom: 0;
}
.field-help {
font-size: 0.8rem;
color: #666;
margin-top: 4px;
display: block;
}
.required-mark {
color: var(--error-color);
font-weight: bold;
}
+113
View File
@@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenManus Local Version</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
<div class="side-panel">
<div class="history-panel">
<h2>History Tasks</h2>
<div id="task-list" class="task-list"></div>
</div>
<div class="set-config-panel">
<select id="language-select" aria-label="Select language">
<option value="en">English</option>
<option value="zh">中文</option>
<option value="ja">日本語</option>
<option value="ko">한국어</option>
<option value="de">Deutsch</option>
</select>
<input type="button" id="config-button" value="Configuration Setting" title="Configuration Settings">
</div>
</div>
<div class="main-panel">
<div id="task-container" class="task-container">
<div class="welcome-message">
<h1>Welcome to OpenManus Local Version</h1>
<p>Please enter a task prompt to start a new task</p>
</div>
<div id="log-container" class="step-container"></div>
</div>
<div id="input-container" class="input-container">
<input
type="text"
id="prompt-input"
placeholder="Enter task prompt..."
onkeypress="if(event.keyCode === 13) createTask()"
>
<button onclick="createTask()">Create Task</button>
</div>
</div>
</div>
<div id="config-modal" class="config-modal">
<div class="config-modal-content">
<div class="config-modal-header">
<h2>System Configuration</h2>
<p>Please fill in the necessary configuration information to continue using the system</p>
<span class="close-modal">&times;</span>
</div>
<div class="config-modal-body">
<div class="note-box">
<p>⚠️ Please ensure that the following configuration information is correct.</p>
<p>If you do not have an API key, please obtain one from the corresponding AI service provider.</p>
</div>
<div class="config-section">
<h3>LLM Configuration</h3>
<div class="form-group">
<label for="llm-model">Model Name <span class="required-mark">*</span></label>
<input type="text" id="llm-model" name="llm.model" placeholder="For example: claude-3-5-sonnet">
</div>
<div class="form-group">
<label for="llm-base-url">API Base URL <span class="required-mark">*</span></label>
<input type="text" id="llm-base-url" name="llm.base_url" placeholder="For example: https://api.openai.com/v1">
</div>
<div class="form-group">
<label for="llm-api-key">API Key <span class="required-mark">*</span></label>
<input type="password" id="llm-api-key" name="llm.api_key" placeholder="Your API key, for example: sk-...">
<span class="field-help">Must be your own valid API key, not the placeholder in the example</span>
</div>
<div class="form-group">
<label for="llm-max-tokens">Max Tokens</label>
<input type="number" id="llm-max-tokens" name="llm.max_tokens" placeholder="For example: 4096">
</div>
<div class="form-group">
<label for="llm-temperature">Temperature</label>
<input type="number" id="llm-temperature" name="llm.temperature" step="0.1" placeholder="For example: 0.0">
</div>
</div>
<div class="config-section">
<h3>Server Configuration</h3>
<div class="form-group">
<label for="server-host">Host <span class="required-mark">*</span></label>
<input type="text" id="server-host" name="server.host" placeholder="For example: localhost">
</div>
<div class="form-group">
<label for="server-port">Port <span class="required-mark">*</span></label>
<input type="number" id="server-port" name="server.port" placeholder="For example: 5172">
</div>
</div>
</div>
<div class="config-modal-footer">
<p id="config-error" class="config-error"></p>
<div class="config-actions">
<button id="cancel-config-btn" class="secondary-btn">Cancel</button>
<button id="save-config-btn" class="primary-btn">Save Configuration</button>
</div>
</div>
</div>
</div>
<script src="/static/main.js"></script>
<script src="/static/i18n.js"></script>
</body>
</html>